diff --git a/Development/Documentation/mono.md b/Development/Documentation/mono.md new file mode 100644 index 000000000..3f3267860 --- /dev/null +++ b/Development/Documentation/mono.md @@ -0,0 +1,13 @@ +## Mono + +Custom fork: [https://github.com/FlaxEngine/mono](https://github.com/FlaxEngine/mono) with custom features for C# assemblies hot-reloading at runtime without domain unload (more: [https://flaxengine.com/blog/flax-facts-16-scripts-hot-reload/](https://flaxengine.com/blog/flax-facts-16-scripts-hot-reload/)). + +### Notes + +Some useful notes and tips for devs: +* When working with mono fork set `localRepoPath` to local repo location in `Source\Tools\Flax.Build\Deps\Dependencies\mono.cs` +* To update mono deps when developing/updating use `.\Development\Scripts\Windows\CallBuildTool.bat -log -ReBuildDeps -verbose -depsToBuild=mono -platform=Windows`, then build engine and run it +* `MONO_GC_DEBUG=check-remset-consistency` - it will do additional checks at each collection to see if there are any missing write barriers +* `MONO_GC_DEBUG=nursery-canaries` - it might catch some buffer overflows in case of problems in code. +* Methods `mono_custom_attrs_from_property` and `mono_custom_attrs_get_attr` are internally cached +* If C++ mono call a method in c# that will throw an error, error will be handled but, not completly. Calling relase domain will return random `Access memory violation`. First search for error in c# code. No workaround yet. diff --git a/Source/Engine/Core/Types/StringBuilder.h b/Source/Engine/Core/Types/StringBuilder.h index f38d5279e..fd0400fba 100644 --- a/Source/Engine/Core/Types/StringBuilder.h +++ b/Source/Engine/Core/Types/StringBuilder.h @@ -269,6 +269,8 @@ public: { return String(_data.Get(), _data.Count()); } + + StringView ToStringView() const; }; inline uint32 GetHash(const StringBuilder& key) diff --git a/Source/Engine/Core/Types/StringView.cpp b/Source/Engine/Core/Types/StringView.cpp index 43776605a..d6cbb3a6a 100644 --- a/Source/Engine/Core/Types/StringView.cpp +++ b/Source/Engine/Core/Types/StringView.cpp @@ -2,6 +2,12 @@ #include "StringView.h" #include "String.h" +#include "StringBuilder.h" + +StringView StringBuilder::ToStringView() const +{ + return StringView(_data.Get(), _data.Count()); +} StringView StringView::Empty; diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.Mono.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.Mono.cpp index 572f8aec7..a8ad6e49f 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.Mono.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MCore.Mono.cpp @@ -118,6 +118,8 @@ void* MonoCalloc(size_t count, size_t size) #if USE_MONO_PROFILER +#include "Engine/Core/Types/StringBuilder.h" + struct FlaxMonoProfiler { }; @@ -126,7 +128,7 @@ FlaxMonoProfiler Profiler; struct StackWalkDataResult { - StringAnsi Buffer; + StringBuilder Buffer; }; mono_bool OnStackWalk(MonoMethod* method, int32_t native_offset, int32_t il_offset, mono_bool managed, void* data) @@ -138,16 +140,16 @@ mono_bool OnStackWalk(MonoMethod* method, int32_t native_offset, int32_t il_offs auto mName = mono_method_get_name(method); auto mKlassNameSpace = mono_class_get_namespace(mono_method_get_class(method)); auto mKlassName = mono_class_get_name(mono_method_get_class(method)); - result->Buffer += mKlassNameSpace; - result->Buffer += "."; - result->Buffer += mKlassName; - result->Buffer += "::"; - result->Buffer += mName; - result->Buffer += "\n"; + result->Buffer.Append(mKlassNameSpace); + result->Buffer.Append(TEXT(".")); + result->Buffer.Append(mKlassName); + result->Buffer.Append(TEXT("::")); + result->Buffer.Append(mName); + result->Buffer.Append(TEXT("\n")); } else if (!managed) { - result->Buffer += "\n"; + result->Buffer.Append(TEXT("\n")); } return 0; @@ -170,10 +172,10 @@ void OnGCAllocation(MonoProfiler* profiler, MonoObject* obj) if (details) { StackWalkDataResult stackTrace; - stackTrace.Buffer.reserve(1024); + stackTrace.Buffer.SetCapacity(1024); mono_stack_walk(&OnStackWalk, &stackTrace); - LOG(Info, "GC new: {0}.{1} ({2} bytes). Stack Trace:\n{3}", name_space, name, size, stackTrace.Buffer.c_str()); + LOG(Info, "GC new: {0}.{1} ({2} bytes). Stack Trace:\n{3}", String(name_space), String(name), size, stackTrace.Buffer.ToStringView()); } } #endif diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 5bddd5d94..35ca38077 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -12,8 +12,9 @@ namespace Flax.Build.Bindings partial class BindingsGenerator { private static readonly bool[] CppParamsThatNeedLocalVariable = new bool[64]; - private static readonly bool[] CppParamsThatNeedConvertion = new bool[64]; - private static readonly string[] CppParamsThatNeedConvertionWrappers = new string[64]; + private static readonly bool[] CppParamsThatNeedConversion = new bool[64]; + private static readonly string[] CppParamsThatNeedConversionWrappers = new string[64]; + private static readonly string[] CppParamsThatNeedConversionTypes = new string[64]; private static readonly string[] CppParamsWrappersCache = new string[64]; public static readonly List CppUsedNonPodTypes = new List(); private static readonly List CppUsedNonPodTypesList = new List(); @@ -620,7 +621,7 @@ namespace Flax.Build.Bindings contents.Append(", "); separator = true; - CppParamsThatNeedConvertion[i] = false; + CppParamsThatNeedConversion[i] = false; CppParamsWrappersCache[i] = GenerateCppWrapperManagedToNative(buildData, parameterInfo.Type, caller, out var managedType, functionInfo, out CppParamsThatNeedLocalVariable[i]); contents.Append(managedType); if (parameterInfo.IsRef || parameterInfo.IsOut || UsePassByReference(buildData, parameterInfo.Type, caller)) @@ -654,8 +655,8 @@ namespace Flax.Build.Bindings if (convertOutputParameter) { useInlinedReturn = false; - CppParamsThatNeedConvertion[i] = true; - CppParamsThatNeedConvertionWrappers[i] = GenerateCppWrapperNativeToManaged(buildData, parameterInfo.Type, caller, out _, functionInfo); + CppParamsThatNeedConversion[i] = true; + CppParamsThatNeedConversionWrappers[i] = GenerateCppWrapperNativeToManaged(buildData, parameterInfo.Type, caller, out CppParamsThatNeedConversionTypes[i], functionInfo); } } } @@ -717,7 +718,7 @@ namespace Flax.Build.Bindings callParams += ", "; separator = true; var name = parameterInfo.Name; - if (CppParamsThatNeedConvertion[i] && (!FindApiTypeInfo(buildData, parameterInfo.Type, caller)?.IsStruct ?? false)) + if (CppParamsThatNeedConversion[i] && (!FindApiTypeInfo(buildData, parameterInfo.Type, caller)?.IsStruct ?? false)) name = '*' + name; string param = string.Empty; @@ -735,7 +736,7 @@ namespace Flax.Build.Bindings } // Special case for output result parameters that needs additional converting from native to managed format (such as non-POD structures or output array parameter) - if (CppParamsThatNeedConvertion[i]) + if (CppParamsThatNeedConversion[i]) { var apiType = FindApiTypeInfo(buildData, parameterInfo.Type, caller); if (apiType != null) @@ -792,9 +793,41 @@ namespace Flax.Build.Bindings var parameterInfo = functionInfo.Parameters[i]; // Special case for output result parameters that needs additional converting from native to managed format (such as non-POD structures or output array parameter) - if (CppParamsThatNeedConvertion[i]) + if (CppParamsThatNeedConversion[i]) { - contents.AppendFormat(" *{0} = {1};", parameterInfo.Name, string.Format(CppParamsThatNeedConvertionWrappers[i], parameterInfo.Name + "Temp")).AppendLine(); + var value = string.Format(CppParamsThatNeedConversionWrappers[i], parameterInfo.Name + "Temp"); + + // MonoObject* parameters returned by reference need write barrier for GC + if (parameterInfo.IsOut) + { + var apiType = FindApiTypeInfo(buildData, parameterInfo.Type, caller); + if (apiType != null) + { + if (apiType.IsClass) + { + contents.AppendFormat(" mono_gc_wbarrier_generic_store({0}, (MonoObject*){1});", parameterInfo.Name, value).AppendLine(); + continue; + } + if (apiType.IsStruct && !apiType.IsPod) + { + CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); + contents.AppendFormat(" {{ auto _temp = {1}; mono_gc_wbarrier_value_copy({0}, &_temp, 1, {2}::TypeInitializer.GetType().ManagedClass->GetNative()); }}", parameterInfo.Name, value, apiType.FullNameNative).AppendLine(); + continue; + } + } + else + { + // BytesContainer + if (parameterInfo.Type.Type == "BytesContainer" && parameterInfo.Type.GenericArgs == null) + { + contents.AppendFormat(" mono_gc_wbarrier_generic_store({0}, (MonoObject*){1});", parameterInfo.Name, value).AppendLine(); + continue; + } + + throw new Exception($"Unsupported type of parameter '{parameterInfo}' in method '{functionInfo}' to be passed using 'out'"); + } + } + contents.AppendFormat(" *{0} = {1};", parameterInfo.Name, value).AppendLine(); } } }