Bläddra i källkod

Bugfix: Major fixes to the scripting runtime
- All GC handles are now properly freed before domain/assembly reload
- When storing references to managed objects in objects on the GC heap, through native code, the GC is now properly notified
- Enabled GC debugging for easier error detection

BearishSun 7 år sedan
förälder
incheckning
0e4ba38c5b
42 ändrade filer med 461 tillägg och 330 borttagningar
  1. 25 4
      Source/BansheeMono/BsMonoArray.cpp
  2. 47 25
      Source/BansheeMono/BsMonoArray.h
  3. 29 25
      Source/BansheeMono/BsMonoAssembly.cpp
  4. 2 2
      Source/BansheeMono/BsMonoAssembly.h
  5. 33 59
      Source/BansheeMono/BsMonoManager.cpp
  6. 2 8
      Source/BansheeMono/BsMonoManager.h
  7. 7 0
      Source/BansheeMono/BsMonoUtil.cpp
  8. 7 0
      Source/BansheeMono/BsMonoUtil.h
  9. 36 36
      Source/BansheeUtility/Debug/BsDebug.cpp
  10. 1 1
      Source/CMakeLists.txt
  11. 1 1
      Source/MBansheeEditor/Utility/UndoRedo.cs
  12. 34 34
      Source/MBansheeEngine/Utility/PixelUtility.cs
  13. 112 14
      Source/SBansheeEditor/BsManagedEditorCommand.cpp
  14. 26 4
      Source/SBansheeEditor/BsManagedEditorCommand.h
  15. 10 10
      Source/SBansheeEditor/Wrappers/BsScriptEditorWindow.cpp
  16. 1 2
      Source/SBansheeEditor/Wrappers/BsScriptEditorWindow.h
  17. 4 4
      Source/SBansheeEditor/Wrappers/GUI/BsScriptGUIEnumField.cpp
  18. 1 1
      Source/SBansheeEngine/BsScriptObject.cpp
  19. 17 4
      Source/SBansheeEngine/BsScriptObject.h
  20. 7 6
      Source/SBansheeEngine/BsScriptObjectManager.cpp
  21. 3 2
      Source/SBansheeEngine/BsScriptObjectManager.h
  22. 2 3
      Source/SBansheeEngine/Serialization/BsManagedSerializableArray.cpp
  23. 2 2
      Source/SBansheeEngine/Wrappers/BsScriptComponent.cpp
  24. 4 4
      Source/SBansheeEngine/Wrappers/BsScriptComponent.h
  25. 1 15
      Source/SBansheeEngine/Wrappers/BsScriptGameObject.cpp
  26. 0 6
      Source/SBansheeEngine/Wrappers/BsScriptGameObject.h
  27. 2 2
      Source/SBansheeEngine/Wrappers/BsScriptInputConfiguration.cpp
  28. 1 1
      Source/SBansheeEngine/Wrappers/BsScriptInputConfiguration.h
  29. 2 6
      Source/SBansheeEngine/Wrappers/BsScriptManagedComponent.cpp
  30. 1 1
      Source/SBansheeEngine/Wrappers/BsScriptManagedComponent.h
  31. 3 7
      Source/SBansheeEngine/Wrappers/BsScriptManagedResource.cpp
  32. 1 1
      Source/SBansheeEngine/Wrappers/BsScriptManagedResource.h
  33. 2 17
      Source/SBansheeEngine/Wrappers/BsScriptResource.cpp
  34. 4 10
      Source/SBansheeEngine/Wrappers/BsScriptResource.h
  35. 3 3
      Source/SBansheeEngine/Wrappers/BsScriptSceneObject.cpp
  36. 1 1
      Source/SBansheeEngine/Wrappers/BsScriptSceneObject.h
  37. 3 3
      Source/SBansheeEngine/Wrappers/GUI/BsScriptGUICanvas.cpp
  38. 9 2
      Source/SBansheeEngine/Wrappers/GUI/BsScriptGUIElement.cpp
  39. 4 1
      Source/SBansheeEngine/Wrappers/GUI/BsScriptGUIElement.h
  40. 8 0
      Source/SBansheeEngine/Wrappers/GUI/BsScriptGUILayout.cpp
  41. 2 2
      Source/SBansheeEngine/Wrappers/GUI/BsScriptGUIWidget.cpp
  42. 1 1
      Source/SBansheeEngine/Wrappers/GUI/BsScriptGUIWidget.h

+ 25 - 4
Source/BansheeMono/BsMonoArray.cpp

@@ -26,14 +26,14 @@ namespace bs
 		void ScriptArray_set<String>(MonoArray* array, UINT32 idx, const String& value)
 		{
 			MonoString* monoString = MonoUtil::stringToMono(value);
-			mono_array_set(array, MonoString*, idx, monoString);
+			mono_array_setref(array, idx, monoString);
 		}
 
 		template<>
 		void ScriptArray_set<WString>(MonoArray* array, UINT32 idx, const WString& value)
 		{
 			MonoString* monoString = MonoUtil::wstringToMono(value);
-			mono_array_set(array, MonoString*, idx, monoString);
+			mono_array_setref(array, idx, monoString);
 		}
 
 		template String ScriptArray_get(MonoArray* array, UINT32 idx);
@@ -46,7 +46,6 @@ namespace bs
 	ScriptArray::ScriptArray(MonoArray* existingArray)
 		:mInternal(existingArray)
 	{
-
 	}
 
 	ScriptArray::ScriptArray(MonoClass& klass, UINT32 size)
@@ -74,11 +73,33 @@ namespace bs
 		return (UINT32)mono_class_array_element_size(elementClass);
 	}
 
-	UINT8* ScriptArray::getArrayAddr(MonoArray* array, UINT32 size, UINT32 idx)
+	void ScriptArray::setRaw(UINT32 idx, const UINT8* value, UINT32 size, UINT32 count)
+	{
+		_setArrayVal(mInternal, idx, value, size, count);
+	}
+
+	UINT8* ScriptArray::_getArrayAddr(MonoArray* array, UINT32 size, UINT32 idx)
 	{
 		return (UINT8*)mono_array_addr_with_size(array, size, idx);
 	}
 
+	void ScriptArray::_setArrayVal(MonoArray* array, UINT32 idx, const UINT8* value, UINT32 size, UINT32 count)
+	{
+		::MonoClass* arrayClass = mono_object_get_class((MonoObject*)(array));
+		::MonoClass* elementClass = mono_class_get_element_class(arrayClass);
+
+		BS_ASSERT((UINT32)mono_class_array_element_size(elementClass) == size);
+		BS_ASSERT((idx + count) <= mono_array_length(array));
+
+		if(mono_class_is_valuetype(elementClass))
+			mono_value_copy_array(array, idx, (void*)value, count);
+		else
+		{
+			UINT8* dest = _getArrayAddr(array, size, idx);
+			mono_gc_wbarrier_arrayref_copy(dest, (void*)value, count);
+		}
+	}
+
 	::MonoClass* ScriptArray::getElementClass(::MonoClass* arrayClass)
 	{
 		return mono_class_get_element_class(arrayClass);

+ 47 - 25
Source/BansheeMono/BsMonoArray.h

@@ -28,32 +28,39 @@ namespace bs
 		template<class T>
 		T get(UINT32 idx);
 
-		/** Sets an entry from the array at the specified index. */
+		/** Assigns a value to the specified index. */
 		template<class T>
 		void set(UINT32 idx, const T& value);
 
-		/** Returns the raw object from the array at the specified index. */
-		template<class T>
-		T* getRawPtr(UINT32 idx = 0)
+		/** 
+		 * Assigns some data represented as raw memory to the array at the specified index. User must provide the size of
+		 * the data, and it must match the element size expected by the array. Multiple array elements can be provided
+		 * sequentially by setting the @p count parameter.
+		 */
+		void setRaw(UINT32 idx, const UINT8* value, UINT32 size, UINT32 count = 1);
+
+		/** 
+		 * Returns the raw memory of the data at the specified array index. Returned value should not be used for writing
+		 * to the array and set() or setRaw() methods should be used instead.
+		 */
+		UINT8* getRaw(UINT32 idx, UINT32 size)
 		{
 #if BS_DEBUG_MODE
-			assert(sizeof(T) == elementSize());
+			assert(size == elementSize());
 #endif
-
-			return (T*)getArrayAddr(mInternal, sizeof(T), idx);
+			return _getArrayAddr(mInternal, size, idx);
 		}
-
 		/** 
-		 * Returns the raw object from the array at the specified index. Provided size determines the size of each
-		 * element in the array. Caller must ensure it is correct for the specified array.
+		 * Returns the raw memory of the data at the specified array index. Returned value should not be used for writing
+		 * to the array and set() or setRaw() methods should be used instead.
 		 */
-		UINT8* getRawPtr(UINT32 size, UINT32 idx)
+		template<class T>
+		T* getRaw(UINT32 idx = 0)
 		{
 #if BS_DEBUG_MODE
-			assert(size == elementSize());
+			assert(sizeof(T) == elementSize());
 #endif
-
-			return getArrayAddr(mInternal, size, idx);
+			return (T*)_getArrayAddr(mInternal, sizeof(T), idx);
 		}
 
 		/** 
@@ -73,6 +80,20 @@ namespace bs
 		/** Returns the managed object representing this array. */
 		MonoArray* getInternal() const { return mInternal; }
 
+		/** Returns the class of the elements within an array class. */
+		static ::MonoClass* getElementClass(::MonoClass* arrayClass);
+
+		/** Returns the rank of the provided array class. */
+		static UINT32 getRank(::MonoClass* arrayClass);
+
+		/** Builds an array class from the provided element class and a rank. */
+		static ::MonoClass* buildArrayClass(::MonoClass* elementClass, UINT32 rank);
+
+		/**
+		 * @name Internal
+		 * @{
+		 */
+
 		/** 
 		 * Returns the address of an array item at the specified index. 
 		 *
@@ -81,17 +102,19 @@ namespace bs
 		 * @param[in]	idx		Index of the item to retrieve.
 		 * @return				Address of the array item at the requested index. 
 		 */
-		static UINT8* getArrayAddr(MonoArray* array, UINT32 size, UINT32 idx);
-
-		/** Returns the class of the elements within an array class. */
-		static ::MonoClass* getElementClass(::MonoClass* arrayClass);
+		static UINT8* _getArrayAddr(MonoArray* array, UINT32 size, UINT32 idx);
 
-		/** Returns the rank of the provided array class. */
-		static UINT32 getRank(::MonoClass* arrayClass);
+		/** 
+		 * Sets one or multiple entries from the array at the specified index, from raw memory. User must provide the size
+		 * of the element, and it must match the element size expected by the array.
+		 */
+		static void _setArrayVal(MonoArray* array, UINT32 idx, const UINT8* value, UINT32 size, UINT32 count = 1);
 
-		/** Builds an array class from the provided element class and a rank. */
-		static ::MonoClass* buildArrayClass(::MonoClass* elementClass, UINT32 rank);
+		/**
+		 * @}
+		 */
 	private:
+
 		MonoArray* mInternal;
 	};
 
@@ -107,14 +130,13 @@ namespace bs
 		template<class T>
 		T ScriptArray_get(MonoArray* array, UINT32 idx)
 		{
-			return *(T*)ScriptArray::getArrayAddr(array, sizeof(T), idx);
+			return *(T*)ScriptArray::_getArrayAddr(array, sizeof(T), idx);
 		}
 
 		template<class T>
 		void ScriptArray_set(MonoArray* array, UINT32 idx, const T& value)
 		{
-			T* item = (T*)ScriptArray::getArrayAddr(array, sizeof(T), idx);
-			*item = value;
+			ScriptArray::_setArrayVal(array, idx, (UINT8*)&value, sizeof(T));
 		}
 
 		template<>

+ 29 - 25
Source/BansheeMono/BsMonoAssembly.cpp

@@ -51,7 +51,7 @@ namespace bs
 		unload();
 	}
 
-	void MonoAssembly::load(MonoDomain* domain)
+	void MonoAssembly::load()
 	{
 		if (mIsLoaded)
 			unload();
@@ -135,33 +135,35 @@ namespace bs
 		if(!mIsLoaded)
 			return;
 
-		if(mDebugData != nullptr)
-		{
-			mono_debug_close_image(mMonoImage);
-
-			bs_free(mDebugData);
-			mDebugData = nullptr;
-		}
-
 		for(auto& entry : mClassesByRaw)
 			bs_delete(entry.second);
 
 		mClasses.clear();
 		mClassesByRaw.clear();
 		mCachedClassList.clear();
+		mHaveCachedClassList = false;
 
-		if(mMonoImage != nullptr && !mIsDependency)
+		if(!mIsDependency)
 		{
-			// Make sure to close the image, otherwise when we try to re-load this assembly the Mono will return the cached
-			// image
-			mono_image_close(mMonoImage);
-			mMonoImage = nullptr;
-		}
+			if (mDebugData != nullptr)
+			{
+				mono_debug_close_image(mMonoImage);
 
-		mIsLoaded = false;
-		mIsDependency = false;
-		mMonoAssembly = nullptr;
-		mHaveCachedClassList = false;
+				bs_free(mDebugData);
+				mDebugData = nullptr;
+			}
+
+			if (mMonoImage != nullptr)
+			{
+				// Make sure to close the image, otherwise when we try to re-load this assembly the Mono will return the cached
+				// image
+				mono_image_close(mMonoImage);
+				mMonoImage = nullptr;
+			}
+
+			mMonoAssembly = nullptr;
+			mIsLoaded = false;
+		}
 	}
 
 	void MonoAssembly::invoke(const String& functionName)
@@ -221,15 +223,16 @@ namespace bs
 		String typeName;
 		MonoUtil::getClassName(rawMonoClass, ns, typeName);
 
+		// Verify the class is actually part of this assembly
+		MonoImage* classImage = mono_class_get_image(rawMonoClass);
+		if(classImage != mMonoImage)
+			return nullptr;
+
 		MonoClass* newClass = new (bs_alloc<MonoClass>()) MonoClass(ns, typeName, rawMonoClass, this);
-		
 		mClassesByRaw[rawMonoClass] = newClass;
 
-		if(!isGenericClass(typeName)) // No point in referencing generic types by name as all instances share it
-		{
-			MonoAssembly::ClassId classId(ns, typeName);
-			mClasses[classId] = newClass;
-		}
+		MonoAssembly::ClassId classId(ns, typeName);
+		mClasses[classId] = newClass;
 
 		return newClass;
 	}
@@ -247,6 +250,7 @@ namespace bs
 		if (iterFind != mClassesByRaw.end())
 			return iterFind->second;
 
+
 		MonoClass* newClass = new (bs_alloc<MonoClass>()) MonoClass(ns, typeName, rawMonoClass, this);
 
 		mClassesByRaw[rawMonoClass] = newClass;

+ 2 - 2
Source/BansheeMono/BsMonoAssembly.h

@@ -71,8 +71,8 @@ namespace bs
 	     */
 		MonoClass* getClass(const String& namespaceName, const String& name, ::MonoClass* rawMonoClass) const;
 
-		/**	Loads an assembly into the specified domain. */
-		void load(MonoDomain* domain);
+		/**	Loads an assembly into the current domain. */
+		void load();
 
 		/**
 		 * Initializes an assembly from an internal mono image.

+ 33 - 59
Source/BansheeMono/BsMonoManager.cpp

@@ -16,6 +16,7 @@
 #include <mono/metadata/mono-gc.h>
 #include <mono/metadata/mono-debug.h>
 #include <mono/utils/mono-logger.h>
+#include <mono/metadata/threads.h>
 
 namespace bs
 {
@@ -90,7 +91,7 @@ namespace bs
 	}
 
 	MonoManager::MonoManager()
-		:mScriptDomain(nullptr), mRootDomain(nullptr), mIsCoreLoaded(false)
+		:mScriptDomain(nullptr), mRootDomain(nullptr), mCorlibAssembly(nullptr)
 	{
 		Path libDir = Paths::findPath(MONO_LIB_DIR);
 		Path etcDir = getMonoEtcFolder();
@@ -100,13 +101,24 @@ namespace bs
 		mono_set_assemblies_path(assembliesDir.toString().c_str());
 
 #if BS_DEBUG_MODE
+		// Note: For proper debugging experience make sure to open a console window to display stdout and stderr, as Mono
+		// uses them for much of its logging.
 		mono_debug_init(MONO_DEBUG_FORMAT_MONO);
 
 		const char* options[] = {
 			"--soft-breakpoints",
-			"--debugger-agent=transport=dt_socket,address=127.0.0.1:17615,embedding=1,server=y,suspend=n"
+			"--debugger-agent=transport=dt_socket,address=127.0.0.1:17615,embedding=1,server=y,suspend=n",
+
+			// GC options:
+			// check-remset-consistency: Makes sure that write barriers are properly issued in native code, and therefore
+			//    all old->new generation references are properly present in the remset. This is easy to mess up in native
+			//    code by performing a simple memory copy without a barrier, so it's important to keep the option on.
+			// verify-before-collections: Unusure what exactly it does, but it sounds like it could help track down
+			//    things like accessing released/moved objects, or attempting to release handles for an unloaded domain.
+			// xdomain-checks: Makes sure that no references are left when a domain is unloaded.
+			"--gc-debug=check-remset-consistency,verify-before-collections,xdomain-checks"
 		};
-		mono_jit_parse_options(2, (char**)options);
+		mono_jit_parse_options(3, (char**)options);
 		mono_trace_set_level_string("warning"); // Note: Switch to "debug" for detailed output, disabled for now due to spam
 #else
 		mono_trace_set_level_string("warning");
@@ -121,6 +133,14 @@ namespace bs
 		mRootDomain = mono_jit_init_version("BansheeMono", MONO_VERSION_DATA[(int)MONO_VERSION].version.c_str());
 		if (mRootDomain == nullptr)
 			BS_EXCEPT(InternalErrorException, "Cannot initialize Mono runtime.");
+
+		mono_thread_set_main(mono_thread_current());
+
+		// Load corlib
+		mCorlibAssembly = new (bs_alloc<MonoAssembly>()) MonoAssembly(L"corlib", "corlib");
+		mCorlibAssembly->loadFromImage(mono_get_corlib());
+
+		mAssemblies["corlib"] = mCorlibAssembly;
 	}
 
 	MonoManager::~MonoManager()
@@ -151,19 +171,14 @@ namespace bs
 
 		if (mScriptDomain == nullptr)
 		{
-			String appDomainName = toString(path);
+			String appDomainName = "ScriptDomain";
 
 			mScriptDomain = mono_domain_create_appdomain(const_cast<char *>(appDomainName.c_str()), nullptr);
-			mono_domain_set(mScriptDomain, true);
-
 			if (mScriptDomain == nullptr)
-			{
 				BS_EXCEPT(InternalErrorException, "Cannot create script app domain.");
-			}
 
-#if BS_DEBUG_MODE
-			mono_debug_domain_create(mScriptDomain);
-#endif
+			if(!mono_domain_set(mScriptDomain, true))
+				BS_EXCEPT(InternalErrorException, "Cannot set script app domain.");
 		}
 
 		auto iterFind = mAssemblies.find(name);
@@ -186,7 +201,7 @@ namespace bs
 	{
 		if (!assembly.mIsLoaded)
 		{
-			assembly.load(mScriptDomain);
+			assembly.load();
 
 			// Fully initialize all types that use this assembly
 			Vector<ScriptMetaInfo>& typeMetas = getScriptMetaData()[assembly.mName];
@@ -210,24 +225,6 @@ namespace bs
 				meta->initCallback();
 			}
 		}
-
-		if (!mIsCoreLoaded)
-		{
-			mIsCoreLoaded = true;
-
-			MonoAssembly* corlib = nullptr;
-
-			auto iterFind = mAssemblies.find("corlib");
-			if (iterFind == mAssemblies.end())
-			{
-				corlib = new (bs_alloc<MonoAssembly>()) MonoAssembly(L"corlib", "corlib");
-				mAssemblies["corlib"] = corlib;
-			}
-			else
-				corlib = iterFind->second;
-
-			corlib->loadFromImage(mono_get_corlib());
-		}
 	}
 
 	MonoAssembly* MonoManager::getAssembly(const String& name) const
@@ -279,7 +276,6 @@ namespace bs
 			onDomainUnload();
 
 			mono_domain_set(mono_get_root_domain(), true);
-			mono_domain_finalize(mScriptDomain, 2000);
 
 			MonoObject* exception = nullptr;
 			mono_domain_try_unload(mScriptDomain, &exception);
@@ -287,8 +283,6 @@ namespace bs
 			if (exception != nullptr)
 				MonoUtil::throwIfException(exception);
 
-			mono_gc_collect(mono_gc_max_generation());
-
 			mScriptDomain = nullptr;
 		}
 
@@ -296,6 +290,11 @@ namespace bs
 		{
 			assemblyEntry.second->unload();
 
+			// "corlib" assembly persists domain unload since it's in the root domain. However we make sure to clear its
+			// class list as it could contain generic instances that use types from other assemblies.
+			if(assemblyEntry.first != "corlib")
+				bs_delete(assemblyEntry.second);
+
 			// Metas hold references to various assembly objects that were just deleted, so clear them
 			Vector<ScriptMetaInfo>& typeMetas = getScriptMetaData()[assemblyEntry.first];
 			for (auto& entry : typeMetas)
@@ -306,32 +305,7 @@ namespace bs
 		}
 
 		mAssemblies.clear();
-		mIsCoreLoaded = false;
-	}
-
-	void MonoManager::loadScriptDomain()
-	{
-		if (mScriptDomain != nullptr)
-			unloadScriptDomain();
-
-		if (mScriptDomain == nullptr)
-		{
-			char domainName[] = "ScriptDomain";
-
-			mScriptDomain = mono_domain_create_appdomain(domainName, nullptr);
-			mono_domain_set(mScriptDomain, false);
-
-			if (mScriptDomain == nullptr)
-			{
-				BS_EXCEPT(InternalErrorException, "Cannot create script app domain.");
-			}
-		}
-
-		if (mScriptDomain != nullptr)
-		{
-			for (auto& assemblyEntry : mAssemblies)
-				initializeAssembly(*assemblyEntry.second);
-		}
+		mAssemblies["corlib"] = mCorlibAssembly;
 	}
 
 	Path MonoManager::getFrameworkAssembliesFolder() const

+ 2 - 8
Source/BansheeMono/BsMonoManager.h

@@ -39,7 +39,7 @@ namespace bs
 		/**	Searches all loaded assemblies for the specified class. */
 		MonoClass* findClass(::MonoClass* rawMonoClass);
 
-		/**	Returns the current Mono domain. */
+		/**	Returns the main (scripting) Mono domain. */
 		MonoDomain* getDomain() const { return mScriptDomain; }
 
 		/**
@@ -53,12 +53,6 @@ namespace bs
 		 */
 		void unloadScriptDomain();
 
-		/**
-		 * Loads a new script domain. If another domain already exists it will be unloaded. This will also restore any
-		 * previously loaded assemblies.
-		 */
-		void loadScriptDomain();
-
 		/** Returns the absolute path of the folder where Mono framework assemblies are located. */
 		Path getFrameworkAssembliesFolder() const;
 
@@ -99,7 +93,7 @@ namespace bs
 		UnorderedMap<String, MonoAssembly*> mAssemblies;
 		MonoDomain* mScriptDomain;
 		MonoDomain* mRootDomain;
-		bool mIsCoreLoaded;
+		MonoAssembly* mCorlibAssembly;
 	};
 
 	/** @} */

+ 7 - 0
Source/BansheeMono/BsMonoUtil.cpp

@@ -135,6 +135,8 @@ namespace bs
 
 	void MonoUtil::freeGCHandle(UINT32 handle)
 	{
+		assert(handle != 0);
+
 		mono_gchandle_free(handle);
 	}
 
@@ -153,6 +155,11 @@ namespace bs
 		return mono_object_unbox(object);
 	}
 
+	void MonoUtil::valueCopy(void* dest, void* src, ::MonoClass* klass)
+	{
+		mono_value_copy(dest, src, klass);
+	}
+
 	bool MonoUtil::isSubClassOf(::MonoClass* subClass, ::MonoClass* parentClass)
 	{
 		return mono_class_is_subclass_of(subClass, parentClass, true) != 0;

+ 7 - 0
Source/BansheeMono/BsMonoUtil.h

@@ -81,6 +81,13 @@ namespace bs
 		/** Unboxes a managed object back to a raw value type. */
 		static void* unbox(MonoObject* object);
 
+		/** 
+		 * Copies the value from @p src to @p dest. This must be a value-type of type @p klass. You need to use this
+		 * form of copying if @p dest is a struct that gets passed to managed code and it contains a reference type. This
+		 * way the GC is informed about the reference in the struct. You can use normal copies otherwise.
+		 */
+		static void valueCopy(void* dest, void* src, ::MonoClass* klass);
+
 		/**	Checks if this class is a sub class of the specified class. */
 		static bool isSubClassOf(::MonoClass* subClass, ::MonoClass* parentClass);
 

+ 36 - 36
Source/BansheeUtility/Debug/BsDebug.cpp

@@ -98,84 +98,84 @@ namespace bs
 			R"(html {
   font-family: sans-serif;
 } 
-            
+			
 table
 {
-    border-collapse: collapse;
-    border-spacing: 0;
-    empty-cells: show;
-    border: 1px solid #cbcbcb;
-  	width:100%;
-  	table-layout:fixed;
+	border-collapse: collapse;
+	border-spacing: 0;
+	empty-cells: show;
+	border: 1px solid #cbcbcb;
+	width:100%;
+	table-layout:fixed;
 }
 
 table caption 
 {
-    color: #000;
-    font: italic 85%/1 arial, sans-serif;
-    padding: 1em 0;
-    text-align: center;
+	color: #000;
+	font: italic 85%/1 arial, sans-serif;
+	padding: 1em 0;
+	text-align: center;
 }
 
 table td,
 table th 
 {
-    border-left: 1px solid #cbcbcb;/*  inner column border */
-    border-width: 0 0 0 1px;
-    font-size: inherit;
-    margin: 0;
-    overflow: visible; /*to make ths where the title is really long work*/
-    padding: 0.5em 1em; /* cell padding */
+	border-left: 1px solid #cbcbcb;/*  inner column border */
+	border-width: 0 0 0 1px;
+	font-size: inherit;
+	margin: 0;
+	overflow: visible; /*to make ths where the title is really long work*/
+	padding: 0.5em 1em; /* cell padding */
 }
 
 table td:first-child,
 table th:first-child 
 {
-    border-left-width: 0;
+	border-left-width: 0;
 }
 
 table thead 
 {
-    background-color: #e0e0e0;
-    color: #000;
-    text-align: left;
-    vertical-align: bottom;
+	background-color: #e0e0e0;
+	color: #000;
+	text-align: left;
+	vertical-align: bottom;
 }
 
 table td 
 {
-    background-color: transparent;
-  	word-wrap:break-word;
-  	vertical-align: top;
-  	color: #7D7D7D;
+	background-color: transparent;
+	word-wrap:break-word;
+	vertical-align: top;
+	color: #7D7D7D;
 }
 
 .debug-row td {
-    background-color: #FFFFFF;
+	background-color: #FFFFFF;
 }
 
 .debug-alt-row td {
-    background-color: #f2f2f2;
+	background-color: #f2f2f2;
 }
 
 .warn-row td {
-    background-color: #ffc016;
-    color: #5F5F5F;
+	background-color: #ffc016;
+	color: #5F5F5F;
 }
 
 .warn-alt-row td {
-    background-color: #fdcb41;
-    color: #5F5F5F;
+	background-color: #fdcb41;
+	color: #5F5F5F;
 }
 
 .error-row td {
-    background-color: #9f1621;
-    color: #9F9F9F;
+	background-color: #9f1621;
+	color: #9F9F9F;
 }
 
 .error-alt-row td {
-    background-color: #ae1621;
-    color: #9F9F9F;
+	background-color: #ae1621;
+	color: #9F9F9F;
 }
 )";
 

+ 1 - 1
Source/CMakeLists.txt

@@ -5,7 +5,7 @@ project (Banshee)
 set (BS_VERSION_MAJOR 0)
 set (BS_VERSION_MINOR 4)
 
-set (BS_PREBUILT_DEPENDENCIES_VERSION 18)
+set (BS_PREBUILT_DEPENDENCIES_VERSION 19)
 set (BS_SRC_DEPENDENCIES_VERSION 15)
 set (BS_BUILTIN_ASSETS_VERSION 3)
 

+ 1 - 1
Source/MBansheeEditor/Utility/UndoRedo.cs

@@ -103,7 +103,7 @@ namespace BansheeEditor
         /// <summary>
         /// Removes a command with the specified identifier from undo/redo stack without executing it.
         /// </summary>
-        /// <param name="id">Identifier of the command as returned by <see cref="GetTopCommandId"/></param>
+        /// <param name="id">Identifier of the command as returned by <see cref="TopCommandId"/></param>
         public void PopCommand(int id)
         {
             Internal_PopCommand(mCachedPtr, id);

+ 34 - 34
Source/MBansheeEngine/Utility/PixelUtility.cs

@@ -124,7 +124,7 @@ namespace BansheeEngine
         /// <param name="options">Options controlling mip-map generation.</param>
         /// <returns>A list of calculated mip-map data. First entry is the largest mip and other follow in order from 
         ///          largest to smallest.</returns>
-		public static PixelData[] GenerateMipmaps(PixelData source, MipMapGenOptions options)
+        public static PixelData[] GenerateMipmaps(PixelData source, MipMapGenOptions options)
         {
             return Internal_GenerateMipmaps(source, ref options);
         }
@@ -204,109 +204,109 @@ namespace BansheeEngine
     /// Types of texture compression quality.
     /// </summary>
     public enum CompressionQuality // Note: Must match the C++ enum CompressionQuality
-	{
-		Fastest,
-		Normal,
-		Production,
-		Highest
-	};
+    {
+        Fastest,
+        Normal,
+        Production,
+        Highest
+    };
 
     /// <summary>
     /// Mode of the alpha channel in a texture.
     /// </summary>
     public enum AlphaMode // Note: Must match the C++ enum AlphaMode
-	{
+    {
         /// <summary>
         /// Texture has no alpha values.
         /// </summary>
-		None,
+        None,
         /// <summary>
         /// Alpha is in the separate transparency channel.
         /// </summary>
-		Transparency,
+        Transparency,
         /// <summary>
         /// Alpha values have been pre-multiplied with the color values.
         /// </summary>
-		Premultiplied
-	};
+        Premultiplied
+    };
 
     /// <summary>
     /// Wrap mode to use when generating mip maps.
     /// </summary>
     public enum MipMapWrapMode // Note: Must match the C++ enum MipMapWrapMode
-	{
-		Mirror,
-		Repeat,
-		Clamp
-	};
+    {
+        Mirror,
+        Repeat,
+        Clamp
+    };
 
     /// <summary>
     /// Filter to use when generating mip maps.
     /// </summary>
     public enum MipMapFilter // Note: Must match the C++ enum MipMapFilter
-	{
-		Box,
-		Triangle,
-		Kaiser
-	};
+    {
+        Box,
+        Triangle,
+        Kaiser
+    };
 
     /// <summary>
     /// Options used to control texture compression.
     /// </summary>
     [StructLayout(LayoutKind.Sequential)]
     public struct CompressionOptions // Note: Must match the C++ struct CompressionOptions
-	{
+    {
         /// <summary>
         /// Format to compress to. Must be a format containing compressed data.
         /// </summary>
-		public PixelFormat format;
+        public PixelFormat format;
 
         /// <summary>
         /// Controls how to (and if) to compress the alpha channel.
         /// </summary>
-	    public AlphaMode alphaMode;
+        public AlphaMode alphaMode;
 
         /// <summary>
         /// Determines does the input data represent a normal map.
         /// </summary>
-		public bool isNormalMap;
+        public bool isNormalMap;
 
         /// <summary>
         /// Determines has the input data been gamma corrected.
         /// </summary>
-		public bool isSRGB;
+        public bool isSRGB;
 
         /// <summary>
         /// Compressed image quality. Better compression might take longer to execute but will generate better results.
         /// </summary>
-		public CompressionQuality quality;
-	};
+        public CompressionQuality quality;
+    };
 
     /// <summary>
     /// Options used to control texture mip map generation.
     /// </summary>
     [StructLayout(LayoutKind.Sequential)]
     public struct MipMapGenOptions // Note: Must match the C++ struct MipMapGenOptions
-	{
+    {
         /// <summary>
         /// Filter to use when downsamping input data.
         /// </summary>
-		public MipMapFilter filter;
+        public MipMapFilter filter;
 
         /// <summary>
         /// Determines how to downsample pixels on borders.
         /// </summary>
-		public MipMapWrapMode wrapMode;
+        public MipMapWrapMode wrapMode;
 
         /// <summary>
         /// Determines does the input data represent a normal map.
         /// </summary>
-		public bool isNormalMap;
+        public bool isNormalMap;
 
         /// <summary>
         /// Should the downsampled values be re-normalized. Only relevant for mip-maps representing normal maps.
         /// </summary>
-		public bool normalizeMipmaps;
+        public bool normalizeMipmaps;
 
         /// <summary>
         /// Determines has the input data been gamma corrected.

+ 112 - 14
Source/SBansheeEditor/BsManagedEditorCommand.cpp

@@ -7,6 +7,9 @@
 #include "BsMonoMethod.h"
 #include "BsMonoManager.h"
 #include "BsMonoUtil.h"
+#include "Serialization/BsScriptAssemblyManager.h"
+#include "Serialization/BsMemorySerializer.h"
+#include "Serialization/BsManagedSerializableObject.h"
 
 namespace bs
 {
@@ -14,12 +17,20 @@ namespace bs
 	MonoMethod* ScriptCmdManaged::sRevertMethod = nullptr;
 
 	ScriptCmdManaged::ScriptCmdManaged(MonoObject* managedInstance)
-		:ScriptObject(managedInstance), mManagedCommand(nullptr)
+		:ScriptObject(managedInstance)
 	{
+		MonoUtil::getClassName(managedInstance, mNamespace, mType);
+
+		if(!ScriptAssemblyManager::instance().hasSerializableObjectInfo(mNamespace, mType))
+		{
+			LOGWRN("UndoableCommand created without [SerializeObject] attribute. The command will not be able to \
+				persist assembly refresh.");
+		}
+
 		mManagedCommand = bs_shared_ptr(new (bs_alloc<CmdManaged>()) CmdManaged(this));
 
 		mGCHandle = MonoUtil::newWeakGCHandle(managedInstance);
-		mWeakHandle = true;
+		mInUndoRedoStack = false;
 	}
 
 	ScriptCmdManaged::~ScriptCmdManaged()
@@ -67,31 +78,118 @@ namespace bs
 		mManagedCommand = nullptr;
 	}
 
-	void ScriptCmdManaged::allocGCHandle()
+	void ScriptCmdManaged::notifyStackAdded()
 	{
-		if (mWeakHandle)
+		if (!mInUndoRedoStack)
 		{
 			MonoObject* obj = MonoUtil::getObjectFromGCHandle(mGCHandle);
 			mGCHandle = MonoUtil::newGCHandle(obj);
-			mWeakHandle = false;
+			mInUndoRedoStack = true;
 		}
 	}
 
-	void ScriptCmdManaged::freeGCHandle()
+	void ScriptCmdManaged::notifyStackRemoved()
 	{
 		MonoObject* obj = nullptr;
 		if (mGCHandle)
 			obj = MonoUtil::getObjectFromGCHandle(mGCHandle);
 
-		if (!mWeakHandle && mGCHandle != 0)
+		if (mInUndoRedoStack && mGCHandle != 0)
+		{
 			MonoUtil::freeGCHandle(mGCHandle);
+			mGCHandle = 0;
+		}
 
 		// Note: Re-creating the weak handle might not be necessary as the command shouldn't be allowed to be re-added
 		// after it has been removed from the undo-redo stack (which should be the only place this method is called from).
 		if(obj)
 			mGCHandle = MonoUtil::newWeakGCHandle(obj);
 
-		mWeakHandle = true;
+		mInUndoRedoStack = false;
+	}
+
+	MonoObject* ScriptCmdManaged::_createManagedInstance(bool construct)
+	{
+		SPtr<ManagedSerializableObjectInfo> currentObjInfo = nullptr;
+
+		// If not in undo-redo stack then this object should have been deleted
+		assert(mInUndoRedoStack);
+
+		// See if this type even still exists
+		if (!ScriptAssemblyManager::instance().getSerializableObjectInfo(mNamespace, mType, currentObjInfo))
+		{
+			mTypeMissing = true;
+			return nullptr;
+		}
+
+		MonoObject* instance = currentObjInfo->mMonoClass->createInstance(construct);
+		mGCHandle = MonoUtil::newGCHandle(instance);
+
+		mTypeMissing = false;
+		return instance;
+	}
+
+	void ScriptCmdManaged::_clearManagedInstance()
+	{
+		if(mInUndoRedoStack)
+		{
+			MonoUtil::freeGCHandle(mGCHandle);
+			mGCHandle = 0;
+		}
+	}
+
+	ScriptObjectBackup ScriptCmdManaged::beginRefresh()
+	{
+		RawBackupData backupData;
+
+		if(mInUndoRedoStack)
+		{
+			// If type is not missing, restore saved data, otherwise keep the data for later
+			SPtr<ManagedSerializableObject> serializableObject;
+			if(!mTypeMissing)
+			{
+				MonoObject* instance = MonoUtil::getObjectFromGCHandle(mGCHandle);
+				serializableObject = ManagedSerializableObject::createFromExisting(instance);
+			}
+			else
+				serializableObject = mSerializedObjectData;
+
+			if (serializableObject != nullptr)
+			{
+				MemorySerializer ms;
+				backupData.data = ms.encode(serializableObject.get(), backupData.size);
+			}
+		}
+
+		return ScriptObjectBackup(backupData);
+	}
+
+	void ScriptCmdManaged::endRefresh(const ScriptObjectBackup& backupData)
+	{
+		const RawBackupData& data = any_cast_ref<RawBackupData>(backupData.data);
+
+		MemorySerializer ms;
+		SPtr<ManagedSerializableObject> serializableObject = std::static_pointer_cast<ManagedSerializableObject>(
+			ms.decode(data.data, data.size));
+
+		if(!mTypeMissing)
+		{
+			SPtr<ManagedSerializableObjectInfo> objInfo;
+			ScriptAssemblyManager::instance().getSerializableObjectInfo(mNamespace, mType, objInfo);
+
+			MonoObject* instance = MonoUtil::getObjectFromGCHandle(mGCHandle);
+			serializableObject->deserialize(instance, objInfo);
+
+			mSerializedObjectData = nullptr;
+		}
+		else
+			mSerializedObjectData = serializableObject;
+	}
+
+	void ScriptCmdManaged::_onManagedInstanceDeleted(bool assemblyRefresh)
+	{
+		if(!mInUndoRedoStack || !assemblyRefresh)
+			ScriptObjectBase::_onManagedInstanceDeleted(assemblyRefresh);
 	}
 
 	CmdManaged::CmdManaged(ScriptCmdManaged* scriptObj)
@@ -112,8 +210,8 @@ namespace bs
 		{
 			LOGWRN("Trying to execute a managed undo/redo command whose managed object has been destroyed, ignoring.");
 			// Note: This can most likely happen if managed undo commands are queued on a global undo/redo stack. When
-			// assembly refresh happens those commands will be destroyed but not removed from the stack. To fix the issue
-			// implement assembly refresh handling for such commands.
+			// assembly refresh happens those commands will be rebuilt, but this can fail if the user removed the command
+			// type from the assembly, or the command isn't serializable.
 
 			return;
 		}
@@ -127,8 +225,8 @@ namespace bs
 		{
 			LOGWRN("Trying to execute a managed undo/redo command whose managed object has been destroyed, ignoring.");
 			// Note: This can most likely happen if managed undo commands are queued on a global undo/redo stack. When
-			// assembly refresh happens those commands will be destroyed but not removed from the stack. To fix the issue
-			// implement assembly refresh handling for such commands.
+			// assembly refresh happens those commands will be rebuilt, but this can fail if the user removed the command
+			// type from the assembly, or the command isn't serializable.
 
 			return;
 		}
@@ -139,7 +237,7 @@ namespace bs
 	void CmdManaged::onCommandAdded()
 	{
 		if(mScriptObj)
-			mScriptObj->allocGCHandle();
+			mScriptObj->notifyStackAdded();
 
 		mRefCount++;
 	}
@@ -151,7 +249,7 @@ namespace bs
 		mRefCount--;
 
 		if (mRefCount == 0)
-			mScriptObj->freeGCHandle();
+			mScriptObj->notifyStackRemoved();
 	}
 
 	void CmdManaged::notifyScriptInstanceDestroyed()

+ 26 - 4
Source/SBansheeEditor/BsManagedEditorCommand.h

@@ -15,7 +15,7 @@ namespace bs
 	 */
 
 	/**	Interop class between C++ & CLR for CmdManaged. */
-	class BS_SCR_BED_EXPORT ScriptCmdManaged : public ScriptObject <ScriptCmdManaged>
+	class BS_SCR_BED_EXPORT ScriptCmdManaged : public ScriptObject <ScriptCmdManaged, PersistentScriptObjectBase>
 	{
 	public:
 		SCRIPT_OBJ(EDITOR_ASSEMBLY, "BansheeEditor", "UndoableCommand")
@@ -40,17 +40,39 @@ namespace bs
 		 * Allocates a GC handle that ensures the object doesn't get GC collected. Must eventually be followed by
 		 * freeGCHandle().
 		 */
-		void allocGCHandle();
+		void notifyStackAdded();
 
 		/** Frees a GC handle previously allocated from allocGCHandle(). */
-		void freeGCHandle();
+		void notifyStackRemoved();
 
 		/** Notifies the script instance that the underlying managed command was destroyed. */
 		void notifyCommandDestroyed();
 
+		/** @copydoc ScriptObjectBase::beginRefresh */
+		ScriptObjectBackup beginRefresh() override;
+
+		/** @copydoc ScriptObjectBase::endRefresh */
+		void endRefresh(const ScriptObjectBackup& backupData) override;
+
+		/** @copydoc ScriptObjectBase::_createManagedInstance */
+		MonoObject* _createManagedInstance(bool construct) override;
+
+		/** @copydoc ScriptObjectBase::_clearManagedInstance */
+		void _clearManagedInstance() override;
+
+		/** @copydoc ScriptObjectBase::_onManagedInstanceDeleted */
+		void _onManagedInstanceDeleted(bool assemblyRefresh) override;
+
 		SPtr<CmdManaged> mManagedCommand;
+
 		UINT32 mGCHandle = 0;
-		bool mWeakHandle = true;
+		bool mInUndoRedoStack = false;
+
+		String mType;
+		String mNamespace;
+
+		bool mTypeMissing = false;
+		SPtr<ManagedSerializableObject> mSerializedObjectData;
 
 		/************************************************************************/
 		/* 								CLR HOOKS						   		*/

+ 10 - 10
Source/SBansheeEditor/Wrappers/BsScriptEditorWindow.cpp

@@ -32,7 +32,7 @@ namespace bs
 
 	ScriptEditorWindow::ScriptEditorWindow(ScriptEditorWidget* editorWidget)
 		: ScriptObject(editorWidget->getManagedInstance()), mName(editorWidget->getName())
-		, mEditorWidget(editorWidget), mRefreshInProgress(false), mIsDestroyed(false)
+		, mEditorWidget(editorWidget), mIsDestroyed(false)
 	{
 		mOnWidgetResizedConn = editorWidget->onResized.connect(std::bind(&ScriptEditorWindow::onWidgetResized, this, _1, _2));
 		mOnFocusChangedConn = editorWidget->onFocusChanged.connect(std::bind(&ScriptEditorWindow::onFocusChanged, this, _1));
@@ -123,10 +123,10 @@ namespace bs
 		return mEditorWidget; 
 	}
 
-	void ScriptEditorWindow::_onManagedInstanceDeleted()
+	void ScriptEditorWindow::_onManagedInstanceDeleted(bool assemblyRefresh)
 	{
-		if (!mRefreshInProgress)
-			ScriptObject::_onManagedInstanceDeleted();
+		if (!assemblyRefresh)
+			ScriptObject::_onManagedInstanceDeleted(assemblyRefresh);
 		else
 		{
 			if(mEditorWidget)
@@ -136,8 +136,6 @@ namespace bs
 
 	ScriptObjectBackup ScriptEditorWindow::beginRefresh()
 	{
-		mRefreshInProgress = true;
-
 		auto iterFind = OpenScriptEditorWindows.find(mName);
 		if (iterFind != OpenScriptEditorWindows.end())
 			iterFind->second->mEditorWidget->clearManagedInstance(true);
@@ -147,12 +145,10 @@ namespace bs
 
 	void ScriptEditorWindow::endRefresh(const ScriptObjectBackup& backupData)
 	{
-		mRefreshInProgress = false;
-
 		if (isDestroyed())
 		{
 			// We couldn't restore managed instance because window class cannot be found
-			_onManagedInstanceDeleted();
+			_onManagedInstanceDeleted(false);
 		}
 
 		PersistentScriptObjectBase::endRefresh(backupData);
@@ -430,7 +426,7 @@ namespace bs
 
 	void ScriptEditorWidget::clearManagedInstance(bool freeHandle)
 	{
-		if(freeHandle)
+		if(freeHandle && mGCHandle != 0)
 			MonoUtil::freeGCHandle(mGCHandle);
 
 		mManagedInstance = nullptr;
@@ -455,6 +451,8 @@ namespace bs
 					mManagedInstance = editorWindowClass->createInstance();
 					mGCHandle = MonoUtil::newGCHandle(mManagedInstance);
 
+					assert(mManagedInstance == MonoUtil::getObjectFromGCHandle(mGCHandle));
+
 					MonoObject* guiPanel = ScriptGUIPanel::createFromExisting(mContent);
 					mContentsPanel = ScriptGUILayout::toNative(guiPanel);
 					ScriptEditorWindow::guiPanelField->set(mManagedInstance, guiPanel);
@@ -505,6 +503,8 @@ namespace bs
 
 		if (mUpdateThunk != nullptr && mManagedInstance != nullptr)
 		{
+			assert(mManagedInstance == MonoUtil::getObjectFromGCHandle(mGCHandle));
+
 			// Note: Not calling virtual methods. Can be easily done if needed but for now doing this
 			// for some extra speed.
 			MonoUtil::invokeThunk(mUpdateThunk, mManagedInstance);

+ 1 - 2
Source/SBansheeEditor/Wrappers/BsScriptEditorWindow.h

@@ -55,7 +55,7 @@ namespace bs
 		void onAssemblyRefreshStarted();
 
 		/** @copydoc ScriptObjectBase::_onManagedInstanceDeleted */
-		void _onManagedInstanceDeleted() override;
+		void _onManagedInstanceDeleted(bool assemblyRefresh) override;
 
 		/** @copydoc ScriptObjectBase::beginRefresh */
 		ScriptObjectBackup beginRefresh() override;
@@ -71,7 +71,6 @@ namespace bs
 		HEvent mOnWidgetResizedConn;
 		HEvent mOnFocusChangedConn;
 		HEvent mOnAssemblyRefreshStartedConn;
-		bool mRefreshInProgress;
 		bool mIsDestroyed;
 
 		static MonoMethod* onResizedMethod;

+ 4 - 4
Source/SBansheeEditor/Wrappers/GUI/BsScriptGUIEnumField.cpp

@@ -80,27 +80,27 @@ namespace bs
 			case sizeof(UINT8):
 			{
 				UINT8 value = 0;
-				memcpy(&value, valuesArr.getRawPtr(elemSize, i), elemSize);
+				memcpy(&value, valuesArr.getRaw(i, elemSize), elemSize);
 				nativeValue = (UINT64)value;
 				break;
 			}
 			case sizeof(UINT16) :
 			{
 				UINT16 value = 0;
-				memcpy(&value, valuesArr.getRawPtr(elemSize, i), elemSize);
+				memcpy(&value, valuesArr.getRaw(i, elemSize), elemSize);
 				nativeValue = (UINT64)value;
 				break;
 			}
 			case sizeof(UINT32):
 			{
 				UINT32 value = 0;
-				memcpy(&value, valuesArr.getRawPtr(elemSize, i), elemSize);
+				memcpy(&value, valuesArr.getRaw(i, elemSize), elemSize);
 				nativeValue = (UINT64)value;
 				break;
 			}
 			case sizeof(UINT64) :
 			{
-				memcpy(&nativeValue, valuesArr.getRawPtr(elemSize, i), elemSize);
+				memcpy(&nativeValue, valuesArr.getRaw(i, elemSize), elemSize);
 				break;
 			}
 			}

+ 1 - 1
Source/SBansheeEngine/BsScriptObject.cpp

@@ -28,7 +28,7 @@ namespace bs
 
 	}
 
-	void ScriptObjectBase::_onManagedInstanceDeleted()
+	void ScriptObjectBase::_onManagedInstanceDeleted(bool assemblyRefresh)
 	{
 		bs_delete(this);
 	}

+ 17 - 4
Source/SBansheeEngine/BsScriptObject.h

@@ -18,9 +18,22 @@ namespace bs
 	/**	Contains backed up interop object data. */
 	struct ScriptObjectBackup
 	{
+		ScriptObjectBackup() {}
+
+		explicit ScriptObjectBackup(const Any& data)
+			:data(data)
+		{ }
+
 		Any data;
 	};
 
+	/** Contains backup data in the form of a raw memory buffer. */
+	struct RawBackupData
+	{
+		UINT8* data = nullptr;
+		UINT32 size = 0;
+	};
+
 	/**
 	 * Base class for all script interop objects. Interop objects form a connection between C++ and CLR classes and methods.
 	 */
@@ -36,9 +49,9 @@ namespace bs
 		 */
 		virtual bool isPersistent() const { return false; }
 
-		/**	
-		 * Clears any managed instance references from the interop object. Normally called right after the assemblies are
-		 * unloaded.
+		/** 
+		 * Clears any managed instance references from the interop object, and releases any GC handles. Called during
+		 * assembly refresh just before the script domain is unloaded. 
 		 */
 		virtual void _clearManagedInstance() { }
 
@@ -46,7 +59,7 @@ namespace bs
 		virtual void _restoreManagedInstance() { }
 
 		/**	Called when the managed instance gets finalized by the CLR. */
-		virtual void _onManagedInstanceDeleted();
+		virtual void _onManagedInstanceDeleted(bool assemblyRefresh);
 
 		/**	Called before assembly reload starts to give the object a chance to back up its data. */
 		virtual ScriptObjectBackup beginRefresh();

+ 7 - 6
Source/SBansheeEngine/BsScriptObjectManager.cpp

@@ -42,17 +42,18 @@ namespace bs
 		for (auto& scriptObject : mScriptObjects)
 			backupData[scriptObject] = scriptObject->beginRefresh();
 
+		for (auto& scriptObject : mScriptObjects)
+			scriptObject->_clearManagedInstance();
+
 		MonoManager::instance().unloadScriptDomain();
+
 		// Unload script domain should trigger finalizers on everything, but since we usually delay
 		// their processing we need to manually trigger it here.
-		processFinalizedObjects();
+		processFinalizedObjects(true);
 
 		for (auto& scriptObject : mScriptObjects)
 			assert(scriptObject->isPersistent() && "Non-persistent ScriptObject alive after domain unload.");
 
-		for (auto& scriptObject : mScriptObjects)
-			scriptObject->_clearManagedInstance();
-
 		ScriptAssemblyManager::instance().clearAssemblyInfo();
 
 		for (auto& assemblyPair : assemblies)
@@ -90,7 +91,7 @@ namespace bs
 		processFinalizedObjects();
 	}
 
-	void ScriptObjectManager::processFinalizedObjects()
+	void ScriptObjectManager::processFinalizedObjects(bool assemblyRefresh)
 	{
 		UINT32 readQueueIdx = 0;
 		{
@@ -100,7 +101,7 @@ namespace bs
 		}
 		
 		for (auto& finalizedObj : mFinalizedObjects[readQueueIdx])
-			finalizedObj->_onManagedInstanceDeleted();
+			finalizedObj->_onManagedInstanceDeleted(assemblyRefresh);
 
 		mFinalizedObjects[readQueueIdx].clear();
 	}

+ 3 - 2
Source/SBansheeEngine/BsScriptObjectManager.h

@@ -46,9 +46,10 @@ namespace bs
 
 		/**
 		 * Triggers _onManagedInstanceDeleted deleted callbacks on all objects that were finalized this frame. This allows
-		 * the native portions of those objects to properly clean up any resources.
+		 * the native portions of those objects to properly clean up any resources. @p assemblyRefresh lets the
+		 * script objects know if finalization is happening due to assembly refresh.
 		 */
-		void processFinalizedObjects();
+		void processFinalizedObjects(bool assemblyRefresh = false);
 
 		/**
 		 * Triggered right after a domain was reloaded. This signals the outside world that they should update any kept Mono

+ 2 - 3
Source/SBansheeEngine/Serialization/BsManagedSerializableArray.cpp

@@ -98,7 +98,7 @@ namespace bs
 			UINT32 numElems = scriptArray.size();
 			assert(arrayIdx < numElems);
 
-			void* arrayValue = scriptArray.getRawPtr(mElemSize, arrayIdx);
+			void* arrayValue = scriptArray.getRaw(arrayIdx, mElemSize);
 
 			if (MonoUtil::isValueType(mElementMonoClass))
 			{
@@ -175,8 +175,7 @@ namespace bs
 		UINT32 numElems = (UINT32)scriptArray.size();
 		assert(arrayIdx < numElems);
 	
-		void* elemAddr = scriptArray.getRawPtr(mElemSize, arrayIdx);
-		memcpy(elemAddr, val, mElemSize);
+		scriptArray.setRaw(arrayIdx, (UINT8*)val, mElemSize);
 	}
 
 	void ManagedSerializableArray::initMonoObjects()

+ 2 - 2
Source/SBansheeEngine/Wrappers/BsScriptComponent.cpp

@@ -22,13 +22,13 @@ namespace bs
 		:ScriptGameObjectBase(instance)
 	{ }
 
-	void ScriptComponentBase::destroy()
+	void ScriptComponentBase::destroy(bool assemblyRefresh)
 	{
 		// It's possible that managed component is destroyed but a reference to it is still kept during assembly refresh. 
 		// Such components shouldn't be restored so we delete them.
 
 		HComponent component = getComponent();
-		if (!mRefreshInProgress || component.isDestroyed(true))
+		if (!assemblyRefresh || component.isDestroyed(true))
 			ScriptGameObjectManager::instance().destroyScriptComponent(this);
 	}
 

+ 4 - 4
Source/SBansheeEngine/Wrappers/BsScriptComponent.h

@@ -27,7 +27,7 @@ namespace bs
 		friend class ScriptGameObjectManager;
 
 		/** Destroys the interop object, unless refresh is in progress in which case it is just prepared for re-creation. */
-		void destroy();
+		void destroy(bool assemblyRefresh);
 
 		/**	Triggered by the script game object manager when the handle this object is referencing is destroyed. */
 		virtual void _notifyDestroyed() { }
@@ -73,7 +73,7 @@ namespace bs
 		/** @copydoc ScriptObjectBase::_clearManagedInstance */
 		void _clearManagedInstance() override
 		{
-			this->mGCHandle = 0;
+			this->freeManagedInstance();
 		}
 
 		/**
@@ -86,11 +86,11 @@ namespace bs
 		}
 
 		/**	Called when the managed instance gets finalized by the CLR. */
-		void _onManagedInstanceDeleted() override
+		void _onManagedInstanceDeleted(bool assemblyRefresh) override
 		{
 			this->freeManagedInstance();
 
-			this->destroy();
+			this->destroy(assemblyRefresh);
 		}
 
 		GameObjectHandle<CompType> mComponent;

+ 1 - 15
Source/SBansheeEngine/Wrappers/BsScriptGameObject.cpp

@@ -7,7 +7,7 @@
 namespace bs
 {
 	ScriptGameObjectBase::ScriptGameObjectBase(MonoObject* instance)
-		:PersistentScriptObjectBase(instance), mRefreshInProgress(false), mGCHandle(0)
+		:PersistentScriptObjectBase(instance), mGCHandle(0)
 	{
 		
 	}
@@ -17,20 +17,6 @@ namespace bs
 		BS_ASSERT(mGCHandle == 0 && "Object being destroyed without its managed instance being freed first.");
 	}
 	
-	ScriptObjectBackup ScriptGameObjectBase::beginRefresh()
-	{
-		mRefreshInProgress = true;
-
-		return PersistentScriptObjectBase::beginRefresh();
-	}
-
-	void ScriptGameObjectBase::endRefresh(const ScriptObjectBackup& backupData)
-	{
-		mRefreshInProgress = false;
-
-		PersistentScriptObjectBase::endRefresh(backupData);
-	}
-
 	MonoObject* ScriptGameObjectBase::getManagedInstance() const
 	{
 		return MonoUtil::getObjectFromGCHandle(mGCHandle);

+ 0 - 6
Source/SBansheeEngine/Wrappers/BsScriptGameObject.h

@@ -27,11 +27,6 @@ namespace bs
 		/** Returns the managed version of this game object. */
 		MonoObject* getManagedInstance() const;
 
-		/** @copydoc ScriptObjectBase::beginRefresh */
-		ScriptObjectBackup beginRefresh() override;
-
-		/** @copydoc ScriptObjectBase::endRefresh */
-		void endRefresh(const ScriptObjectBackup& backupData) override;
 	protected:
 		/** 
 		 * Makes the object reference the specific managed instance. Internally this allocates a GC handle that keeps a
@@ -48,7 +43,6 @@ namespace bs
 		 */
 		void freeManagedInstance();
 
-		bool mRefreshInProgress;
 		UINT32 mGCHandle;
 	};
 

+ 2 - 2
Source/SBansheeEngine/Wrappers/BsScriptInputConfiguration.cpp

@@ -108,12 +108,12 @@ namespace bs
 		return thisPtr->getInternalValue()->getRepeatInterval();
 	}
 
-	void ScriptInputConfiguration::_onManagedInstanceDeleted()
+	void ScriptInputConfiguration::_onManagedInstanceDeleted(bool assemblyRefresh)
 	{
 		UINT64 configId = (UINT64)mInputConfig.get();
 		ScriptInputConfigurations.erase(configId);
 
-		ScriptObject::_onManagedInstanceDeleted();
+		ScriptObject::_onManagedInstanceDeleted(assemblyRefresh);
 	}
 
 	ScriptVirtualAxis::ScriptVirtualAxis(MonoObject* instance)

+ 1 - 1
Source/SBansheeEngine/Wrappers/BsScriptInputConfiguration.h

@@ -40,7 +40,7 @@ namespace bs
 		ScriptInputConfiguration(MonoObject* instance, const SPtr<InputConfiguration>& inputConfig);
 
 		/** @copydoc ScriptObjectBase::_onManagedInstanceDeleted */
-		void _onManagedInstanceDeleted() override;
+		void _onManagedInstanceDeleted(bool assemblyRefresh) override;
 
 		SPtr<InputConfiguration> mInputConfig;
 		UINT32 mGCHandle = 0;

+ 2 - 6
Source/SBansheeEngine/Wrappers/BsScriptManagedComponent.cpp

@@ -96,8 +96,6 @@ namespace bs
 
 	ScriptObjectBackup ScriptManagedComponent::beginRefresh()
 	{
-		ScriptGameObjectBase::beginRefresh();
-
 		HManagedComponent managedComponent = static_object_cast<ManagedComponent>(mComponent);
 		ScriptObjectBackup backupData;
 
@@ -117,18 +115,16 @@ namespace bs
 
 		MonoObject* instance = MonoUtil::getObjectFromGCHandle(mGCHandle);
 		managedComponent->restore(instance, componentBackup, mTypeMissing);
-
-		ScriptGameObjectBase::endRefresh(backupData);
 	}
 
-	void ScriptManagedComponent::_onManagedInstanceDeleted()
+	void ScriptManagedComponent::_onManagedInstanceDeleted(bool assemblyRefresh)
 	{
 		mGCHandle = 0;
 
 		// It's possible that managed component is destroyed but a reference to it
 		// is still kept during assembly refresh. Such components shouldn't be restored
 		// so we delete them.
-		if (!mRefreshInProgress || mComponent.isDestroyed(true))
+		if (!assemblyRefresh || mComponent.isDestroyed(true))
 			ScriptGameObjectManager::instance().destroyScriptComponent(this);
 	}
 }

+ 1 - 1
Source/SBansheeEngine/Wrappers/BsScriptManagedComponent.h

@@ -45,7 +45,7 @@ namespace bs
 		void _clearManagedInstance() override;
 
 		/** @copydoc ScriptObjectBase::_onManagedInstanceDeleted */
-		void _onManagedInstanceDeleted() override;
+		void _onManagedInstanceDeleted(bool assemblyRefresh) override;
 
 		HManagedComponent mComponent;
 		String mNamespace;

+ 3 - 7
Source/SBansheeEngine/Wrappers/BsScriptManagedResource.cpp

@@ -54,8 +54,6 @@ namespace bs
 
 	ScriptObjectBackup ScriptManagedResource::beginRefresh()
 	{
-		ScriptResourceBase::beginRefresh();
-
 		ScriptObjectBackup backupData;
 		backupData.data = mResource->backup(true);
 
@@ -71,16 +69,14 @@ namespace bs
 
 		// If we could not find resource type after refresh, treat it as if it was destroyed
 		if (instance == nullptr)
-			_onManagedInstanceDeleted();
-
-		ScriptResourceBase::endRefresh(backupData);
+			_onManagedInstanceDeleted(false);
 	}
 
-	void ScriptManagedResource::_onManagedInstanceDeleted()
+	void ScriptManagedResource::_onManagedInstanceDeleted(bool assemblyRefresh)
 	{
 		mGCHandle = 0;
 		
-		if (!mRefreshInProgress)
+		if (!assemblyRefresh)
 		{
 			// The only way this method should be reachable is when Resource::unload is called, which means the resource
 			// has had to been already freed. Even if all managed instances are released ManagedResource itself holds the last

+ 1 - 1
Source/SBansheeEngine/Wrappers/BsScriptManagedResource.h

@@ -44,7 +44,7 @@ namespace bs
 		void _clearManagedInstance() override;
 
 		/** @copydoc ScriptObjectBase::_onManagedInstanceDeleted */
-		void _onManagedInstanceDeleted() override;
+		void _onManagedInstanceDeleted(bool assemblyRefresh) override;
 
 		HManagedResource mResource;
 		String mNamespace;

+ 2 - 17
Source/SBansheeEngine/Wrappers/BsScriptResource.cpp

@@ -9,7 +9,7 @@
 namespace bs
 {
 	ScriptResourceBase::ScriptResourceBase(MonoObject* instance)
-		:PersistentScriptObjectBase(instance), mRefreshInProgress(false), mGCHandle(0)
+		:PersistentScriptObjectBase(instance), mGCHandle(0)
 	{ }
 
 	ScriptResourceBase::~ScriptResourceBase()
@@ -17,20 +17,6 @@ namespace bs
 		BS_ASSERT(mGCHandle == 0 && "Object being destroyed without its managed instance being freed first.");
 	}
 
-	ScriptObjectBackup ScriptResourceBase::beginRefresh()
-	{
-		mRefreshInProgress = true;
-
-		return PersistentScriptObjectBase::beginRefresh();
-	}
-
-	void ScriptResourceBase::endRefresh(const ScriptObjectBackup& backupData)
-	{
-		mRefreshInProgress = false;
-
-		PersistentScriptObjectBase::endRefresh(backupData);
-	}
-
 	MonoObject* ScriptResourceBase::getManagedInstance() const
 	{
 		return MonoUtil::getObjectFromGCHandle(mGCHandle);
@@ -54,8 +40,7 @@ namespace bs
 
 	void ScriptResourceBase::destroy()
 	{
-		if (!mRefreshInProgress)
-			ScriptResourceManager::instance().destroyScriptResource(this);
+		ScriptResourceManager::instance().destroyScriptResource(this);
 	}
 
 	void ScriptResource::initRuntimeData()

+ 4 - 10
Source/SBansheeEngine/Wrappers/BsScriptResource.h

@@ -25,12 +25,6 @@ namespace bs
 		/** Returns the managed version of this resource. */
 		MonoObject* getManagedInstance() const;
 
-		/** @copydoc ScriptObjectBase::beginRefresh */
-		ScriptObjectBackup beginRefresh() override;
-
-		/** @copydoc ScriptObjectBase::endRefresh */
-		void endRefresh(const ScriptObjectBackup& backupData) override;
-
 	protected:
 		friend class ScriptResourceManager;
 
@@ -60,7 +54,6 @@ namespace bs
 		/** Destroys the interop object, unless refresh is in progress in which case it is just prepared for re-creation. */
 		void destroy();
 
-		bool mRefreshInProgress;
 		UINT32 mGCHandle;
 	};
 
@@ -101,7 +94,7 @@ namespace bs
 		/** @copydoc ScriptObjectBase::_clearManagedInstance */
 		void _clearManagedInstance() override
 		{
-			this->mGCHandle = 0;
+			this->freeManagedInstance();
 		}
 
 		/**	
@@ -113,11 +106,12 @@ namespace bs
 		}
 
 		/**	Called when the managed instance gets finalized by the CLR. */
-		void _onManagedInstanceDeleted() override
+		void _onManagedInstanceDeleted(bool assemblyRefresh) override
 		{
 			this->freeManagedInstance();
 
-			this->destroy();
+			if(!assemblyRefresh)
+				this->destroy();
 		}
 
 		ResourceHandle<ResType> mResource;

+ 3 - 3
Source/SBansheeEngine/Wrappers/BsScriptSceneObject.cpp

@@ -396,9 +396,9 @@ namespace bs
 		return false;
 	}
 
-	void ScriptSceneObject::_onManagedInstanceDeleted()
+	void ScriptSceneObject::_onManagedInstanceDeleted(bool assemblyRefresh)
 	{
-		if (!mRefreshInProgress)
+		if (!assemblyRefresh)
 			ScriptGameObjectManager::instance().destroyScriptSceneObject(this);
 		else
 			freeManagedInstance();
@@ -414,7 +414,7 @@ namespace bs
 
 	void ScriptSceneObject::_clearManagedInstance()
 	{
-		this->mGCHandle = 0;
+		freeManagedInstance();
 	}
 
 	void ScriptSceneObject::_notifyDestroyed()

+ 1 - 1
Source/SBansheeEngine/Wrappers/BsScriptSceneObject.h

@@ -37,7 +37,7 @@ namespace bs
 		ScriptSceneObject(MonoObject* instance, const HSceneObject& sceneObject);
 
 		/** @copydoc ScriptObjectBase::_onManagedInstanceDeleted */
-		void _onManagedInstanceDeleted() override;
+		void _onManagedInstanceDeleted(bool assemblyRefresh) override;
 
 		/** @copydoc ScriptObjectBase::_createManagedInstance */
 		MonoObject* _createManagedInstance(bool construct) override;

+ 3 - 3
Source/SBansheeEngine/Wrappers/GUI/BsScriptGUICanvas.cpp

@@ -60,7 +60,7 @@ namespace bs
 		UINT32 size = verticesArray.size();
 
 		Vector<Vector2I> nativeVertices(size);
-		memcpy(nativeVertices.data(), verticesArray.getRawPtr<Vector2I>(), sizeof(Vector2I) * size);
+		memcpy(nativeVertices.data(), verticesArray.getRaw<Vector2I>(), sizeof(Vector2I) * size);
 
 		canvas->drawPolyLine(nativeVertices, *color, depth);
 	}
@@ -86,7 +86,7 @@ namespace bs
 		UINT32 size = verticesArray.size();
 
 		Vector<Vector2I> nativeVertices(size);
-		memcpy(nativeVertices.data(), verticesArray.getRawPtr<Vector2I>(), sizeof(Vector2I) * size);
+		memcpy(nativeVertices.data(), verticesArray.getRaw<Vector2I>(), sizeof(Vector2I) * size);
 
 		canvas->drawTriangleStrip(nativeVertices, *color, depth);
 	}
@@ -100,7 +100,7 @@ namespace bs
 		UINT32 size = verticesArray.size();
 
 		Vector<Vector2I> nativeVertices(size);
-		memcpy(nativeVertices.data(), verticesArray.getRawPtr<Vector2I>(), sizeof(Vector2I) * size);
+		memcpy(nativeVertices.data(), verticesArray.getRaw<Vector2I>(), sizeof(Vector2I) * size);
 
 		canvas->drawTriangleList(nativeVertices, *color, depth);
 	}

+ 9 - 2
Source/SBansheeEngine/Wrappers/GUI/BsScriptGUIElement.cpp

@@ -48,11 +48,18 @@ namespace bs
 		return MonoUtil::getObjectFromGCHandle(mGCHandle);
 	}
 
-	void ScriptGUIElementBaseTBase::_onManagedInstanceDeleted()
+	void ScriptGUIElementBaseTBase::_onManagedInstanceDeleted(bool assemblyRefresh)
 	{
 		destroy();
 
-		ScriptObjectBase::_onManagedInstanceDeleted();
+		ScriptObjectBase::_onManagedInstanceDeleted(assemblyRefresh);
+	}
+
+	void ScriptGUIElementBaseTBase::_clearManagedInstance()
+	{
+		// Need to call destroy here because we need to release any GC handles before the domain is unloaded
+
+		destroy();
 	}
 
 	ScriptGUIElementTBase::ScriptGUIElementTBase(MonoObject* instance)

+ 4 - 1
Source/SBansheeEngine/Wrappers/GUI/BsScriptGUIElement.h

@@ -46,7 +46,10 @@ namespace bs
 		void initialize(GUIElementBase* element);
 
 		/** @copydoc ScriptObjectBase::_onManagedInstanceDeleted */
-		void _onManagedInstanceDeleted() override;
+		void _onManagedInstanceDeleted(bool assemblyRefresh) override;
+
+		/** @copydoc ScriptObjectBase::_clearManagedInstance */
+		void _clearManagedInstance() override;
 
 		/**	Triggered when the focus changes for the underlying GUIElementBase. */
 		static void onFocusChanged(ScriptGUIElementBaseTBase* thisPtr, bool focus);

+ 8 - 0
Source/SBansheeEngine/Wrappers/GUI/BsScriptGUILayout.cpp

@@ -83,7 +83,11 @@ namespace bs
 
 		if (iterFind != mChildren.end())
 		{
+			assert(iterFind->gcHandle != 0);
+
 			MonoUtil::freeGCHandle(iterFind->gcHandle);
+			iterFind->gcHandle = 0;
+
 			mChildren.erase(iterFind);
 		}
 	}
@@ -197,7 +201,11 @@ namespace bs
 		{
 			instance->getInternalValue()->removeElement(child.element->getGUIElement());
 
+			assert(child.gcHandle != 0);
+
 			MonoUtil::freeGCHandle(child.gcHandle);
+			child.gcHandle = 0;
+
 			child.element->setParent(nullptr);
 		}
 

+ 2 - 2
Source/SBansheeEngine/Wrappers/GUI/BsScriptGUIWidget.cpp

@@ -139,10 +139,10 @@ namespace bs
 		}
 	}
 
-	void ScriptGUIWidget::_onManagedInstanceDeleted()
+	void ScriptGUIWidget::_onManagedInstanceDeleted(bool assemblyRefresh)
 	{
 		destroy();
 
-		ScriptObject::_onManagedInstanceDeleted();
+		ScriptObject::_onManagedInstanceDeleted(assemblyRefresh);
 	}
 }

+ 1 - 1
Source/SBansheeEngine/Wrappers/GUI/BsScriptGUIWidget.h

@@ -30,7 +30,7 @@ namespace bs
 		void destroy();
 
 		/** @copydoc ScriptObject::_onManagedInstanceDeleted */
-		void _onManagedInstanceDeleted() override;
+		void _onManagedInstanceDeleted(bool assemblyRefresh) override;
 
 		SPtr<GUIWidget> mGUIWidget;
 		ScriptGUILayout* mPanel;