From 5fa10fd17b7b74a73680133a43d387b7581431f6 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 14 Jan 2023 17:45:21 +0200 Subject: [PATCH 001/188] Allow unsubscribing from managed events multiple times --- .../Bindings/BindingsGenerator.CSharp.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index dc3e09b8f..502dc5324 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -762,7 +762,7 @@ namespace Flax.Build.Bindings CppParamsWrappersCache[i] = result; } - string eventSignature; + string eventType; if (useCustomDelegateSignature) { contents.Append(indent).Append($"/// The delegate for event {eventInfo.Name}.").AppendLine(); @@ -777,24 +777,25 @@ namespace Flax.Build.Bindings contents.Append(CppParamsWrappersCache[i]).Append(" arg").Append(i); } contents.Append(");").AppendLine().AppendLine(); - eventSignature = "event " + eventInfo.Name + "Delegate"; + eventType = eventInfo.Name + "Delegate"; } else { - eventSignature = "event Action"; + eventType = "Action"; if (paramsCount != 0) { - eventSignature += '<'; + eventType += '<'; for (var i = 0; i < paramsCount; i++) { if (i != 0) - eventSignature += ", "; + eventType += ", "; CppParamsWrappersCache[i] = GenerateCSharpNativeToManaged(buildData, eventInfo.Type.GenericArgs[i], classInfo); - eventSignature += CppParamsWrappersCache[i]; + eventType += CppParamsWrappersCache[i]; } - eventSignature += '>'; + eventType += '>'; } } + string eventSignature = "event " + eventType; GenerateCSharpComment(contents, indent, eventInfo.Comment, true); GenerateCSharpAttributes(buildData, contents, indent, classInfo, eventInfo, useUnmanaged); @@ -808,11 +809,7 @@ namespace Flax.Build.Bindings indent += " "; var eventInstance = eventInfo.IsStatic ? string.Empty : "__unmanagedPtr, "; contents.Append(indent).Append($"add {{ Internal_{eventInfo.Name} += value; if (Internal_{eventInfo.Name}_Count++ == 0) Internal_{eventInfo.Name}_Bind({eventInstance}true); }}").AppendLine(); - contents.Append(indent).Append("remove { ").AppendLine(); - contents.Append("#if FLAX_EDITOR || BUILD_DEBUG").AppendLine(); - contents.Append(indent).Append($"if (Internal_{eventInfo.Name} != null) {{ bool invalid = true; foreach (Delegate e in Internal_{eventInfo.Name}.GetInvocationList()) {{ if (e == (Delegate)value) {{ invalid = false; break; }} }} if (invalid) throw new Exception(\"Cannot unregister from event if not registered before.\"); }}").AppendLine(); - contents.Append("#endif").AppendLine(); - contents.Append(indent).Append($"Internal_{eventInfo.Name} -= value; if (--Internal_{eventInfo.Name}_Count == 0) Internal_{eventInfo.Name}_Bind({eventInstance}false); }}").AppendLine(); + contents.Append(indent).Append($"remove {{ var __delegate = ({eventType})Delegate.Remove(Internal_{eventInfo.Name}, value); if (__delegate != Internal_{eventInfo.Name}) {{ Internal_{eventInfo.Name} = __delegate; if (--Internal_{eventInfo.Name}_Count == 0) Internal_{eventInfo.Name}_Bind({eventInstance}false); }} }}").AppendLine(); indent = indent.Substring(0, indent.Length - 4); contents.Append(indent).Append('}').AppendLine(); From bf9dbbc79e7ad293f1bd46c75c16f70d89f62af5 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 14 Jan 2023 18:01:14 +0200 Subject: [PATCH 002/188] Restore disabled compiler warnings of unused events --- Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 502dc5324..93e70c9bd 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -826,6 +826,7 @@ namespace Flax.Build.Bindings contents.Append("static "); contents.Append($"{eventSignature} Internal_{eventInfo.Name};"); contents.AppendLine(); + contents.Append("#pragma warning restore 67").AppendLine(); contents.AppendLine(); contents.Append(indent).Append("internal "); From d4d404ac0b054573ee2eaf5d3c4f8140658fd27a Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 5 Aug 2023 12:48:42 +0300 Subject: [PATCH 003/188] Optimize native interop marshallers runtime type checking --- .../Engine/Engine/NativeInterop.Unmanaged.cs | 43 +- Source/Engine/Engine/NativeInterop.cs | 730 ++++++++++++------ 2 files changed, 515 insertions(+), 258 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index d2a8ca55e..f6239dcf4 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -695,7 +695,7 @@ namespace FlaxEngine.Interop catch (Exception exception) { if (exceptionPtr != IntPtr.Zero) - Marshal.WriteIntPtr(exceptionPtr, ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); + Unsafe.Write(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); return IntPtr.Zero; } return returnValue; @@ -710,7 +710,7 @@ namespace FlaxEngine.Interop for (int i = 0; i < numParams; i++) { - IntPtr nativePtr = Marshal.ReadIntPtr(IntPtr.Add(paramPtr, sizeof(IntPtr) * i)); + IntPtr nativePtr = Unsafe.Read((IntPtr.Add(paramPtr, sizeof(IntPtr) * i)).ToPointer()); methodParameters[i] = MarshalToManaged(nativePtr, methodHolder.parameterTypes[i]); } @@ -726,7 +726,7 @@ namespace FlaxEngine.Interop realException = exception.InnerException; if (exceptionPtr != IntPtr.Zero) - Marshal.WriteIntPtr(exceptionPtr, ManagedHandle.ToIntPtr(realException, GCHandleType.Weak)); + Unsafe.Write(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(realException, GCHandleType.Weak)); else throw realException; return IntPtr.Zero; @@ -738,7 +738,7 @@ namespace FlaxEngine.Interop Type parameterType = methodHolder.parameterTypes[i]; if (parameterType.IsByRef) { - IntPtr nativePtr = Marshal.ReadIntPtr(IntPtr.Add(paramPtr, sizeof(IntPtr) * i)); + IntPtr nativePtr = Unsafe.Read((IntPtr.Add(paramPtr, sizeof(IntPtr) * i)).ToPointer()); MarshalToNative(methodParameters[i], nativePtr, parameterType.GetElementType()); } } @@ -800,7 +800,40 @@ namespace FlaxEngine.Interop { object fieldOwner = fieldOwnerHandle.Target; FieldHolder field = Unsafe.As(fieldHandle.Target); - field.toNativeMarshaller(field.field, fieldOwner, valuePtr, out int fieldOffset); + field.toNativeMarshaller(field.fieldOffset, fieldOwner, valuePtr, out int fieldSize); + } + + [UnmanagedCallersOnly] + internal static void FieldGetValueReference(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle, IntPtr valuePtr) + { + object fieldOwner = fieldOwnerHandle.Target; + FieldHolder field = Unsafe.As(fieldHandle.Target); + if (fieldOwner.GetType().IsValueType) + { + ref IntPtr fieldRef = ref FieldHelper.GetValueTypeFieldReference(field.fieldOffset, ref fieldOwner); + Unsafe.Write(valuePtr.ToPointer(), fieldRef); + } + else + { + ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(field.fieldOffset, ref fieldOwner); + Unsafe.Write(valuePtr.ToPointer(), fieldRef); + } + } + + [UnmanagedCallersOnly] + internal static void FieldGetValueReferenceWithOffset(ManagedHandle fieldOwnerHandle, int fieldOffset, IntPtr valuePtr) + { + object fieldOwner = fieldOwnerHandle.Target; + if (fieldOwner.GetType().IsValueType) + { + ref IntPtr fieldRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + Unsafe.Write(valuePtr.ToPointer(), fieldRef); + } + else + { + ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + Unsafe.Write(valuePtr.ToPointer(), fieldRef); + } } [UnmanagedCallersOnly] diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index da372ac90..739329ed8 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -278,19 +278,12 @@ namespace FlaxEngine.Interop return FindType(internalAssemblyQualifiedName); } - internal class ReferenceTypePlaceholder - { - } - - internal struct ValueTypePlaceholder - { - } + internal class ReferenceTypePlaceholder { } + internal struct ValueTypePlaceholder { } internal delegate object MarshalToManagedDelegate(IntPtr nativePtr, bool byRef); - internal delegate void MarshalToNativeDelegate(object managedObject, IntPtr nativePtr); - - internal delegate void MarshalToNativeFieldDelegate(FieldInfo field, object fieldOwner, IntPtr nativePtr, out int fieldOffset); + internal delegate void MarshalToNativeFieldDelegate(int fieldOffset, object fieldOwner, IntPtr nativePtr, out int fieldSize); internal static ConcurrentDictionary toManagedMarshallers = new ConcurrentDictionary(1, 3); internal static ConcurrentDictionary toNativeMarshallers = new ConcurrentDictionary(1, 3); @@ -332,7 +325,7 @@ namespace FlaxEngine.Interop deleg(managedObject, nativePtr); } - internal static MarshalToNativeFieldDelegate GetToNativeFieldMarshallerDelegate(Type type) + internal static MarshalToNativeFieldDelegate GetToNativeFieldMarshallerDelegate(FieldInfo field, Type type) { static MarshalToNativeFieldDelegate Factory(Type type) { @@ -349,9 +342,46 @@ namespace FlaxEngine.Interop return toNativeFieldMarshallers.GetOrAdd(type, Factory); } - internal static void MarshalToNativeField(FieldInfo field, object fieldOwner, IntPtr nativePtr, out int fieldOffset) + internal static class FieldHelper { - GetToNativeFieldMarshallerDelegate(fieldOwner.GetType())(field, fieldOwner, nativePtr, out fieldOffset); + /// + /// Returns the address of the field, relative to field owner. + /// + internal static int GetFieldOffset(FieldInfo field, Type type) + { + // Get the address of the field, source: https://stackoverflow.com/a/56512720 + int fieldOffset = Unsafe.Read((field.FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF; + if (!type.IsValueType) + fieldOffset += IntPtr.Size; + return fieldOffset; + } + + /// + /// Returns a reference to the value of the field. + /// + internal static ref TField GetReferenceTypeFieldReference(int fieldOffset, ref object fieldOwner) + { + byte* fieldPtr = (byte*)Unsafe.As(ref fieldOwner) + fieldOffset; + return ref Unsafe.AsRef(fieldPtr); + } + + /// + /// Returns a reference to the value of the field. + /// + internal static ref TField GetValueTypeFieldReference(int fieldOffset, ref T fieldOwner) //where T : struct + { + byte* fieldPtr = (byte*)Unsafe.AsPointer(ref fieldOwner) + fieldOffset; + return ref Unsafe.AsRef(fieldPtr); + } + + /// + /// Returns a reference to the value of the field. + /// + internal static ref TField GetReferenceTypeFieldReference(int fieldOffset, ref T fieldOwner) //where T : class + { + byte* fieldPtr = (byte*)Unsafe.As(ref fieldOwner) + fieldOffset; + return ref Unsafe.AsRef(fieldPtr); + } } /// @@ -360,12 +390,12 @@ namespace FlaxEngine.Interop internal static class MarshalHelper { private delegate void MarshalToNativeTypedDelegate(ref T managedValue, IntPtr nativePtr); - private delegate void MarshalToManagedTypedDelegate(ref T managedValue, IntPtr nativePtr, bool byRef); - - internal delegate void MarshalFieldTypedDelegate(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset); + internal delegate void MarshalFieldTypedDelegate(int managedFieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize); + internal delegate void* GetBasePointer(ref T fieldOwner); internal static FieldInfo[] marshallableFields; + internal static int[] marshallableFieldOffsets; internal static MarshalFieldTypedDelegate[] toManagedFieldMarshallers; internal static MarshalFieldTypedDelegate[] toNativeFieldMarshallers; @@ -376,28 +406,9 @@ namespace FlaxEngine.Interop { Type type = typeof(T); - // Setup marshallers for managed and native directions - MethodInfo toManagedMethod; - if (type.IsValueType) - toManagedMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperValueType.ToManaged), BindingFlags.Static | BindingFlags.NonPublic); - else if (type.IsArray && type.GetElementType().IsValueType) - toManagedMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type.GetElementType()).GetMethod(nameof(MarshalHelperValueType.ToManagedArray), BindingFlags.Static | BindingFlags.NonPublic); - else if (type.IsArray && !type.GetElementType().IsValueType) - toManagedMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type.GetElementType()).GetMethod(nameof(MarshalHelperReferenceType.ToManagedArray), BindingFlags.Static | BindingFlags.NonPublic); - else - toManagedMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperReferenceType.ToManaged), BindingFlags.Static | BindingFlags.NonPublic); - toManagedTypedMarshaller = toManagedMethod.CreateDelegate(); - - MethodInfo toNativeMethod; - if (type.IsValueType) - toNativeMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperValueType.ToNative), BindingFlags.Static | BindingFlags.NonPublic); - else - toNativeMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperReferenceType.ToNative), BindingFlags.Static | BindingFlags.NonPublic); - toNativeTypedMarshaller = toNativeMethod.CreateDelegate(); - + // Setup field-by-field marshallers for reference types or structures containing references if (!type.IsPrimitive && !type.IsPointer && type != typeof(bool)) { - // Setup field-by-field marshallers for reference types or structures containing references marshallableFields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (type.IsValueType && !marshallableFields.Any(x => (x.FieldType.IsClass && !x.FieldType.IsPointer) || x.FieldType.Name == "Boolean")) marshallableFields = null; @@ -408,6 +419,7 @@ namespace FlaxEngine.Interop { toManagedFieldMarshallers = new MarshalFieldTypedDelegate[marshallableFields.Length]; toNativeFieldMarshallers = new MarshalFieldTypedDelegate[marshallableFields.Length]; + marshallableFieldOffsets = new int[marshallableFields.Length]; BindingFlags bindingFlags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public; for (int i = 0; i < marshallableFields.Length; i++) { @@ -418,8 +430,16 @@ namespace FlaxEngine.Interop if (fieldType.IsPointer) { - toManagedFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToManagedFieldPointer), bindingFlags); - toNativeFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToNativeFieldPointer), bindingFlags); + if (type.IsValueType) + { + toManagedFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToManagedFieldPointerValueType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToNativeFieldPointerValueType), bindingFlags); + } + else + { + toManagedFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToManagedFieldPointerReferenceType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToNativeFieldPointerReferenceType), bindingFlags); + } } else if (fieldType.IsValueType) { @@ -430,8 +450,16 @@ namespace FlaxEngine.Interop } else { - toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedField), bindingFlags); - toNativeFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToNativeField), bindingFlags); + if (type.IsValueType) + { + toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldValueType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToNativeFieldValueType), bindingFlags); + } + else + { + toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldReferenceType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToNativeFieldReferenceType), bindingFlags); + } } } else if (fieldType.IsArray) @@ -439,25 +467,125 @@ namespace FlaxEngine.Interop Type arrayElementType = fieldType.GetElementType(); if (arrayElementType.IsValueType) { - toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldArray), bindingFlags); - toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeField), bindingFlags); + if (type.IsValueType) + { + toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldArrayValueType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldValueType), bindingFlags); + } + else + { + toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldArrayReferenceType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldReferenceType), bindingFlags); + } } else { - toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldArray), bindingFlags); - toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeField), bindingFlags); + if (type.IsValueType) + { + toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldArrayValueType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldValueType), bindingFlags); + } + else + { + toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldArrayReferenceType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldReferenceType), bindingFlags); + } } } else { - toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedField), bindingFlags); - toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeField), bindingFlags); + if (type.IsValueType) + { + toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldValueType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldValueType), bindingFlags); + } + else + { + toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldReferenceType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldReferenceType), bindingFlags); + } } toManagedFieldMarshallers[i] = toManagedFieldMethod.CreateDelegate(); toNativeFieldMarshallers[i] = toNativeFieldMethod.CreateDelegate(); + marshallableFieldOffsets[i] = FieldHelper.GetFieldOffset(field, type); } } } + + // Setup marshallers for managed and native directions + MethodInfo toManagedMethod; + if (type.IsValueType) + { + string methodName; + if (type == typeof(IntPtr)) + methodName = nameof(MarshalHelperValueType.ToManagedPointer); + else if (type == typeof(ManagedHandle)) + methodName = nameof(MarshalHelperValueType.ToManagedHandle); + else if (marshallableFields != null) + methodName = nameof(MarshalHelperValueType.ToManagedWithMarshallableFields); + else + methodName = nameof(MarshalHelperValueType.ToManaged); + + toManagedMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); + } + else if (type.IsArray) + { + Type elementType = type.GetElementType(); + if (elementType.IsValueType) + { + string methodName; + if (ArrayFactory.GetMarshalledType(elementType) == elementType) + methodName = nameof(MarshalHelperValueType.ToManagedArray); + else + methodName = nameof(MarshalHelperValueType.ToManagedArrayMarshalled); + toManagedMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type.GetElementType()).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); + } + else + toManagedMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type.GetElementType()).GetMethod(nameof(MarshalHelperReferenceType.ToManagedArray), BindingFlags.Static | BindingFlags.NonPublic); + } + else + { + string methodName; + if (type == typeof(string)) + methodName = nameof(MarshalHelperReferenceType.ToManagedString); + else if (type == typeof(Type)) + methodName = nameof(MarshalHelperReferenceType.ToManagedType); + else if (type.IsClass) + methodName = nameof(MarshalHelperReferenceType.ToManagedClass); + else if (type.IsInterface) // Dictionary + methodName = nameof(MarshalHelperReferenceType.ToManagedInterface); + else + throw new NativeInteropException($"Unsupported type '{type.FullName}'"); + toManagedMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); + } + toManagedTypedMarshaller = toManagedMethod.CreateDelegate(); + + MethodInfo toNativeMethod; + if (type.IsValueType) + { + if (type.IsByRef) + throw new NotImplementedException(); // Is this possible? + if (marshallableFields != null) + toNativeMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperValueType.ToNativeWithMarshallableFields), BindingFlags.Static | BindingFlags.NonPublic); + else + toNativeMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperValueType.ToNative), BindingFlags.Static | BindingFlags.NonPublic); + } + else + { + string methodName; + if (type == typeof(string)) + methodName = nameof(MarshalHelperReferenceType.ToNativeString); + else if (type == typeof(Type)) + methodName = nameof(MarshalHelperReferenceType.ToNativeType); + else if (type.IsPointer) + methodName = nameof(MarshalHelperReferenceType.ToNativePointer); + else if (type.IsArray) + methodName = nameof(MarshalHelperReferenceType.ToNativeArray); + else + methodName = nameof(MarshalHelperReferenceType.ToNative); + toNativeMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); + } + toNativeTypedMarshaller = toNativeMethod.CreateDelegate(); } internal static object ToManagedWrapper(IntPtr nativePtr, bool byRef) @@ -475,16 +603,12 @@ namespace FlaxEngine.Interop [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static T ToManagedUnbox(IntPtr nativePtr) { - T managed = default; - if (nativePtr != IntPtr.Zero) - { - Type type = typeof(T); - if (type.IsArray) - managed = (T)MarshalToManaged(nativePtr, type); // Array might be in internal format of custom structs so unbox if need to - else - managed = (T)ManagedHandle.FromIntPtr(nativePtr).Target; - } - return managed; + T value = default; + if (nativePtr == IntPtr.Zero) + return value; + + MarshalHelper.ToManaged(ref value, nativePtr, false); + return value; } internal static Array ToManagedArray(Span ptrSpan) @@ -512,52 +636,48 @@ namespace FlaxEngine.Interop toNativeTypedMarshaller(ref managedValue, nativePtr); } - internal static void ToNativeField(FieldInfo field, ref T fieldOwner, IntPtr nativePtr, out int fieldOffset) + internal static void ToNativeField(int fieldOffset, ref T fieldOwner, IntPtr nativePtr, out int fieldSize) { if (marshallableFields != null) { for (int i = 0; i < marshallableFields.Length; i++) { - if (marshallableFields[i] == field) + if (marshallableFieldOffsets[i] == fieldOffset) { - toNativeFieldMarshallers[i](marshallableFields[i], ref fieldOwner, nativePtr, out fieldOffset); + toNativeFieldMarshallers[i](fieldOffset, ref fieldOwner, nativePtr, out fieldSize); return; } } } - throw new NativeInteropException($"Invalid field {field.Name} to marshal for type {typeof(T).Name}"); + throw new NativeInteropException($"Invalid field with offset {fieldOffset} to marshal for type {typeof(T).Name}"); } - private static void ToManagedFieldPointer(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + private static void ToManagedFieldPointerValueType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { - ref IntPtr fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - fieldValueRef = Unsafe.Read(fieldPtr.ToPointer()); - fieldOffset = IntPtr.Size; + ref IntPtr fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + fieldValueRef = Unsafe.Read(nativeFieldPtr.ToPointer()); + fieldSize = IntPtr.Size; } - private static void ToNativeFieldPointer(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + private static void ToManagedFieldPointerReferenceType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { - ref IntPtr fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - Unsafe.Write(fieldPtr.ToPointer(), fieldValueRef); - fieldOffset = IntPtr.Size; + ref IntPtr fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + fieldValueRef = Unsafe.Read(nativeFieldPtr.ToPointer()); + fieldSize = IntPtr.Size; } - /// - /// Returns a reference to the value of the field. - /// - private static ref TField GetFieldReference(FieldInfo field, ref T fieldOwner) + private static void ToNativeFieldPointerValueType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { - // Get the address of the field, source: https://stackoverflow.com/a/56512720 - if (typeof(T).IsValueType) - { - byte* fieldPtr = (byte*)Unsafe.AsPointer(ref fieldOwner) + (Marshal.ReadInt32(field.FieldHandle.Value + 4 + IntPtr.Size) & 0xFFFFFF); - return ref Unsafe.AsRef(fieldPtr); - } - else - { - byte* fieldPtr = (byte*)Unsafe.As(ref fieldOwner) + IntPtr.Size + (Marshal.ReadInt32(field.FieldHandle.Value + 4 + IntPtr.Size) & 0xFFFFFF); - return ref Unsafe.AsRef(fieldPtr); - } + ref IntPtr fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValueRef); + fieldSize = IntPtr.Size; + } + + private static void ToNativeFieldPointerReferenceType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + { + ref IntPtr fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValueRef); + fieldSize = IntPtr.Size; } private static IntPtr EnsureAlignment(IntPtr ptr, int alignment) @@ -588,48 +708,92 @@ namespace FlaxEngine.Interop fieldAlignment = GetTypeSize(fieldType); } - internal static void ToManagedField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedFieldValueType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { - fieldOffset = Unsafe.SizeOf(); + fieldSize = Unsafe.SizeOf(); if (fieldAlignment > 1) { - IntPtr fieldStartPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, fieldAlignment); - fieldOffset += (fieldPtr - fieldStartPtr).ToInt32(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, fieldAlignment); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); } - ref TField fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - MarshalHelperValueType.ToManaged(ref fieldValueRef, fieldPtr, false); + ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, nativeFieldPtr, false); } - internal static void ToManagedFieldArray(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedFieldReferenceType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { - // Follows the same marshalling semantics with reference types - fieldOffset = Unsafe.SizeOf(); - IntPtr fieldStartPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, IntPtr.Size); - fieldOffset += (fieldPtr - fieldStartPtr).ToInt32(); - - ref TField[] fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - MarshalHelperValueType.ToManagedArray(ref fieldValueRef, Unsafe.Read(fieldPtr.ToPointer()), false); - } - - internal static void ToNativeField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) - { - fieldOffset = Unsafe.SizeOf(); + fieldSize = Unsafe.SizeOf(); if (fieldAlignment > 1) { - IntPtr startPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, fieldAlignment); - fieldOffset += (fieldPtr - startPtr).ToInt32(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, fieldAlignment); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + } + + ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, nativeFieldPtr, false); + } + + internal static void ToManagedFieldArrayValueType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct + { + // Follows the same marshalling semantics with reference types + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + + ref TField[] fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); + } + + internal static void ToManagedFieldArrayReferenceType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + { + // Follows the same marshalling semantics with reference types + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + + ref TField[] fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); + } + + internal static void ToNativeFieldValueType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct + { + fieldSize = Unsafe.SizeOf(); + if (fieldAlignment > 1) + { + IntPtr startPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, fieldAlignment); + fieldSize += (nativeFieldPtr - startPtr).ToInt32(); } #if USE_AOT TField fieldValueRef = (TField)field.GetValue(fieldOwner); #else - ref TField fieldValueRef = ref GetFieldReference(field, ref fieldOwner); + ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); #endif - MarshalHelperValueType.ToNative(ref fieldValueRef, fieldPtr); + MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); + } + + internal static void ToNativeFieldReferenceType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + { + fieldSize = Unsafe.SizeOf(); + if (fieldAlignment > 1) + { + IntPtr startPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, fieldAlignment); + fieldSize += (nativeFieldPtr - startPtr).ToInt32(); + } + +#if USE_AOT + TField fieldValueRef = (TField)field.GetValue(fieldOwner); +#else + ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); +#endif + MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); } } @@ -639,50 +803,83 @@ namespace FlaxEngine.Interop { } - internal static void ToManagedField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedField(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) { - fieldOffset = 0; + fieldSize = 0; } - internal static void ToNativeField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToNativeField(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) { - fieldOffset = 0; + fieldSize = 0; } } private static class ReferenceTypeField where TField : class { - internal static void ToManagedField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedFieldValueType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { - fieldOffset = Unsafe.SizeOf(); - IntPtr fieldStartPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, IntPtr.Size); - fieldOffset += (fieldPtr - fieldStartPtr).ToInt32(); + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); - ref TField fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - MarshalHelperReferenceType.ToManaged(ref fieldValueRef, Unsafe.Read(fieldPtr.ToPointer()), false); + ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); } - internal static void ToManagedFieldArray(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedFieldReferenceType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { - fieldOffset = Unsafe.SizeOf(); - IntPtr fieldStartPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, IntPtr.Size); - fieldOffset += (fieldPtr - fieldStartPtr).ToInt32(); + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); - ref TField[] fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - MarshalHelperReferenceType.ToManagedArray(ref fieldValueRef, Unsafe.Read(fieldPtr.ToPointer()), false); + ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); } - internal static void ToNativeField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedFieldArrayValueType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { - fieldOffset = Unsafe.SizeOf(); - IntPtr fieldStartPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, IntPtr.Size); - fieldOffset += (fieldPtr - fieldStartPtr).ToInt32(); + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); - ref TField fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - MarshalHelperReferenceType.ToNative(ref fieldValueRef, fieldPtr); + ref TField[] fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); + } + + internal static void ToManagedFieldArrayReferenceType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + { + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + + ref TField[] fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); + } + + internal static void ToNativeFieldValueType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct + { + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + + ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); + } + + internal static void ToNativeFieldReferenceType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + { + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + + ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); } } } @@ -691,80 +888,87 @@ namespace FlaxEngine.Interop { internal static void ToNativeWrapper(object managedObject, IntPtr nativePtr) { - ToNative(ref Unsafe.Unbox(managedObject), nativePtr); + MarshalHelper.ToNative(ref Unsafe.Unbox(managedObject), nativePtr); } - internal static void ToNativeFieldWrapper(FieldInfo field, object fieldOwner, IntPtr nativePtr, out int fieldOffset) + internal static void ToNativeFieldWrapper(int fieldOffset, object fieldOwner, IntPtr nativePtr, out int fieldSize) { - MarshalHelper.ToNativeField(field, ref Unsafe.Unbox(fieldOwner), nativePtr, out fieldOffset); + MarshalHelper.ToNativeField(fieldOffset, ref Unsafe.Unbox(fieldOwner), nativePtr, out fieldSize); + } + + internal static void ToManagedPointer(ref IntPtr managedValue, IntPtr nativePtr, bool byRef) + { + Type type = typeof(T); + byRef |= type.IsByRef; // Is this needed? + if (type.IsByRef) + Assert.IsTrue(type.GetElementType().IsValueType); + + managedValue = byRef ? nativePtr : Unsafe.Read(nativePtr.ToPointer()); + } + + internal static void ToManagedHandle(ref ManagedHandle managedValue, IntPtr nativePtr, bool byRef) + { + managedValue = ManagedHandle.FromIntPtr(nativePtr); + } + + internal static void ToManagedWithMarshallableFields(ref T managedValue, IntPtr nativePtr, bool byRef) + { + IntPtr fieldPtr = nativePtr; + for (int i = 0; i < MarshalHelper.marshallableFields.Length; i++) + { + MarshalHelper.toManagedFieldMarshallers[i](MarshalHelper.marshallableFieldOffsets[i], ref managedValue, fieldPtr, out int fieldSize); + fieldPtr += fieldSize; + } + Assert.IsTrue((fieldPtr - nativePtr) <= Unsafe.SizeOf()); } internal static void ToManaged(ref T managedValue, IntPtr nativePtr, bool byRef) { - Type type = typeof(T); - byRef |= type.IsByRef; - if (byRef) - { - if (type.IsByRef) - type = type.GetElementType(); - Assert.IsTrue(type.IsValueType); - } - - if (type == typeof(IntPtr) && byRef) - managedValue = (T)(object)nativePtr; - else if (type == typeof(ManagedHandle)) - managedValue = (T)(object)ManagedHandle.FromIntPtr(nativePtr); - else if (MarshalHelper.marshallableFields != null) - { - IntPtr fieldPtr = nativePtr; - for (int i = 0; i < MarshalHelper.marshallableFields.Length; i++) - { - MarshalHelper.toManagedFieldMarshallers[i](MarshalHelper.marshallableFields[i], ref managedValue, fieldPtr, out int fieldOffset); - fieldPtr += fieldOffset; - } - Assert.IsTrue((fieldPtr - nativePtr) <= Unsafe.SizeOf()); - } - else - managedValue = Unsafe.Read(nativePtr.ToPointer()); + managedValue = Unsafe.Read(nativePtr.ToPointer()); } internal static void ToManagedArray(ref T[] managedValue, IntPtr nativePtr, bool byRef) { if (byRef) - nativePtr = Marshal.ReadIntPtr(nativePtr); + nativePtr = Unsafe.Read(nativePtr.ToPointer()); - Type elementType = typeof(T); if (nativePtr != IntPtr.Zero) { ManagedArray managedArray = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); - if (ArrayFactory.GetMarshalledType(elementType) == elementType) - managedValue = Unsafe.As(managedArray.ToArray()); - else if (elementType.IsValueType) - managedValue = Unsafe.As(MarshalHelper.ToManagedArray(managedArray)); - else - managedValue = Unsafe.As(MarshalHelper.ToManagedArray(managedArray.ToSpan())); + managedValue = Unsafe.As(managedArray.ToArray()); } else managedValue = null; } - internal static void ToNative(ref T managedValue, IntPtr nativePtr) + internal static void ToManagedArrayMarshalled(ref T[] managedValue, IntPtr nativePtr, bool byRef) { - if (typeof(T).IsByRef) - throw new NotImplementedException(); + if (byRef) + nativePtr = Unsafe.Read(nativePtr.ToPointer()); - if (MarshalHelper.marshallableFields != null) + if (nativePtr != IntPtr.Zero) { - IntPtr fieldPtr = nativePtr; - for (int i = 0; i < MarshalHelper.marshallableFields.Length; i++) - { - MarshalHelper.toNativeFieldMarshallers[i](MarshalHelper.marshallableFields[i], ref managedValue, nativePtr, out int fieldOffset); - nativePtr += fieldOffset; - } - Assert.IsTrue((nativePtr - fieldPtr) <= Unsafe.SizeOf()); + ManagedArray managedArray = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); + managedValue = Unsafe.As(MarshalHelper.ToManagedArray(managedArray)); } else - Unsafe.AsRef(nativePtr.ToPointer()) = managedValue; + managedValue = null; + } + + internal static void ToNativeWithMarshallableFields(ref T managedValue, IntPtr nativePtr) + { + IntPtr fieldPtr = nativePtr; + for (int i = 0; i < MarshalHelper.marshallableFields.Length; i++) + { + MarshalHelper.toNativeFieldMarshallers[i](MarshalHelper.marshallableFieldOffsets[i], ref managedValue, nativePtr, out int fieldSize); + nativePtr += fieldSize; + } + Assert.IsTrue((nativePtr - fieldPtr) <= Unsafe.SizeOf()); + } + + internal static void ToNative(ref T managedValue, IntPtr nativePtr) + { + Unsafe.AsRef(nativePtr.ToPointer()) = managedValue; } } @@ -773,37 +977,47 @@ namespace FlaxEngine.Interop internal static void ToNativeWrapper(object managedObject, IntPtr nativePtr) { T managedValue = Unsafe.As(managedObject); - ToNative(ref managedValue, nativePtr); + MarshalHelper.ToNative(ref managedValue, nativePtr); } - internal static void ToNativeFieldWrapper(FieldInfo field, object managedObject, IntPtr nativePtr, out int fieldOffset) + internal static void ToNativeFieldWrapper(int fieldOffset, object managedObject, IntPtr nativePtr, out int fieldSize) { T managedValue = Unsafe.As(managedObject); - MarshalHelper.ToNativeField(field, ref managedValue, nativePtr, out fieldOffset); + MarshalHelper.ToNativeField(fieldOffset, ref managedValue, nativePtr, out fieldSize); } - internal static void ToManaged(ref T managedValue, IntPtr nativePtr, bool byRef) + internal static void ToManagedString(ref string managedValue, IntPtr nativePtr, bool byRef) { - Type type = typeof(T); if (byRef) - nativePtr = Marshal.ReadIntPtr(nativePtr); + nativePtr = Unsafe.Read(nativePtr.ToPointer()); + managedValue = ManagedString.ToManaged(nativePtr); + } - if (type == typeof(string)) - managedValue = Unsafe.As(ManagedString.ToManaged(nativePtr)); - else if (nativePtr == IntPtr.Zero) - managedValue = null; - else if (type.IsClass) - managedValue = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); - else if (type.IsInterface) // Dictionary - managedValue = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); - else - throw new NotImplementedException(); + internal static void ToManagedType(ref Type managedValue, IntPtr nativePtr, bool byRef) + { + if (byRef) + nativePtr = Unsafe.Read(nativePtr.ToPointer()); + managedValue = nativePtr == IntPtr.Zero ? null : Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); + } + + internal static void ToManagedClass(ref T managedValue, IntPtr nativePtr, bool byRef) + { + if (byRef) + nativePtr = Unsafe.Read(nativePtr.ToPointer()); + managedValue = nativePtr == IntPtr.Zero ? null : Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); + } + + internal static void ToManagedInterface(ref T managedValue, IntPtr nativePtr, bool byRef) // Dictionary + { + if (byRef) + nativePtr = Unsafe.Read(nativePtr.ToPointer()); + managedValue = nativePtr == IntPtr.Zero ? null : Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); } internal static void ToManagedArray(ref T[] managedValue, IntPtr nativePtr, bool byRef) { if (byRef) - nativePtr = Marshal.ReadIntPtr(nativePtr); + nativePtr = Unsafe.Read(nativePtr.ToPointer()); if (nativePtr != IntPtr.Zero) { @@ -814,56 +1028,64 @@ namespace FlaxEngine.Interop managedValue = null; } + + internal static void ToNativeString(ref string managedValue, IntPtr nativePtr) + { + Unsafe.Write(nativePtr.ToPointer(), ManagedString.ToNativeWeak(managedValue)); + } + + internal static void ToNativeType(ref Type managedValue, IntPtr nativePtr) + { + Unsafe.Write(nativePtr.ToPointer(), managedValue != null ? ManagedHandle.ToIntPtr(GetTypeGCHandle(managedValue)) : IntPtr.Zero); + } + + internal static void ToNativePointer(ref T managedValue, IntPtr nativePtr) + { + IntPtr managedPtr; + if (Pointer.Unbox(managedValue) == null) + managedPtr = IntPtr.Zero; + else if (managedValue is FlaxEngine.Object obj) + managedPtr = FlaxEngine.Object.GetUnmanagedPtr(obj); + else + managedPtr = ManagedHandle.ToIntPtr(managedValue, GCHandleType.Weak); + Unsafe.Write(nativePtr.ToPointer(), managedPtr); + } + + internal static void ToNativeArray(ref T managedValue, IntPtr nativePtr) + { + IntPtr managedPtr; + if (managedValue == null) + managedPtr = IntPtr.Zero; + else + { + Type type = typeof(T); + var elementType = type.GetElementType(); + var arr = Unsafe.As(managedValue); + var marshalledType = ArrayFactory.GetMarshalledType(elementType); + ManagedArray managedArray; + if (marshalledType == elementType) + managedArray = ManagedArray.WrapNewArray(arr, type); + else if (elementType.IsValueType) + { + // Convert array of custom structures into internal native layout + managedArray = ManagedArray.AllocateNewArray(arr.Length, type, marshalledType); + IntPtr managedArrayPtr = managedArray.Pointer; + for (int i = 0; i < arr.Length; i++) + { + MarshalToNative(arr.GetValue(i), managedArrayPtr, elementType); + managedArrayPtr += managedArray.ElementSize; + } + } + else + managedArray = ManagedArrayToGCHandleWrappedArray(arr); + managedPtr = ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak); + } + Unsafe.Write(nativePtr.ToPointer(), managedPtr); + } + internal static void ToNative(ref T managedValue, IntPtr nativePtr) { - Type type = typeof(T); - - IntPtr managedPtr; - if (type == typeof(string)) - managedPtr = ManagedString.ToNativeWeak(managedValue as string); - else if (type.IsPointer) - { - if (Pointer.Unbox(managedValue) == null) - managedPtr = IntPtr.Zero; - else if (managedValue is FlaxEngine.Object flaxObj) - managedPtr = FlaxEngine.Object.GetUnmanagedPtr(flaxObj); - else - managedPtr = ManagedHandle.ToIntPtr(managedValue, GCHandleType.Weak); - } - else if (type == typeof(Type)) - managedPtr = managedValue != null ? ManagedHandle.ToIntPtr(GetTypeGCHandle((Type)(object)managedValue)) : IntPtr.Zero; - else if (type.IsArray) - { - if (managedValue == null) - managedPtr = IntPtr.Zero; - else - { - var elementType = type.GetElementType(); - var arr = Unsafe.As(managedValue); - var marshalledType = ArrayFactory.GetMarshalledType(elementType); - ManagedArray managedArray; - if (marshalledType == elementType) - managedArray = ManagedArray.WrapNewArray(arr, type); - else if (elementType.IsValueType) - { - // Convert array of custom structures into internal native layout - managedArray = ManagedArray.AllocateNewArray(arr.Length, type, marshalledType); - IntPtr managedArrayPtr = managedArray.Pointer; - for (int i = 0; i < arr.Length; i++) - { - MarshalToNative(arr.GetValue(i), managedArrayPtr, elementType); - managedArrayPtr += managedArray.ElementSize; - } - } - else - managedArray = ManagedArrayToGCHandleWrappedArray(arr); - managedPtr = ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak); - } - } - else - managedPtr = managedValue != null ? ManagedHandle.ToIntPtr(managedValue, GCHandleType.Weak) : IntPtr.Zero; - - Unsafe.Write(nativePtr.ToPointer(), managedPtr); + Unsafe.Write(nativePtr.ToPointer(), managedValue != null ? ManagedHandle.ToIntPtr(managedValue, GCHandleType.Weak) : IntPtr.Zero); } } @@ -954,11 +1176,13 @@ namespace FlaxEngine.Interop { internal FieldInfo field; internal MarshalToNativeFieldDelegate toNativeMarshaller; + internal int fieldOffset; internal FieldHolder(FieldInfo field, Type type) { this.field = field; - toNativeMarshaller = GetToNativeFieldMarshallerDelegate(type); + toNativeMarshaller = GetToNativeFieldMarshallerDelegate(field, type); + fieldOffset = FieldHelper.GetFieldOffset(field, type); } } @@ -1244,7 +1468,7 @@ namespace FlaxEngine.Interop // Returned exception is the last parameter IntPtr exceptionPtr = nativePtrs[parameterTypes.Length]; if (exceptionPtr != IntPtr.Zero) - Marshal.WriteIntPtr(exceptionPtr, ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); + Unsafe.Write(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); return IntPtr.Zero; } return returnValue; @@ -1266,7 +1490,7 @@ namespace FlaxEngine.Interop if (type.IsByRef) { // References use indirection to support value returning - nativePtr = Marshal.ReadIntPtr(nativePtr); + nativePtr = Unsafe.Read(nativePtr.ToPointer()); type = elementType; } if (type.IsArray) @@ -1286,7 +1510,7 @@ namespace FlaxEngine.Interop // Returned exception is the last parameter IntPtr exceptionPtr = nativePtrs[numParams]; if (exceptionPtr != IntPtr.Zero) - Marshal.WriteIntPtr(exceptionPtr, ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); + Unsafe.Write(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); return IntPtr.Zero; } @@ -1300,11 +1524,11 @@ namespace FlaxEngine.Interop { type = type.GetElementType(); if (managed == null) - Marshal.WriteIntPtr(nativePtr, IntPtr.Zero); + Unsafe.Write(nativePtr.ToPointer(), IntPtr.Zero); else if (type.IsArray) MarshalToNative(managed, nativePtr, type); else - Marshal.WriteIntPtr(nativePtr, ManagedHandle.ToIntPtr(ManagedHandle.Alloc(managed, GCHandleType.Weak))); + Unsafe.Write(nativePtr.ToPointer(), ManagedHandle.ToIntPtr(ManagedHandle.Alloc(managed, GCHandleType.Weak))); } } From 53b1d0dd857d3b70d993b5dd7a57f41362a43cc2 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 4 Aug 2023 21:39:17 +0300 Subject: [PATCH 004/188] Optimize ScriptingObject managed initialization --- Source/Engine/Engine/NativeInterop.Invoker.cs | 12 +- .../Engine/NativeInterop.Marshallers.cs | 4 +- .../Engine/Engine/NativeInterop.Unmanaged.cs | 246 +++++++++++------- Source/Engine/Engine/NativeInterop.cs | 178 +++++++++++-- .../Engine/Scripting/ManagedCLR/MAssembly.h | 9 + Source/Engine/Scripting/ManagedCLR/MCore.cpp | 12 + Source/Engine/Scripting/ManagedCLR/MCore.h | 9 + Source/Engine/Scripting/ManagedCLR/MField.h | 13 +- Source/Engine/Scripting/Object.cs | 4 +- Source/Engine/Scripting/Runtime/DotNet.cpp | 104 +++++--- Source/Engine/Scripting/Runtime/Mono.cpp | 9 + Source/Engine/Scripting/Runtime/None.cpp | 9 + Source/Engine/Scripting/ScriptingObject.cpp | 96 ++++--- Source/Engine/Scripting/ScriptingObject.h | 7 + 14 files changed, 507 insertions(+), 205 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Invoker.cs b/Source/Engine/Engine/NativeInterop.Invoker.cs index 2fc54597c..3b5db40e9 100644 --- a/Source/Engine/Engine/NativeInterop.Invoker.cs +++ b/Source/Engine/Engine/NativeInterop.Invoker.cs @@ -31,7 +31,7 @@ namespace FlaxEngine.Interop if (typeof(TRet) == typeof(bool)) return (bool)(object)returnValue ? boolTruePtr : boolFalsePtr; if (typeof(TRet) == typeof(Type)) - return ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnValue))); + return ManagedHandle.ToIntPtr(GetTypeManagedHandle(Unsafe.As(returnValue))); if (typeof(TRet).IsArray) { var elementType = typeof(TRet).GetElementType(); @@ -52,8 +52,8 @@ namespace FlaxEngine.Interop return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnObject); if (returnType == typeof(bool)) return (bool)returnObject ? boolTruePtr : boolFalsePtr; - if (returnType == typeof(Type)) - return ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnObject))); + if (returnType == typeof(Type) || returnType == typeof(TypeHolder)) + return ManagedHandle.ToIntPtr(GetTypeManagedHandle(Unsafe.As(returnObject))); if (returnType.IsArray && ArrayFactory.GetMarshalledType(returnType.GetElementType()) == returnType.GetElementType()) return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As(returnObject)), GCHandleType.Weak); if (returnType.IsArray) @@ -72,7 +72,7 @@ namespace FlaxEngine.Interop if (typeof(TRet) == typeof(ManagedHandle)) return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnValue); if (typeof(TRet) == typeof(Type)) - return ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnValue))); + return ManagedHandle.ToIntPtr(GetTypeManagedHandle(Unsafe.As(returnValue))); if (typeof(TRet).IsArray) { var elementType = typeof(TRet).GetElementType(); @@ -108,8 +108,8 @@ namespace FlaxEngine.Interop return (IntPtr)(object)returnObject; if (returnType == typeof(ManagedHandle)) return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnObject); - if (returnType == typeof(Type)) - return returnObject != null ? ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnObject))) : IntPtr.Zero; + if (returnType == typeof(Type) || returnType == typeof(TypeHolder)) + return returnObject != null ? ManagedHandle.ToIntPtr(GetTypeManagedHandle(Unsafe.As(returnObject))) : IntPtr.Zero; if (returnType.IsArray) { var elementType = returnType.GetElementType(); diff --git a/Source/Engine/Engine/NativeInterop.Marshallers.cs b/Source/Engine/Engine/NativeInterop.Marshallers.cs index 2e3bc9183..ebfe4e61d 100644 --- a/Source/Engine/Engine/NativeInterop.Marshallers.cs +++ b/Source/Engine/Engine/NativeInterop.Marshallers.cs @@ -117,13 +117,13 @@ namespace FlaxEngine.Interop [CustomMarshaller(typeof(Type), MarshalMode.Default, typeof(SystemTypeMarshaller))] public static class SystemTypeMarshaller { - public static Type ConvertToManaged(IntPtr unmanaged) => Unsafe.As(ManagedHandleMarshaller.ConvertToManaged(unmanaged)); + public static Type ConvertToManaged(IntPtr unmanaged) => unmanaged != IntPtr.Zero ? Unsafe.As(ManagedHandleMarshaller.ConvertToManaged(unmanaged)).type : null; public static IntPtr ConvertToUnmanaged(Type managed) { if (managed == null) return IntPtr.Zero; - ManagedHandle handle = NativeInterop.GetTypeGCHandle(managed); + ManagedHandle handle = NativeInterop.GetTypeManagedHandle(managed); return ManagedHandle.ToIntPtr(handle); } diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index f6239dcf4..4c3cab9c5 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -19,6 +19,7 @@ namespace FlaxEngine.Interop internal struct NativeClassDefinitions { internal ManagedHandle typeHandle; + internal IntPtr nativePointer; internal IntPtr name; internal IntPtr fullname; internal IntPtr @namespace; @@ -40,6 +41,7 @@ namespace FlaxEngine.Interop internal IntPtr name; internal ManagedHandle fieldHandle; internal ManagedHandle fieldTypeHandle; + internal int fieldOffset; internal uint fieldAttributes; } @@ -139,6 +141,9 @@ namespace FlaxEngine.Interop unsafe partial class NativeInterop { + [LibraryImport("FlaxEngine", EntryPoint = "NativeInterop_CreateClass", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))] + internal static partial void NativeInterop_CreateClass(ref NativeClassDefinitions managedClass, ManagedHandle assemblyHandle); + internal enum MTypes : uint { End = 0x00, @@ -205,46 +210,8 @@ namespace FlaxEngine.Interop NativeMemory.AlignedFree(ptr); } - [UnmanagedCallersOnly] - internal static void GetManagedClasses(ManagedHandle assemblyHandle, NativeClassDefinitions** managedClasses, int* managedClassCount) + private static Assembly GetOwningAssembly(Type type) { - Assembly assembly = Unsafe.As(assemblyHandle.Target); - var assemblyTypes = GetAssemblyTypes(assembly); - - NativeClassDefinitions* arr = (NativeClassDefinitions*)NativeAlloc(assemblyTypes.Length, Unsafe.SizeOf()); - - for (int i = 0; i < assemblyTypes.Length; i++) - { - var type = assemblyTypes[i]; - IntPtr ptr = IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf() * i); - var managedClass = new NativeClassDefinitions - { - typeHandle = GetTypeGCHandle(type), - name = NativeAllocStringAnsi(type.Name), - fullname = NativeAllocStringAnsi(type.GetTypeName()), - @namespace = NativeAllocStringAnsi(type.Namespace ?? ""), - typeAttributes = (uint)type.Attributes, - }; - Unsafe.Write(ptr.ToPointer(), managedClass); - } - - *managedClasses = arr; - *managedClassCount = assemblyTypes.Length; - } - - [UnmanagedCallersOnly] - internal static void GetManagedClassFromType(ManagedHandle typeHandle, NativeClassDefinitions* managedClass, ManagedHandle* assemblyHandle) - { - Type type = Unsafe.As(typeHandle.Target); - *managedClass = new NativeClassDefinitions - { - typeHandle = GetTypeGCHandle(type), - name = NativeAllocStringAnsi(type.Name), - fullname = NativeAllocStringAnsi(type.GetTypeName()), - @namespace = NativeAllocStringAnsi(type.Namespace ?? ""), - typeAttributes = (uint)type.Attributes, - }; - Assembly assembly = null; if (type.IsGenericType && !type.Assembly.IsCollectible) { @@ -261,14 +228,87 @@ namespace FlaxEngine.Interop } if (assembly == null) assembly = type.Assembly; + return assembly; + } - *assemblyHandle = GetAssemblyHandle(assembly); + private static NativeClassDefinitions CreateNativeClassDefinitions(Type type, out ManagedHandle assemblyHandle) + { + assemblyHandle = GetAssemblyHandle(GetOwningAssembly(type)); + return CreateNativeClassDefinitions(type); + } + + private static NativeClassDefinitions CreateNativeClassDefinitions(Type type) + { + return new NativeClassDefinitions() + { + typeHandle = RegisterType(type).handle, + name = NativeAllocStringAnsi(type.Name), + fullname = NativeAllocStringAnsi(type.GetTypeName()), + @namespace = NativeAllocStringAnsi(type.Namespace ?? ""), + typeAttributes = (uint)type.Attributes, + }; + } + + private static NativeClassDefinitions CreateNativeClassDefinitions(Type type, ManagedHandle typeHandle, out ManagedHandle assemblyHandle) + { + assemblyHandle = GetAssemblyHandle(GetOwningAssembly(type)); + return new NativeClassDefinitions() + { + typeHandle = typeHandle, + name = NativeAllocStringAnsi(type.Name), + fullname = NativeAllocStringAnsi(type.GetTypeName()), + @namespace = NativeAllocStringAnsi(type.Namespace ?? ""), + typeAttributes = (uint)type.Attributes, + }; + } + + [UnmanagedCallersOnly] + internal static void GetManagedClasses(ManagedHandle assemblyHandle, NativeClassDefinitions** managedClasses, int* managedClassCount) + { + Assembly assembly = Unsafe.As(assemblyHandle.Target); + Type[] assemblyTypes = GetAssemblyTypes(assembly); + + *managedClasses = (NativeClassDefinitions*)NativeAlloc(assemblyTypes.Length, Unsafe.SizeOf()); + *managedClassCount = assemblyTypes.Length; + Span span = new Span(*managedClasses, assemblyTypes.Length); + for (int i = 0; i < assemblyTypes.Length; i++) + { + Type type = assemblyTypes[i]; + ref var managedClass = ref span[i]; + managedClass = CreateNativeClassDefinitions(type); + } + } + + [UnmanagedCallersOnly] + internal static void RegisterManagedClassNativePointers(NativeClassDefinitions** managedClasses, int managedClassCount) + { + Span span = new Span(Unsafe.Read(managedClasses).ToPointer(), managedClassCount); + foreach (ref NativeClassDefinitions managedClass in span) + { + TypeHolder typeHolder = Unsafe.As(managedClass.typeHandle.Target); + typeHolder.managedClassPointer = managedClass.nativePointer; + } + } + + [UnmanagedCallersOnly] + internal static void GetManagedClassFromType(ManagedHandle typeHandle, NativeClassDefinitions* managedClass, ManagedHandle* assemblyHandle) + { + Type type = Unsafe.As(typeHandle.Target); + *managedClass = CreateNativeClassDefinitions(type, out ManagedHandle handle); + *assemblyHandle = handle; + } + + private static void RegisterNativeClassFromType(TypeHolder typeHolder, ManagedHandle typeHandle) + { + NativeClassDefinitions managedClass = CreateNativeClassDefinitions(typeHolder.type, typeHandle, out ManagedHandle assemblyHandle); + NativeInterop_CreateClass(ref managedClass, assemblyHandle); + typeHolder.managedClassPointer = managedClass.nativePointer; } [UnmanagedCallersOnly] internal static void GetClassMethods(ManagedHandle typeHandle, NativeMethodDefinitions** classMethods, int* classMethodsCount) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); var methods = new List(); var staticMethods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); @@ -296,7 +336,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static void GetClassFields(ManagedHandle typeHandle, NativeFieldDefinitions** classFields, int* classFieldsCount) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); NativeFieldDefinitions* arr = (NativeFieldDefinitions*)NativeAlloc(fields.Length, Unsafe.SizeOf()); @@ -318,7 +358,8 @@ namespace FlaxEngine.Interop { name = NativeAllocStringAnsi(fieldHolder.field.Name), fieldHandle = fieldHandle, - fieldTypeHandle = GetTypeGCHandle(fieldHolder.field.FieldType), + fieldTypeHandle = GetTypeManagedHandle(fieldHolder.field.FieldType), + fieldOffset = fieldHolder.fieldOffset, fieldAttributes = (uint)fieldHolder.field.Attributes, }; Unsafe.Write(IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf() * i).ToPointer(), classField); @@ -330,7 +371,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static void GetClassProperties(ManagedHandle typeHandle, NativePropertyDefinitions** classProperties, int* classPropertiesCount) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); var arr = (NativePropertyDefinitions*)NativeAlloc(properties.Length, Unsafe.SizeOf()); @@ -364,7 +405,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static void GetClassAttributes(ManagedHandle typeHandle, ManagedHandle** classAttributes, int* classAttributesCount) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); object[] attributeValues = type.GetCustomAttributes(false); ManagedHandle* arr = (ManagedHandle*)NativeAlloc(attributeValues.Length, Unsafe.SizeOf()); @@ -384,13 +425,13 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static ManagedHandle GetCustomAttribute(ManagedHandle typeHandle, ManagedHandle attributeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); var attributes = type.GetCustomAttributes(false); object attrib; if (attributeHandle.IsAllocated) { // Check for certain attribute type - Type attributeType = Unsafe.As(attributeHandle.Target); + Type attributeType = Unsafe.As(attributeHandle.Target); attrib = attributes.FirstOrDefault(x => x.GetType() == attributeType); } else @@ -413,7 +454,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static void GetClassInterfaces(ManagedHandle typeHandle, IntPtr* classInterfaces, int* classInterfacesCount) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); Type[] interfaces = type.GetInterfaces(); // Match mono_class_get_interfaces which doesn't return interfaces from base class @@ -465,7 +506,7 @@ namespace FlaxEngine.Interop IntPtr arr = (IntPtr)NativeAlloc(interfaces.Length, IntPtr.Size); for (int i = 0; i < interfaces.Length; i++) { - ManagedHandle handle = GetTypeGCHandle(interfaces[i]); + ManagedHandle handle = GetTypeManagedHandle(interfaces[i]); Unsafe.Write(IntPtr.Add(arr, IntPtr.Size * i).ToPointer(), handle); } *classInterfaces = arr; @@ -477,7 +518,7 @@ namespace FlaxEngine.Interop { MethodHolder methodHolder = Unsafe.As(methodHandle.Target); Type returnType = methodHolder.returnType; - return GetTypeGCHandle(returnType); + return GetTypeManagedHandle(returnType); } [UnmanagedCallersOnly] @@ -488,7 +529,7 @@ namespace FlaxEngine.Interop IntPtr arr = (IntPtr)NativeAlloc(methodHolder.parameterTypes.Length, IntPtr.Size); for (int i = 0; i < methodHolder.parameterTypes.Length; i++) { - ManagedHandle typeHandle = GetTypeGCHandle(methodHolder.parameterTypes[i]); + ManagedHandle typeHandle = GetTypeManagedHandle(methodHolder.parameterTypes[i]); Unsafe.Write(IntPtr.Add(new IntPtr(arr), IntPtr.Size * i).ToPointer(), typeHandle); } *typeHandles = arr; @@ -509,22 +550,15 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static ManagedHandle NewObject(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); - if (type.IsAbstract) - { - // Dotnet doesn't allow to instantiate abstract type thus allow to use generated mock class usage (eg. for Script or GPUResource) for generated abstract types - var abstractWrapper = type.GetNestedType("AbstractWrapper", BindingFlags.NonPublic); - if (abstractWrapper != null) - type = abstractWrapper; - } - object value = RuntimeHelpers.GetUninitializedObject(type); + TypeHolder typeHolder = Unsafe.As(typeHandle.Target); + object value = typeHolder.CreateObject(); return ManagedHandle.Alloc(value); } [UnmanagedCallersOnly] internal static ManagedHandle NewArray(ManagedHandle typeHandle, long size) { - Type elementType = Unsafe.As(typeHandle.Target); + Type elementType = Unsafe.As(typeHandle.Target); Type marshalledType = ArrayFactory.GetMarshalledType(elementType); Type arrayType = elementType.MakeArrayType(); if (marshalledType.IsValueType) @@ -543,9 +577,9 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static ManagedHandle GetArrayTypeFromElementType(ManagedHandle elementTypeHandle) { - Type elementType = Unsafe.As(elementTypeHandle.Target); + Type elementType = Unsafe.As(elementTypeHandle.Target); Type classType = elementType.MakeArrayType(); - return GetTypeGCHandle(classType); + return GetTypeManagedHandle(classType); } [UnmanagedCallersOnly] @@ -593,7 +627,7 @@ namespace FlaxEngine.Interop Type classType = obj.GetType(); if (classType == typeof(ManagedArray)) classType = ((ManagedArray)obj).ArrayType; - return GetTypeGCHandle(classType); + return GetTypeManagedHandle(classType); } [UnmanagedCallersOnly] @@ -634,7 +668,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static ManagedHandle BoxValue(ManagedHandle typeHandle, IntPtr valuePtr) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); object value = MarshalToManaged(valuePtr, type); return ManagedHandle.Alloc(value, GCHandleType.Weak); } @@ -679,6 +713,14 @@ namespace FlaxEngine.Interop } } + [UnmanagedCallersOnly] + internal static IntPtr GetObjectClass(ManagedHandle objectHandle) + { + object obj = objectHandle.Target; + TypeHolder typeHolder = GetTypeHolder(obj.GetType()); + return typeHolder.managedClassPointer; + } + [UnmanagedCallersOnly] internal static IntPtr InvokeMethod(ManagedHandle instanceHandle, ManagedHandle methodHandle, IntPtr paramPtr, IntPtr exceptionPtr) { @@ -792,7 +834,7 @@ namespace FlaxEngine.Interop internal static int FieldGetOffset(ManagedHandle fieldHandle) { FieldHolder field = Unsafe.As(fieldHandle.Target); - return (int)Marshal.OffsetOf(field.field.DeclaringType, field.field.Name); + return field.fieldOffset; } [UnmanagedCallersOnly] @@ -861,7 +903,15 @@ namespace FlaxEngine.Interop } [UnmanagedCallersOnly] - internal static ManagedHandle LoadAssemblyImage(IntPtr assemblyPathPtr, IntPtr* assemblyName, IntPtr* assemblyFullName) + internal static void GetAssemblyName(ManagedHandle assemblyHandle, IntPtr* assemblyName, IntPtr* assemblyFullName) + { + Assembly assembly = Unsafe.As(assemblyHandle.Target); + *assemblyName = NativeAllocStringAnsi(assembly.GetName().Name); + *assemblyFullName = NativeAllocStringAnsi(assembly.FullName); + } + + [UnmanagedCallersOnly] + internal static ManagedHandle LoadAssemblyImage(IntPtr assemblyPathPtr) { if (!firstAssemblyLoaded) { @@ -869,8 +919,6 @@ namespace FlaxEngine.Interop firstAssemblyLoaded = true; Assembly flaxEngineAssembly = AssemblyLoadContext.Default.Assemblies.First(x => x.GetName().Name == "FlaxEngine.CSharp"); - *assemblyName = NativeAllocStringAnsi(flaxEngineAssembly.GetName().Name); - *assemblyFullName = NativeAllocStringAnsi(flaxEngineAssembly.FullName); return GetAssemblyHandle(flaxEngineAssembly); } @@ -903,21 +951,16 @@ namespace FlaxEngine.Interop // Assemblies loaded via streams have no Location: https://github.com/dotnet/runtime/issues/12822 AssemblyLocations.Add(assembly.FullName, assemblyPath); - *assemblyName = NativeAllocStringAnsi(assembly.GetName().Name); - *assemblyFullName = NativeAllocStringAnsi(assembly.FullName); return GetAssemblyHandle(assembly); } [UnmanagedCallersOnly] - internal static ManagedHandle GetAssemblyByName(IntPtr namePtr, IntPtr* assemblyName, IntPtr* assemblyFullName) + internal static ManagedHandle GetAssemblyByName(IntPtr namePtr) { string name = Marshal.PtrToStringAnsi(namePtr); Assembly assembly = Utils.GetAssemblies().FirstOrDefault(x => x.GetName().Name == name); if (assembly == null) return new ManagedHandle(); - - *assemblyName = NativeAllocStringAnsi(assembly.GetName().Name); - *assemblyFullName = NativeAllocStringAnsi(assembly.FullName); return GetAssemblyHandle(assembly); } @@ -956,9 +999,9 @@ namespace FlaxEngine.Interop // Release all references in collectible ALC cachedDelegatesCollectible.Clear(); - foreach (var pair in typeHandleCacheCollectible) - pair.Value.Free(); - typeHandleCacheCollectible.Clear(); + foreach (var pair in managedTypesCollectible) + pair.Value.handle.Free(); + managedTypesCollectible.Clear(); foreach (var handle in methodHandlesCollectible) handle.Free(); methodHandlesCollectible.Clear(); @@ -988,7 +1031,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static int NativeSizeOf(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); Type nativeType = GetInternalType(type) ?? type; if (nativeType == typeof(Version)) nativeType = typeof(NativeVersion); @@ -1006,8 +1049,8 @@ namespace FlaxEngine.Interop if (typeHandle == otherTypeHandle) return 1; - Type type = Unsafe.As(typeHandle.Target); - Type otherType = Unsafe.As(otherTypeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); + Type otherType = Unsafe.As(otherTypeHandle.Target); if (type == otherType) return 1; @@ -1024,37 +1067,39 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static byte TypeIsAssignableFrom(ManagedHandle typeHandle, ManagedHandle otherTypeHandle) { - Type type = Unsafe.As(typeHandle.Target); - Type otherType = Unsafe.As(otherTypeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); + Type otherType = Unsafe.As(otherTypeHandle.Target); return (byte)(type.IsAssignableFrom(otherType) ? 1 : 0); } [UnmanagedCallersOnly] internal static byte TypeIsValueType(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); return (byte)(type.IsValueType ? 1 : 0); } [UnmanagedCallersOnly] internal static byte TypeIsEnum(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); return (byte)(type.IsEnum ? 1 : 0); } [UnmanagedCallersOnly] - internal static ManagedHandle GetClassParent(ManagedHandle typeHandle) + internal static IntPtr GetClassParent(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); - return GetTypeGCHandle(type.BaseType); + TypeHolder typeHolder = Unsafe.As(typeHandle.Target); + TypeHolder baseTypeHolder = GetTypeHolder(typeHolder.type.BaseType); + return baseTypeHolder.managedClassPointer; } [UnmanagedCallersOnly] - internal static ManagedHandle GetElementClass(ManagedHandle typeHandle) + internal static IntPtr GetElementClass(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); - return GetTypeGCHandle(type.GetElementType()); + TypeHolder typeHolder = Unsafe.As(typeHandle.Target); + TypeHolder elementTypeHolder = GetTypeHolder(typeHolder.type.GetElementType()); + return elementTypeHolder.managedClassPointer; } [UnmanagedCallersOnly] @@ -1145,32 +1190,35 @@ namespace FlaxEngine.Interop } [UnmanagedCallersOnly] - internal static ManagedHandle GetTypeClass(ManagedHandle typeHandle) + internal static IntPtr GetTypeClass(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); - if (type.IsByRef) - type = type.GetElementType(); // Drop reference type (&) to get actual value type - return GetTypeGCHandle(type); + TypeHolder typeHolder = Unsafe.As(typeHandle.Target); + if (typeHolder.type.IsByRef) + { + // Drop reference type (&) to get actual value type + return GetTypeHolder(typeHolder.type.GetElementType()).managedClassPointer; + } + return typeHolder.managedClassPointer; } [UnmanagedCallersOnly] internal static bool GetTypeIsPointer(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); return type.IsPointer; } [UnmanagedCallersOnly] internal static bool GetTypeIsReference(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); return type.IsByRef; } [UnmanagedCallersOnly] internal static uint GetTypeMTypesEnum(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); if (type.IsByRef) type = type.GetElementType(); // Drop reference type (&) to get actual value type MTypes monoType; diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 739329ed8..6c0388d53 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -36,12 +36,12 @@ namespace FlaxEngine.Interop private static List methodHandles = new(); private static ConcurrentDictionary cachedDelegates = new(); - private static Dictionary typeHandleCache = new(); + private static Dictionary managedTypes = new(new TypeComparer()); private static List fieldHandleCache = new(); #if FLAX_EDITOR private static List methodHandlesCollectible = new(); private static ConcurrentDictionary cachedDelegatesCollectible = new(); - private static Dictionary typeHandleCacheCollectible = new(); + private static Dictionary managedTypesCollectible = new(new TypeComparer()); private static List fieldHandleCacheCollectible = new(); #endif private static Dictionary classAttributesCacheCollectible = new(); @@ -117,6 +117,38 @@ namespace FlaxEngine.Interop { } + // Cache offsets to frequently accessed fields of FlaxEngine.Object + private static int unmanagedPtrFieldOffset = IntPtr.Size + (Unsafe.Read((typeof(FlaxEngine.Object).GetField("__unmanagedPtr", BindingFlags.Instance | BindingFlags.NonPublic).FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF); + private static int internalIdFieldOffset = IntPtr.Size + (Unsafe.Read((typeof(FlaxEngine.Object).GetField("__internalId", BindingFlags.Instance | BindingFlags.NonPublic).FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF); + + [UnmanagedCallersOnly] + internal static void ScriptingObjectSetInternalValues(ManagedHandle objectHandle, IntPtr unmanagedPtr, IntPtr idPtr) + { + object obj = objectHandle.Target; + if (obj is not Object) + return; + + { + ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(unmanagedPtrFieldOffset, ref obj); + fieldRef = unmanagedPtr; + } + + if (idPtr != IntPtr.Zero) + { + ref Guid nativeId = ref Unsafe.AsRef(idPtr.ToPointer()); + ref Guid fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(internalIdFieldOffset, ref obj); + fieldRef = nativeId; + } + } + + [UnmanagedCallersOnly] + internal static ManagedHandle ScriptingObjectCreate(ManagedHandle typeHandle, IntPtr unmanagedPtr, IntPtr idPtr) + { + TypeHolder typeHolder = Unsafe.As(typeHandle.Target); + object obj = typeHolder.CreateScriptingObject(unmanagedPtr, idPtr); + return ManagedHandle.Alloc(obj); + } + internal static void* NativeAlloc(int byteCount) { return NativeMemory.AlignedAlloc((UIntPtr)byteCount, 16); @@ -1036,7 +1068,7 @@ namespace FlaxEngine.Interop internal static void ToNativeType(ref Type managedValue, IntPtr nativePtr) { - Unsafe.Write(nativePtr.ToPointer(), managedValue != null ? ManagedHandle.ToIntPtr(GetTypeGCHandle(managedValue)) : IntPtr.Zero); + Unsafe.Write(nativePtr.ToPointer(), managedValue != null ? ManagedHandle.ToIntPtr(GetTypeManagedHandle(managedValue)) : IntPtr.Zero); } internal static void ToNativePointer(ref T managedValue, IntPtr nativePtr) @@ -1186,6 +1218,72 @@ namespace FlaxEngine.Interop } } + internal class TypeComparer : IEqualityComparer + { + public bool Equals(Type x, Type y) => x == y; + public int GetHashCode(Type obj) => obj.GetHashCode(); + } + + internal class TypeHolder + { + internal Type type; + internal Type wrappedType; + internal ConstructorInfo ctor; + internal IntPtr managedClassPointer; // MClass* + + internal TypeHolder(Type type) + { + this.type = type; + wrappedType = type; + + if (type.IsAbstract) + { + // Dotnet doesn't allow to instantiate abstract type thus allow to use generated mock class usage (eg. for Script or GPUResource) for generated abstract types + var abstractWrapper = type.GetNestedType("AbstractWrapper", BindingFlags.NonPublic); + if (abstractWrapper != null) + wrappedType = abstractWrapper; + } + + ctor = wrappedType.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); + } + + internal object CreateObject() + { + return RuntimeHelpers.GetUninitializedObject(wrappedType); + } + + internal object CreateScriptingObject(IntPtr unmanagedPtr, IntPtr idPtr) + { + object obj = CreateObject(); + if (obj is Object) + { + { + ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(unmanagedPtrFieldOffset, ref obj); + fieldRef = unmanagedPtr; + } + + if (idPtr != IntPtr.Zero) + { + ref Guid nativeId = ref Unsafe.AsRef(idPtr.ToPointer()); + ref Guid fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(internalIdFieldOffset, ref obj); + fieldRef = nativeId; + } + } + + if (ctor != null) + ctor.Invoke(obj, null); + else + Debug.LogException(new Exception($"Missing empty constructor in type '{wrappedType}'.")); + + return obj; + } + + public static implicit operator Type(TypeHolder holder) => holder?.type ?? null; + public bool Equals(TypeHolder other) => type == other.type; + public bool Equals(Type other) => type == other; + public override int GetHashCode() => type.GetHashCode(); + } + internal static class ArrayFactory { private delegate Array CreateArrayDelegate(long size); @@ -1302,29 +1400,77 @@ namespace FlaxEngine.Interop return types; } - /// - /// Returns a static ManagedHandle for given Type, and caches it if needed. - /// - internal static ManagedHandle GetTypeGCHandle(Type type) + internal static TypeHolder GetTypeHolder(Type type) { - if (typeHandleCache.TryGetValue(type, out ManagedHandle handle)) - return handle; + if (managedTypes.TryGetValue(type, out (TypeHolder typeHolder, ManagedHandle handle) tuple)) + return tuple.typeHolder; #if FLAX_EDITOR - if (typeHandleCacheCollectible.TryGetValue(type, out handle)) - return handle; + if (managedTypesCollectible.TryGetValue(type, out tuple)) + return tuple.typeHolder; #endif + return RegisterType(type, true).typeHolder; + } - handle = ManagedHandle.Alloc(type); + internal static (TypeHolder typeHolder, ManagedHandle handle) GetTypeHolderAndManagedHandle(Type type) + { + if (managedTypes.TryGetValue(type, out (TypeHolder typeHolder, ManagedHandle handle) tuple)) + return tuple; #if FLAX_EDITOR - if (type.IsCollectible) // check if generic parameters are also collectible? - typeHandleCacheCollectible.Add(type, handle); + if (managedTypesCollectible.TryGetValue(type, out tuple)) + return tuple; +#endif + return RegisterType(type, true); + } + + /// + /// Returns a static ManagedHandle to TypeHolder for given Type, and caches it if needed. + /// + internal static ManagedHandle GetTypeManagedHandle(Type type) + { + if (managedTypes.TryGetValue(type, out (TypeHolder typeHolder, ManagedHandle handle) tuple)) + return tuple.handle; +#if FLAX_EDITOR + if (managedTypesCollectible.TryGetValue(type, out tuple)) + return tuple.handle; +#endif + return RegisterType(type, true).handle; + } + + internal static (TypeHolder typeHolder, ManagedHandle handle) RegisterType(Type type, bool registerNativeType = false) + { + // TODO: should this strip by-ref? + + (TypeHolder typeHolder, ManagedHandle handle) tuple; + tuple.typeHolder = new TypeHolder(type); + tuple.handle = ManagedHandle.Alloc(tuple.typeHolder); +#if FLAX_EDITOR + bool isCollectible = type.IsCollectible; + if (!isCollectible && type.IsGenericType && !type.Assembly.IsCollectible) + { + // The owning assembly of a generic type with type arguments referencing + // collectible assemblies must be one of the collectible assemblies. + foreach (var genericType in type.GetGenericArguments()) + { + if (genericType.Assembly.IsCollectible) + { + isCollectible = true; + break; + } + } + } + + if (isCollectible) + managedTypesCollectible.Add(type, tuple); else #endif { - typeHandleCache.Add(type, handle); + managedTypes.Add(type, tuple); } - return handle; + if (registerNativeType) + RegisterNativeClassFromType(tuple.typeHolder, tuple.handle); + + return tuple; } internal static int GetTypeSize(Type type) diff --git a/Source/Engine/Scripting/ManagedCLR/MAssembly.h b/Source/Engine/Scripting/ManagedCLR/MAssembly.h index e0952f9f6..3436df567 100644 --- a/Source/Engine/Scripting/ManagedCLR/MAssembly.h +++ b/Source/Engine/Scripting/ManagedCLR/MAssembly.h @@ -51,6 +51,15 @@ public: /// The assembly name. MAssembly(MDomain* domain, const StringAnsiView& name); + /// + /// Initializes a new instance of the class. + /// + /// The assembly domain. + /// The assembly name. + /// The assembly full name. + /// The managed handle of the assembly. + MAssembly(MDomain* domain, const StringAnsiView& name, const StringAnsiView& fullname, void* handle); + /// /// Finalizes an instance of the class. /// diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.cpp index 86b906f81..d9cc6f863 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MCore.cpp @@ -48,6 +48,18 @@ MAssembly::MAssembly(MDomain* domain, const StringAnsiView& name) { } +MAssembly::MAssembly(MDomain* domain, const StringAnsiView& name, const StringAnsiView& fullname, void* handle) + : _domain(domain) + , _isLoaded(false) + , _isLoading(false) + , _hasCachedClasses(false) + , _reloadCount(0) + , _name(name) + , _fullname(fullname) + , _handle(handle) +{ +} + MAssembly::~MAssembly() { Unload(); diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.h b/Source/Engine/Scripting/ManagedCLR/MCore.h index e1de3c207..2f4a94bde 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.h +++ b/Source/Engine/Scripting/ManagedCLR/MCore.h @@ -189,4 +189,13 @@ public: static MClass* Double; static MClass* String; }; + + /// + /// Utilities for ScriptingObject management. + /// + struct FLAXENGINE_API ScriptingObject + { + static void SetInternalValues(MObject* object, void* unmanagedPtr, const Guid* id); + static MObject* CreateScriptingObject(MClass* klass, void* unmanagedPtr, const Guid* id); + }; }; diff --git a/Source/Engine/Scripting/ManagedCLR/MField.h b/Source/Engine/Scripting/ManagedCLR/MField.h index 796de27f7..41545656d 100644 --- a/Source/Engine/Scripting/ManagedCLR/MField.h +++ b/Source/Engine/Scripting/ManagedCLR/MField.h @@ -19,6 +19,7 @@ protected: #elif USE_NETCORE void* _handle; void* _type; + int32 _fieldOffset; #endif MClass* _parentClass; @@ -35,7 +36,7 @@ public: #if USE_MONO explicit MField(MonoClassField* monoField, const char* name, MClass* parentClass); #elif USE_NETCORE - MField(MClass* parentClass, void* handle, const char* name, void* type, MFieldAttributes attributes); + MField(MClass* parentClass, void* handle, const char* name, void* type, int fieldOffset, MFieldAttributes attributes); #endif public: @@ -102,6 +103,16 @@ public: /// The return value of undefined type. void GetValue(MObject* instance, void* result) const; + /// + /// Retrieves value currently set in the field on the specified object instance. If field is static object instance can be null. + /// + /// + /// Value will be a pointer. + /// + /// The object of given type to get value from. + /// The return value of undefined type. + void GetValueReference(MObject* instance, void* result) const; + /// /// Retrieves value currently set in the field on the specified object instance. If field is static object instance can be null. If returned value is a value type it will be boxed. /// diff --git a/Source/Engine/Scripting/Object.cs b/Source/Engine/Scripting/Object.cs index 0180c0935..8af120eaa 100644 --- a/Source/Engine/Scripting/Object.cs +++ b/Source/Engine/Scripting/Object.cs @@ -48,7 +48,7 @@ namespace FlaxEngine // Construct missing native object if managed objects gets created in managed world if (__unmanagedPtr == IntPtr.Zero) { - Internal_ManagedInstanceCreated(this); + Internal_ManagedInstanceCreated(this, FlaxEngine.Interop.NativeInterop.GetTypeHolder(GetType()).managedClassPointer); if (__unmanagedPtr == IntPtr.Zero) throw new Exception($"Failed to create native instance for object of type {GetType().FullName} (assembly: {GetType().Assembly.FullName})."); } @@ -308,7 +308,7 @@ namespace FlaxEngine internal static partial Object Internal_Create2(string typeName); [LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_ManagedInstanceCreated", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))] - internal static partial void Internal_ManagedInstanceCreated(Object managedInstance); + internal static partial void Internal_ManagedInstanceCreated(Object managedInstance, IntPtr theKlass); [LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_ManagedInstanceDeleted", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))] internal static partial void Internal_ManagedInstanceDeleted(IntPtr nativeInstance); diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 36f1f16af..92cff843d 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -13,6 +13,7 @@ #include "Engine/Platform/Platform.h" #include "Engine/Platform/File.h" #include "Engine/Platform/FileSystem.h" +#include "Engine/Scripting/Internal/InternalCalls.h" #include "Engine/Scripting/ManagedCLR/MCore.h" #include "Engine/Scripting/ManagedCLR/MAssembly.h" #include "Engine/Scripting/ManagedCLR/MClass.h" @@ -214,6 +215,7 @@ void* GetCustomAttribute(const MClass* klass, const MClass* attributeClass); struct NativeClassDefinitions { void* typeHandle; + MClass* nativePointer; const char* name; const char* fullname; const char* namespace_; @@ -233,6 +235,7 @@ struct NativeFieldDefinitions const char* name; void* fieldHandle; void* fieldType; + int fieldOffset; MFieldAttributes fieldAttributes; }; @@ -341,8 +344,8 @@ void MCore::Object::Init(MObject* obj) MClass* MCore::Object::GetClass(MObject* obj) { ASSERT(obj); - MType* typeHandle = GetObjectType(obj); - return GetOrCreateClass(typeHandle); + static void* GetObjectClassPtr = GetStaticMethodPointer(TEXT("GetObjectClass")); + return (MClass*)CallStaticMethod(GetObjectClassPtr, obj); } MString* MCore::Object::ToString(MObject* obj) @@ -568,8 +571,7 @@ MObject* MCore::Exception::GetNotSupported(const char* msg) MClass* MCore::Type::GetClass(MType* type) { static void* GetTypeClassPtr = GetStaticMethodPointer(TEXT("GetTypeClass")); - type = (MType*)CallStaticMethod(GetTypeClassPtr, type); - return GetOrCreateClass(type); + return CallStaticMethod(GetTypeClassPtr, type); } MType* MCore::Type::GetElementType(MType* type) @@ -634,10 +636,16 @@ const MAssembly::ClassesDictionary& MAssembly::GetClasses() const MClass* klass = New(this, managedClass.typeHandle, managedClass.name, managedClass.fullname, managedClass.namespace_, managedClass.typeAttributes); _classes.Add(klass->GetFullName(), klass); + managedClass.nativePointer = klass; + MCore::GC::FreeMemory((void*)managedClasses[i].name); MCore::GC::FreeMemory((void*)managedClasses[i].fullname); MCore::GC::FreeMemory((void*)managedClasses[i].namespace_); } + + static void* RegisterManagedClassNativePointersPtr = GetStaticMethodPointer(TEXT("RegisterManagedClassNativePointers")); + CallStaticMethod(RegisterManagedClassNativePointersPtr, &managedClasses, classCount); + MCore::GC::FreeMemory(managedClasses); const auto endTime = DateTime::NowUTC(); @@ -652,6 +660,39 @@ const MAssembly::ClassesDictionary& MAssembly::GetClasses() const return _classes; } +void GetAssemblyName(void* assemblyHandle, StringAnsi& name, StringAnsi& fullname) +{ + static void* GetAssemblyNamePtr = GetStaticMethodPointer(TEXT("GetAssemblyName")); + const char* name_; + const char* fullname_; + CallStaticMethod(GetAssemblyNamePtr, assemblyHandle, &name_, &fullname_); + name = name_; + fullname = fullname_; + MCore::GC::FreeMemory((void*)name_); + MCore::GC::FreeMemory((void*)fullname_); +} + +DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* managedClass, void* assemblyHandle) +{ + MAssembly* assembly = GetAssembly(assemblyHandle); + if (assembly == nullptr) + { + StringAnsi assemblyName; + StringAnsi assemblyFullName; + GetAssemblyName(assemblyHandle, assemblyName, assemblyFullName); + + assembly = New(nullptr, assemblyName, assemblyFullName, assemblyHandle); + CachedAssemblyHandles.Add(assemblyHandle, assembly); + } + + MClass* klass = New(assembly, managedClass->typeHandle, managedClass->name, managedClass->fullname, managedClass->namespace_, managedClass->typeAttributes); + if (assembly != nullptr) + { + const_cast(assembly->GetClasses()).Add(klass->GetFullName(), klass); + } + managedClass->nativePointer = klass; +} + bool MAssembly::LoadCorlib() { if (IsLoaded()) @@ -671,14 +712,9 @@ bool MAssembly::LoadCorlib() // Load { - const char* name; - const char* fullname; static void* GetAssemblyByNamePtr = GetStaticMethodPointer(TEXT("GetAssemblyByName")); - _handle = CallStaticMethod(GetAssemblyByNamePtr, "System.Private.CoreLib", &name, &fullname); - _name = name; - _fullname = fullname; - MCore::GC::FreeMemory((void*)name); - MCore::GC::FreeMemory((void*)fullname); + _handle = CallStaticMethod(GetAssemblyByNamePtr, "System.Private.CoreLib"); + GetAssemblyName(_handle, _name, _fullname); } if (_handle == nullptr) { @@ -698,19 +734,14 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa // TODO: Use new hostfxr delegate load_assembly_bytes? (.NET 8+) // Open .Net assembly const StringAnsi assemblyPathAnsi = assemblyPath.ToStringAnsi(); - const char* name; - const char* fullname; static void* LoadAssemblyImagePtr = GetStaticMethodPointer(TEXT("LoadAssemblyImage")); - _handle = CallStaticMethod(LoadAssemblyImagePtr, assemblyPathAnsi.Get(), &name, &fullname); - _name = name; - _fullname = fullname; - MCore::GC::FreeMemory((void*)name); - MCore::GC::FreeMemory((void*)fullname); + _handle = CallStaticMethod(LoadAssemblyImagePtr, assemblyPathAnsi.Get()); if (_handle == nullptr) { Log::CLRInnerException(TEXT(".NET assembly image is invalid at ") + assemblyPath); return true; } + GetAssemblyName(_handle, _name, _fullname); CachedAssemblyHandles.Add(_handle, this); // Provide new path of hot-reloaded native library path for managed DllImport @@ -833,8 +864,7 @@ MType* MClass::GetType() const MClass* MClass::GetBaseClass() const { static void* GetClassParentPtr = GetStaticMethodPointer(TEXT("GetClassParent")); - MType* parentTypeHandle = CallStaticMethod(GetClassParentPtr, _handle); - return GetOrCreateClass(parentTypeHandle); + return CallStaticMethod(GetClassParentPtr, _handle); } bool MClass::IsSubClassOf(const MClass* klass, bool checkInterfaces) const @@ -869,8 +899,7 @@ uint32 MClass::GetInstanceSize() const MClass* MClass::GetElementClass() const { static void* GetElementClassPtr = GetStaticMethodPointer(TEXT("GetElementClass")); - MType* elementTypeHandle = CallStaticMethod(GetElementClassPtr, _handle); - return GetOrCreateClass(elementTypeHandle); + return CallStaticMethod(GetElementClassPtr, _handle); } MMethod* MClass::GetMethod(const char* name, int32 numParams) const @@ -930,7 +959,7 @@ const Array& MClass::GetFields() const for (int32 i = 0; i < numFields; i++) { NativeFieldDefinitions& definition = fields[i]; - MField* field = New(const_cast(this), definition.fieldHandle, definition.name, definition.fieldType, definition.fieldAttributes); + MField* field = New(const_cast(this), definition.fieldHandle, definition.name, definition.fieldType, definition.fieldOffset, definition.fieldAttributes); _fields.Add(field); MCore::GC::FreeMemory((void*)definition.name); @@ -1013,7 +1042,7 @@ bool MClass::HasAttribute(const MClass* monoClass) const bool MClass::HasAttribute() const { - return GetCustomAttribute(this, nullptr) != nullptr; + return !GetAttributes().IsEmpty(); } MObject* MClass::GetAttribute(const MClass* monoClass) const @@ -1123,11 +1152,12 @@ MException::~MException() Delete(InnerException); } -MField::MField(MClass* parentClass, void* handle, const char* name, void* type, MFieldAttributes attributes) +MField::MField(MClass* parentClass, void* handle, const char* name, void* type, int fieldOffset, MFieldAttributes attributes) : _handle(handle) , _type(type) , _parentClass(parentClass) , _name(name) + , _fieldOffset(fieldOffset) , _hasCachedAttributes(false) { switch (attributes & MFieldAttributes::FieldAccessMask) @@ -1163,8 +1193,7 @@ MType* MField::GetType() const int32 MField::GetOffset() const { - static void* FieldGetOffsetPtr = GetStaticMethodPointer(TEXT("FieldGetOffset")); - return CallStaticMethod(FieldGetOffsetPtr, _handle); + return _fieldOffset; } void MField::GetValue(MObject* instance, void* result) const @@ -1173,6 +1202,12 @@ void MField::GetValue(MObject* instance, void* result) const CallStaticMethod(FieldGetValuePtr, instance, _handle, result); } +void MField::GetValueReference(MObject* instance, void* result) const +{ + static void* FieldGetValueReferencePtr = GetStaticMethodPointer(TEXT("FieldGetValueReferenceWithOffset")); + CallStaticMethod(FieldGetValueReferencePtr, instance, _fieldOffset, result); +} + MObject* MField::GetValueBoxed(MObject* instance) const { static void* FieldGetValueBoxedPtr = GetStaticMethodPointer(TEXT("FieldGetValueBoxed")); @@ -1505,8 +1540,7 @@ void* GetCustomAttribute(const MClass* klass, const MClass* attributeClass) const Array& attributes = klass->GetAttributes(); for (MObject* attr : attributes) { - MType* typeHandle = GetObjectType(attr); - MClass* attrClass = GetOrCreateClass(typeHandle); + MClass* attrClass = MCore::Object::GetClass(attr); if (attrClass == attributeClass) return attr; } @@ -1661,6 +1695,18 @@ void* GetStaticMethodPointer(const String& methodName) return fun; } +void MCore::ScriptingObject::SetInternalValues(MObject* object, void* unmanagedPtr, const Guid* id) +{ + static void* ScriptingObjectSetInternalValuesPtr = GetStaticMethodPointer(TEXT("ScriptingObjectSetInternalValues")); + CallStaticMethod(ScriptingObjectSetInternalValuesPtr, object, unmanagedPtr, id); +} + +MObject* MCore::ScriptingObject::CreateScriptingObject(MClass* klass, void* unmanagedPtr, const Guid* id) +{ + static void* ScriptingObjectSetInternalValuesPtr = GetStaticMethodPointer(TEXT("ScriptingObjectCreate")); + return CallStaticMethod(ScriptingObjectSetInternalValuesPtr, klass->_handle, unmanagedPtr, id); +} + #elif DOTNET_HOST_MONO #ifdef USE_MONO_AOT_MODULE diff --git a/Source/Engine/Scripting/Runtime/Mono.cpp b/Source/Engine/Scripting/Runtime/Mono.cpp index 0a60db42f..d0e273bf7 100644 --- a/Source/Engine/Scripting/Runtime/Mono.cpp +++ b/Source/Engine/Scripting/Runtime/Mono.cpp @@ -2122,6 +2122,15 @@ const Array& MProperty::GetAttributes() const return _attributes; } +void MCore::ScriptingObject::SetInternalValues(MObject* object, void* unmanagedPtr, const Guid* id) +{ +} + +MObject* MCore::ScriptingObject::CreateScriptingObject(MClass* klass, void* unmanagedPtr, const Guid* id) +{ + return nullptr; +} + #endif #if USE_MONO && PLATFORM_WIN32 && !USE_MONO_DYNAMIC_LIB diff --git a/Source/Engine/Scripting/Runtime/None.cpp b/Source/Engine/Scripting/Runtime/None.cpp index ef9c118cf..df47c7683 100644 --- a/Source/Engine/Scripting/Runtime/None.cpp +++ b/Source/Engine/Scripting/Runtime/None.cpp @@ -560,4 +560,13 @@ const Array& MProperty::GetAttributes() const return _attributes; } +void MCore::ScriptingObject::SetInternalValues(MObject* object, void* unmanagedPtr, const Guid* id) +{ +} + +MObject* MCore::ScriptingObject::CreateScriptingObject(MClass* klass, void* unmanagedPtr, const Guid* id) +{ + return nullptr; +} + #endif diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index 9ece58f2a..f676b5962 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -180,10 +180,15 @@ ScriptingObject* ScriptingObject::ToNative(MObject* obj) #if USE_CSHARP if (obj) { +#if USE_MONO // TODO: cache the field offset from object and read directly from object pointer const auto ptrField = MCore::Object::GetClass(obj)->GetField(ScriptingObject_unmanagedPtr); CHECK_RETURN(ptrField, nullptr); ptrField->GetValue(obj, &ptr); +#else + static const MField* ptrField = MCore::Object::GetClass(obj)->GetField(ScriptingObject_unmanagedPtr); + ptrField->GetValueReference(obj, &ptr); +#endif } #endif return ptr; @@ -274,12 +279,7 @@ bool ScriptingObject::CreateManaged() if (const auto monoClass = GetClass()) { // Reset managed to unmanaged pointer - const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr); - if (monoUnmanagedPtrField) - { - void* param = nullptr; - monoUnmanagedPtrField->SetValue(managedInstance, ¶m); - } + SetInternalValues(monoClass, managedInstance, nullptr, nullptr); } MCore::GCHandle::Free(handle); return true; @@ -295,6 +295,32 @@ bool ScriptingObject::CreateManaged() #if USE_CSHARP +void ScriptingObject::SetInternalValues(MClass* monoClass, MObject* managedInstance, void* unmanagedPtr, const Guid* id) +{ +#if USE_MONO + // Set handle to unmanaged object + const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr); + if (monoUnmanagedPtrField) + { + const void* param = unmanagedPtr; + monoUnmanagedPtrField->SetValue(managedInstance, ¶m); + } + + if (id != nullptr) + { + // Set object id + const MField* monoIdField = monoClass->GetField(ScriptingObject_id); + if (monoIdField) + { + monoIdField->SetValue(managedInstance, (void*)id); + } + } + +#else + MCore::ScriptingObject::SetInternalValues(managedInstance, unmanagedPtr, id); +#endif +} + MObject* ScriptingObject::CreateManagedInternal() { // Get class @@ -308,6 +334,7 @@ MObject* ScriptingObject::CreateManagedInternal() // Ensure to have managed domain attached (this can be called from custom native thread, eg. content loader) MCore::Thread::Attach(); +#if USE_MONO // Allocate managed instance MObject* managedInstance = MCore::Object::New(monoClass); if (managedInstance == nullptr) @@ -315,23 +342,17 @@ MObject* ScriptingObject::CreateManagedInternal() LOG(Warning, "Failed to create new instance of the object of type {0}", String(monoClass->GetFullName())); } - // Set handle to unmanaged object - const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr); - if (monoUnmanagedPtrField) - { - const void* value = this; - monoUnmanagedPtrField->SetValue(managedInstance, &value); - } - - // Set object id - const MField* monoIdField = monoClass->GetField(ScriptingObject_id); - if (monoIdField) - { - monoIdField->SetValue(managedInstance, (void*)&_id); - } + SetInternalValues(monoClass, managedInstance, this, &_id); // Initialize managed instance (calls constructor) MCore::Object::Init(managedInstance); +#else + MObject* managedInstance = MCore::ScriptingObject::CreateScriptingObject(monoClass, this, &_id); + if (managedInstance == nullptr) + { + LOG(Warning, "Failed to create new instance of the object of type {0}", String(monoClass->GetFullName())); + } +#endif return managedInstance; } @@ -349,12 +370,7 @@ void ScriptingObject::DestroyManaged() { if (const auto monoClass = GetClass()) { - const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr); - if (monoUnmanagedPtrField) - { - void* param = nullptr; - monoUnmanagedPtrField->SetValue(managedInstance, ¶m); - } + SetInternalValues(monoClass, managedInstance, nullptr, nullptr); } } @@ -478,12 +494,7 @@ bool ManagedScriptingObject::CreateManaged() if (const auto monoClass = GetClass()) { // Reset managed to unmanaged pointer - const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr); - if (monoUnmanagedPtrField) - { - void* param = nullptr; - monoUnmanagedPtrField->SetValue(managedInstance, ¶m); - } + SetInternalValues(monoClass, managedInstance, nullptr, nullptr); } MCore::GCHandle::Free(handle); return true; @@ -605,10 +616,8 @@ DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_Create2(MString* typeNameObj) return managedInstance; } -DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceCreated(MObject* managedInstance) +DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceCreated(MObject* managedInstance, MClass* typeClass) { - MClass* typeClass = MCore::Object::GetClass(managedInstance); - // Get the assembly with that class auto module = ManagedBinaryModule::FindModule(typeClass); if (module == nullptr) @@ -646,21 +655,8 @@ DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceCreated(MObject* manage MClass* monoClass = obj->GetClass(); - // Set handle to unmanaged object - const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr); - if (monoUnmanagedPtrField) - { - const void* value = obj; - monoUnmanagedPtrField->SetValue(managedInstance, &value); - } - - // Set object id - const MField* monoIdField = monoClass->GetField(ScriptingObject_id); - if (monoIdField) - { - const Guid id = obj->GetID(); - monoIdField->SetValue(managedInstance, (void*)&id); - } + const Guid id = obj->GetID(); + ScriptingObject::SetInternalValues(monoClass, managedInstance, obj, &id); // Register object if (!obj->IsRegistered()) diff --git a/Source/Engine/Scripting/ScriptingObject.h b/Source/Engine/Scripting/ScriptingObject.h index ad72a31c3..0dfa80450 100644 --- a/Source/Engine/Scripting/ScriptingObject.h +++ b/Source/Engine/Scripting/ScriptingObject.h @@ -206,6 +206,13 @@ public: /// void UnregisterObject(); +#if USE_CSHARP + /// + /// Sets the internal values in managed object. + /// + static void SetInternalValues(MClass* monoClass, MObject* managedInstance, void* unmanagedPtr, const Guid* id); +#endif + protected: #if USE_CSHARP /// From 9ec7b09771d00bf722670cf246a6bb5ec865a691 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sun, 20 Aug 2023 20:31:45 +0300 Subject: [PATCH 005/188] Merge fixes --- Source/Engine/Engine/NativeInterop.Invoker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Engine/NativeInterop.Invoker.cs b/Source/Engine/Engine/NativeInterop.Invoker.cs index 1c76ff7e7..36d7f2e03 100644 --- a/Source/Engine/Engine/NativeInterop.Invoker.cs +++ b/Source/Engine/Engine/NativeInterop.Invoker.cs @@ -84,7 +84,7 @@ namespace FlaxEngine.Interop internal static IntPtr MarshalReturnValueType(ref Type returnValue) { - return returnValue != null ? ManagedHandle.ToIntPtr(GetTypeGCHandle(returnValue)) : IntPtr.Zero; + return returnValue != null ? ManagedHandle.ToIntPtr(GetTypeManagedHandle(returnValue)) : IntPtr.Zero; } internal static IntPtr MarshalReturnValueArray(ref TRet returnValue) From 6dd0957c4ad8f6650c3c4214ab95fb9a445b5d83 Mon Sep 17 00:00:00 2001 From: Crawcik Date: Thu, 14 Sep 2023 22:34:03 +0200 Subject: [PATCH 006/188] Adding better finding dotnet root location for mac&unix --- .../Flax.Build/Build/DotNet/DotNetSdk.cs | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs index d3a706a43..e61642379 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs @@ -192,18 +192,18 @@ namespace Flax.Build case TargetPlatform.Linux: { // TODO: Support /etc/dotnet/install_location - - // Detect custom RID in some distros - rid = Utilities.ReadProcessOutput("dotnet", "--info").Split('\n').FirstOrDefault(x => x.StartsWith(" RID:"), "").Replace("RID:", "").Trim(); + rid = ""; ridFallback = $"linux-{arch}"; - if (rid == ridFallback) - ridFallback = ""; if (string.IsNullOrEmpty(dotnetPath)) + dotnetPath ??= SearchForDotnetLocationLinux(); + + if (dotnetPath == null) { - dotnetPath = "/usr/lib/dotnet/"; - if (!Directory.Exists(dotnetPath)) - dotnetPath = "/usr/share/dotnet/"; + rid = Utilities.ReadProcessOutput("dotnet", "--info").Split('\n').FirstOrDefault(x => x.StartsWith(" RID:"), "").Replace("RID:", "").Trim(); + if (rid == ridFallback) + ridFallback = ""; } + break; } case TargetPlatform.Mac: @@ -211,7 +211,12 @@ namespace Flax.Build rid = $"osx-{arch}"; ridFallback = ""; if (string.IsNullOrEmpty(dotnetPath)) - dotnetPath = "/usr/local/share/dotnet/"; + { + if (Directory.Exists("/usr/local/share/dotnet")) // Officialy recommended dotnet location + dotnetPath = "/usr/local/share/dotnet"; + else if (Environment.GetEnvironmentVariable("PATH") is string globalBinPath) + dotnetPath = globalBinPath.Split(':').FirstOrDefault(x => File.Exists(Path.Combine(x, "dotnet"))); + } break; } default: throw new InvalidPlatformException(platform); @@ -441,5 +446,18 @@ namespace Flax.Build // TODO: reject 'future' versions like .Net 8? return versions.OrderByDescending(ParseVersion).FirstOrDefault(); } + + private static string SearchForDotnetLocationLinux() + { + if (File.Exists("/etc/dotnet/install_location")) // Officialy recommended dotnet location file + return File.ReadAllText("/etc/dotnet/install_location").Trim(); + if (Directory.Exists("/usr/share/dotnet")) // Officialy recommended dotnet location + return"/usr/share/dotnet"; + if (Directory.Exists("/usr/lib/dotnet")) // Deprecated recommended dotnet location + return "/usr/lib/dotnet"; + if (Environment.GetEnvironmentVariable("PATH") is string globalBinPath) // Searching for dotnet binary + return globalBinPath.Split(':').FirstOrDefault(x => File.Exists(Path.Combine(x, "dotnet"))); + return null; + } } } From 73bf28dc4712da7d465dda33299e107cfbc9df46 Mon Sep 17 00:00:00 2001 From: Crawcik Date: Thu, 14 Sep 2023 22:37:56 +0200 Subject: [PATCH 007/188] Change of priorities on RID --- Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs index e61642379..ecb6c9fea 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs @@ -192,17 +192,13 @@ namespace Flax.Build case TargetPlatform.Linux: { // TODO: Support /etc/dotnet/install_location - rid = ""; - ridFallback = $"linux-{arch}"; + rid = $"linux-{arch}"; + ridFallback = ""; if (string.IsNullOrEmpty(dotnetPath)) dotnetPath ??= SearchForDotnetLocationLinux(); if (dotnetPath == null) - { - rid = Utilities.ReadProcessOutput("dotnet", "--info").Split('\n').FirstOrDefault(x => x.StartsWith(" RID:"), "").Replace("RID:", "").Trim(); - if (rid == ridFallback) - ridFallback = ""; - } + ridFallback = Utilities.ReadProcessOutput("dotnet", "--info").Split('\n').FirstOrDefault(x => x.StartsWith(" RID:"), "").Replace("RID:", "").Trim(); break; } From 64a5d895bd607a90bce13832ba353c7459578b7b Mon Sep 17 00:00:00 2001 From: Crawcik Date: Thu, 14 Sep 2023 23:18:12 +0200 Subject: [PATCH 008/188] Better version checking --- Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs index ecb6c9fea..4b430702f 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs @@ -428,7 +428,8 @@ namespace Flax.Build version = version.Substring(0, version.IndexOf("-")); rev = 0; } - Version ver = new Version(version); + if (!Version.TryParse(version, out var ver)) + return null; return new Version(ver.Major, ver.Minor, ver.Build, rev); } From 1dd7a27568e6d4d1438bcb849f210960bf02dbdf Mon Sep 17 00:00:00 2001 From: Crawcik Date: Thu, 14 Sep 2023 23:19:36 +0200 Subject: [PATCH 009/188] Checking for executable --- Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs index 4b430702f..66b578693 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs @@ -448,9 +448,9 @@ namespace Flax.Build { if (File.Exists("/etc/dotnet/install_location")) // Officialy recommended dotnet location file return File.ReadAllText("/etc/dotnet/install_location").Trim(); - if (Directory.Exists("/usr/share/dotnet")) // Officialy recommended dotnet location + if (File.Exists("/usr/share/dotnet/dotnet")) // Officialy recommended dotnet location return"/usr/share/dotnet"; - if (Directory.Exists("/usr/lib/dotnet")) // Deprecated recommended dotnet location + if (File.Exists("/usr/lib/dotnet/dotnet")) // Deprecated recommended dotnet location return "/usr/lib/dotnet"; if (Environment.GetEnvironmentVariable("PATH") is string globalBinPath) // Searching for dotnet binary return globalBinPath.Split(':').FirstOrDefault(x => File.Exists(Path.Combine(x, "dotnet"))); From 2d2bfd9cd1c066c758de96c5314a36964be34d0c Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 16 Sep 2023 14:14:26 +0300 Subject: [PATCH 010/188] Include EditorModules in Assembly lookup paths --- Source/Engine/Engine/NativeInterop.Unmanaged.cs | 4 ++-- Source/Engine/Engine/NativeInterop.cs | 8 ++++---- Source/Engine/Scripting/Runtime/DotNet.cpp | 7 +++++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 2c1bab744..e96839546 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -184,7 +184,7 @@ namespace FlaxEngine.Interop { string moduleName = Marshal.PtrToStringAnsi(moduleNamePtr); string modulePath = Marshal.PtrToStringAnsi(modulePathPtr); - nativeLibraryPaths[moduleName] = modulePath; + libraryPaths[moduleName] = modulePath; } [UnmanagedCallersOnly] @@ -909,7 +909,7 @@ namespace FlaxEngine.Interop loadedNativeLibraries.Remove(nativeLibraryName); } if (nativeLibraryName != null) - nativeLibraryPaths.Remove(nativeLibraryName); + libraryPaths.Remove(nativeLibraryName); } [UnmanagedCallersOnly] diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index dd6087b98..8a8d543f1 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -50,7 +50,7 @@ namespace FlaxEngine.Interop private static Dictionary _typeSizeCache = new(); private static Dictionary loadedNativeLibraries = new(); - internal static Dictionary nativeLibraryPaths = new(); + internal static Dictionary libraryPaths = new(); private static Dictionary assemblyOwnedNativeLibraries = new(); internal static AssemblyLoadContext scriptingAssemblyLoadContext; @@ -59,7 +59,7 @@ namespace FlaxEngine.Interop { if (!loadedNativeLibraries.TryGetValue(libraryName, out IntPtr nativeLibrary)) { - if (!nativeLibraryPaths.TryGetValue(libraryName, out var nativeLibraryPath)) + if (!libraryPaths.TryGetValue(libraryName, out var nativeLibraryPath)) nativeLibraryPath = libraryName; nativeLibrary = NativeLibrary.Load(nativeLibraryPath, assembly, dllImportSearchPath); @@ -101,9 +101,9 @@ namespace FlaxEngine.Interop private static Assembly OnScriptingAssemblyLoadContextResolving(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName) { // FIXME: There should be a better way to resolve the path to EditorTargetPath where the dependencies are stored - foreach (string nativeLibraryPath in nativeLibraryPaths.Values) + foreach (string libraryPath in libraryPaths.Values) { - string editorTargetPath = Path.GetDirectoryName(nativeLibraryPath); + string editorTargetPath = Path.GetDirectoryName(libraryPath); var assemblyPath = Path.Combine(editorTargetPath, assemblyName.Name + ".dll"); if (File.Exists(assemblyPath)) diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 1bcef50b8..63feccba6 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -719,6 +719,13 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa StringAnsi nativeName = _name.EndsWith(".CSharp") ? StringAnsi(_name.Get(), _name.Length() - 7) : StringAnsi(_name); RegisterNativeLibrary(nativeName.Get(), StringAnsi(nativePath).Get()); } +#if USE_EDITOR + // Register the editor module location for Assembly resolver + else + { + RegisterNativeLibrary(_name.Get(), StringAnsi(assemblyPath).Get()); + } +#endif _hasCachedClasses = false; _assemblyPath = assemblyPath; From f114301e97a124b15a1ffd08b2eeb390c0fcb3e4 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 16 Sep 2023 15:02:31 +0300 Subject: [PATCH 011/188] Improve hostfxr error messages --- Source/Engine/Scripting/Runtime/DotNet.cpp | 28 ++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 1bcef50b8..dba30f1e5 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -1588,7 +1588,10 @@ bool InitHostfxr() void* hostfxr = Platform::LoadLibrary(path.Get()); if (hostfxr == nullptr) { - LOG(Fatal, "Failed to load hostfxr library ({0})", path); + if (FileSystem::FileExists(path)) + LOG(Fatal, "Failed to load hostfxr library, possible platform/architecture mismatch with the library. See log for more information. ({0})", path); + else + LOG(Fatal, "Failed to load hostfxr library ({0})", path); return true; } hostfxr_initialize_for_runtime_config = (hostfxr_initialize_for_runtime_config_fn)Platform::GetProcAddress(hostfxr, "hostfxr_initialize_for_runtime_config"); @@ -1627,7 +1630,28 @@ bool InitHostfxr() if (rc != 0 || handle == nullptr) { hostfxr_close(handle); - LOG(Fatal, "Failed to initialize hostfxr: {0:x} ({1})", (unsigned int)rc, String(init_params.dotnet_root)); + if (rc == 0x80008096) // FrameworkMissingFailure + { + String platformStr; + switch (PLATFORM_TYPE) + { + case PlatformType::Windows: + case PlatformType::UWP: + platformStr = PLATFORM_64BITS ? "Windows x64" : "Windows x86"; + break; + case PlatformType::Linux: + platformStr = PLATFORM_ARCH_ARM64 ? "Linux Arm64" : PLATFORM_ARCH_ARM ? "Linux Arm32" : PLATFORM_64BITS ? "Linux x64" : "Linux x86"; + break; + case PlatformType::Mac: + platformStr = PLATFORM_ARCH_ARM || PLATFORM_ARCH_ARM64 ? "macOS Arm64" : PLATFORM_64BITS ? "macOS x64" : "macOS x86"; + break; + default:; + platformStr = ""; + } + LOG(Fatal, "Failed to resolve compatible .NET runtime version in '{0}'. Make sure the correct platform version for runtime is installed ({1})", platformStr, String(init_params.dotnet_root)); + } + else + LOG(Fatal, "Failed to initialize hostfxr: {0:x} ({1})", (unsigned int)rc, String(init_params.dotnet_root)); return true; } From 785943bef81b7e6c87ed207085127bc99d51c3a3 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 16 Sep 2023 15:58:02 +0300 Subject: [PATCH 012/188] Add missing marshaller methods for compatibility --- .../Engine/NativeInterop.Marshallers.cs | 64 +++++++++++-------- .../Bindings/BindingsGenerator.CSharp.cs | 4 ++ .../Flax.Build/Build/DotNet/DotNetSdk.cs | 2 +- 3 files changed, 42 insertions(+), 28 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Marshallers.cs b/Source/Engine/Engine/NativeInterop.Marshallers.cs index 74d6e6bad..a74299670 100644 --- a/Source/Engine/Engine/NativeInterop.Marshallers.cs +++ b/Source/Engine/Engine/NativeInterop.Marshallers.cs @@ -32,6 +32,7 @@ namespace FlaxEngine.Interop public static class NativeToManaged { public static object ConvertToManaged(IntPtr unmanaged) => unmanaged == IntPtr.Zero ? null : ManagedHandle.FromIntPtr(unmanaged).Target; + public static IntPtr ConvertToUnmanaged(object managed) => managed != null ? ManagedHandle.ToIntPtr(managed, GCHandleType.Weak) : IntPtr.Zero; public static void Free(IntPtr unmanaged) { @@ -44,6 +45,7 @@ namespace FlaxEngine.Interop #endif public static class ManagedToNative { + public static object ConvertToManaged(IntPtr unmanaged) => unmanaged == IntPtr.Zero ? null : ManagedHandle.FromIntPtr(unmanaged).Target; public static IntPtr ConvertToUnmanaged(object managed) => managed != null ? ManagedHandle.ToIntPtr(managed, GCHandleType.Weak) : IntPtr.Zero; public static void Free(IntPtr unmanaged) @@ -147,29 +149,16 @@ namespace FlaxEngine.Interop #if FLAX_EDITOR [HideInEditor] #endif - [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ManagedToUnmanagedIn, typeof(ObjectMarshaller.ManagedToNative))] - [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.UnmanagedToManagedOut, typeof(ObjectMarshaller.ManagedToNative))] - [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ElementIn, typeof(ObjectMarshaller.ManagedToNative))] - [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ManagedToUnmanagedOut, typeof(ObjectMarshaller.NativeToManaged))] - [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.UnmanagedToManagedIn, typeof(ObjectMarshaller.NativeToManaged))] - [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ElementOut, typeof(ObjectMarshaller.NativeToManaged))] + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ManagedToUnmanagedIn, typeof(ObjectMarshaller))] + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.UnmanagedToManagedOut, typeof(ObjectMarshaller))] + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ElementIn, typeof(ObjectMarshaller))] + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ManagedToUnmanagedOut, typeof(ObjectMarshaller))] + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.UnmanagedToManagedIn, typeof(ObjectMarshaller))] + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ElementOut, typeof(ObjectMarshaller))] public static class ObjectMarshaller { -#if FLAX_EDITOR - [HideInEditor] -#endif - public static class NativeToManaged - { - public static FlaxEngine.Object ConvertToManaged(IntPtr unmanaged) => unmanaged != IntPtr.Zero ? Unsafe.As(ManagedHandle.FromIntPtr(unmanaged).Target) : null; - } - -#if FLAX_EDITOR - [HideInEditor] -#endif - public static class ManagedToNative - { - public static IntPtr ConvertToUnmanaged(FlaxEngine.Object managed) => Unsafe.As(managed) != null ? ManagedHandle.ToIntPtr(managed) : IntPtr.Zero; - } + public static FlaxEngine.Object ConvertToManaged(IntPtr unmanaged) => unmanaged != IntPtr.Zero ? Unsafe.As(ManagedHandle.FromIntPtr(unmanaged).Target) : null; + public static IntPtr ConvertToUnmanaged(FlaxEngine.Object managed) => Unsafe.As(managed) != null ? ManagedHandle.ToIntPtr(managed) : IntPtr.Zero; } #if FLAX_EDITOR @@ -342,6 +331,7 @@ namespace FlaxEngine.Interop public static class NativeToManaged { public static Dictionary ConvertToManaged(IntPtr unmanaged) => DictionaryMarshaller.ToManaged(unmanaged); + public static IntPtr ConvertToUnmanaged(Dictionary managed) => DictionaryMarshaller.ToNative(managed, GCHandleType.Weak); public static void Free(IntPtr unmanaged) => DictionaryMarshaller.Free(unmanaged); } @@ -350,8 +340,8 @@ namespace FlaxEngine.Interop #endif public static class ManagedToNative { + public static Dictionary ConvertToManaged(IntPtr unmanaged) => DictionaryMarshaller.ToManaged(unmanaged); public static IntPtr ConvertToUnmanaged(Dictionary managed) => DictionaryMarshaller.ToNative(managed, GCHandleType.Weak); - public static void Free(IntPtr unmanaged) { //DictionaryMarshaller.Free(unmanaged); // No need to free weak handles @@ -425,6 +415,28 @@ namespace FlaxEngine.Interop return new T[numElements]; } + public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[] managed, out int numElements) + { + if (managed is null) + { + numElements = 0; + return null; + } + numElements = managed.Length; + (ManagedHandle managedArrayHandle, _) = ManagedArray.AllocatePooledArray(managed.Length); + return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArrayHandle); + } + + public static ReadOnlySpan GetManagedValuesSource(T[] managed) => managed; + + public static Span GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged) + { + if (unmanaged == null) + return Span.Empty; + ManagedArray managedArray = Unsafe.As(ManagedHandle.FromIntPtr(new IntPtr(unmanaged)).Target); + return managedArray.ToSpan(); + } + public static Span GetManagedValuesDestination(T[] managed) => managed; public static ReadOnlySpan GetUnmanagedValuesSource(TUnmanagedElement* unmanaged, int numElements) @@ -591,6 +603,7 @@ namespace FlaxEngine.Interop public static class NativeToManaged { public static string ConvertToManaged(IntPtr unmanaged) => ManagedString.ToManaged(unmanaged); + public static unsafe IntPtr ConvertToUnmanaged(string managed) => managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed, GCHandleType.Weak); public static void Free(IntPtr unmanaged) => ManagedString.Free(unmanaged); } @@ -599,11 +612,8 @@ namespace FlaxEngine.Interop #endif public static class ManagedToNative { - public static unsafe IntPtr ConvertToUnmanaged(string managed) - { - return managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed, GCHandleType.Weak); - } - + public static string ConvertToManaged(IntPtr unmanaged) => ManagedString.ToManaged(unmanaged); + public static unsafe IntPtr ConvertToUnmanaged(string managed) => managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed, GCHandleType.Weak); public static void Free(IntPtr unmanaged) { //ManagedString.Free(unmanaged); // No need to free weak handles diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index b825f5ac1..6a9aa7669 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -1340,6 +1340,7 @@ namespace Flax.Build.Bindings public static class NativeToManaged { public static {{classInfo.Name}} ConvertToManaged(IntPtr unmanaged) => Unsafe.As<{{classInfo.Name}}>(ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(unmanaged)); + public static IntPtr ConvertToUnmanaged({{classInfo.Name}} managed) => ManagedHandleMarshaller.ManagedToNative.ConvertToUnmanaged(managed); public static void Free(IntPtr unmanaged) => ManagedHandleMarshaller.NativeToManaged.Free(unmanaged); } #if FLAX_EDITOR @@ -1347,6 +1348,7 @@ namespace Flax.Build.Bindings #endif public static class ManagedToNative { + public static {{classInfo.Name}} ConvertToManaged(IntPtr unmanaged) => Unsafe.As<{{classInfo.Name}}>(ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(unmanaged)); public static IntPtr ConvertToUnmanaged({{classInfo.Name}} managed) => ManagedHandleMarshaller.ManagedToNative.ConvertToUnmanaged(managed); public static void Free(IntPtr unmanaged) => ManagedHandleMarshaller.ManagedToNative.Free(unmanaged); } @@ -1668,6 +1670,7 @@ namespace Flax.Build.Bindings contents.Append(indent).AppendLine("[HideInEditor]"); contents.Append(indent).AppendLine("public static class NativeToManaged").Append(indent).AppendLine("{"); contents.Append(indent2).AppendLine($"public static {structureInfo.Name} ConvertToManaged({structureInfo.Name}Internal unmanaged) => {marshallerName}.ToManaged(unmanaged);"); + contents.Append(indent2).AppendLine($"public static {structureInfo.Name}Internal ConvertToUnmanaged({structureInfo.Name} managed) => {marshallerName}.ToNative(managed);"); contents.Append(indent2).AppendLine($"public static void Free({structureInfo.Name}Internal unmanaged)"); contents.Append(indent2).AppendLine("{").Append(indent3).AppendLine(freeContents2.Replace("\n", "\n" + indent3).ToString().TrimEnd()).Append(indent2).AppendLine("}"); contents.Append(indent).AppendLine("}"); @@ -1676,6 +1679,7 @@ namespace Flax.Build.Bindings if (buildData.Target != null && buildData.Target.IsEditor) contents.Append(indent).AppendLine("[HideInEditor]"); contents.Append(indent).AppendLine($"public static class ManagedToNative").Append(indent).AppendLine("{"); + contents.Append(indent2).AppendLine($"public static {structureInfo.Name} ConvertToManaged({structureInfo.Name}Internal unmanaged) => {marshallerName}.ToManaged(unmanaged);"); contents.Append(indent2).AppendLine($"public static {structureInfo.Name}Internal ConvertToUnmanaged({structureInfo.Name} managed) => {marshallerName}.ToNative(managed);"); contents.Append(indent2).AppendLine($"public static void Free({structureInfo.Name}Internal unmanaged) => {marshallerName}.Free(unmanaged);"); contents.Append(indent).AppendLine("}"); diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs index d3a706a43..385bc3572 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs @@ -271,7 +271,7 @@ namespace Flax.Build // Found IsValid = true; - Log.Verbose($"Found .NET SDK {VersionName} (runtime {RuntimeVersionName}) at {RootPath}"); + Log.Info($"Using .NET SDK {VersionName}, runtime {RuntimeVersionName} ({RootPath})"); foreach (var e in _hostRuntimes) Log.Verbose($" - Host Runtime for {e.Key.Key} {e.Key.Value}"); } From 5ab299fed9377280e9cec6a57634c342135520fd Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 16 Sep 2023 17:58:38 +0300 Subject: [PATCH 013/188] Fix assets getting deleted when modified by external applications --- Source/Editor/Modules/ContentDatabaseModule.cs | 11 ++++++----- Source/Editor/Windows/ContentWindow.cs | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs index 29d6b2c00..143352421 100644 --- a/Source/Editor/Modules/ContentDatabaseModule.cs +++ b/Source/Editor/Modules/ContentDatabaseModule.cs @@ -643,7 +643,8 @@ namespace FlaxEditor.Modules /// Deletes the specified item. /// /// The item. - public void Delete(ContentItem item) + /// If the file was deleted by the user and not outside the editor. + public void Delete(ContentItem item, bool deletedByUser = false) { if (item == null) throw new ArgumentNullException(); @@ -667,12 +668,12 @@ namespace FlaxEditor.Modules var children = folder.Children.ToArray(); for (int i = 0; i < children.Length; i++) { - Delete(children[i]); + Delete(children[i], deletedByUser); } } // Remove directory - if (Directory.Exists(path)) + if (deletedByUser && Directory.Exists(path)) { try { @@ -701,7 +702,7 @@ namespace FlaxEditor.Modules // Delete asset by using content pool FlaxEngine.Content.DeleteAsset(path); } - else + else if (deletedByUser) { // Delete file if (File.Exists(path)) @@ -847,7 +848,7 @@ namespace FlaxEditor.Modules Editor.Log(string.Format($"Content item \'{child.Path}\' has been removed")); // Destroy it - Delete(child); + Delete(child, false); i--; } diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 0f3c12286..91e4ec1b5 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -626,7 +626,7 @@ namespace FlaxEditor.Windows // Delete items for (int i = 0; i < toDelete.Count; i++) - Editor.ContentDatabase.Delete(toDelete[i]); + Editor.ContentDatabase.Delete(toDelete[i], true); RefreshView(); } From a89856becef9b76bc5712a421e5f7b6e73748b35 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 16 Sep 2023 10:15:26 -0500 Subject: [PATCH 014/188] Increase size of clamp node to handle vector4. --- Source/Editor/Surface/Archetypes/Math.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Surface/Archetypes/Math.cs b/Source/Editor/Surface/Archetypes/Math.cs index 1b8f62e62..fe2f1e044 100644 --- a/Source/Editor/Surface/Archetypes/Math.cs +++ b/Source/Editor/Surface/Archetypes/Math.cs @@ -126,7 +126,7 @@ namespace FlaxEditor.Surface.Archetypes Title = "Clamp", Description = "Clamps value to the specified range", Flags = NodeFlags.AllGraphs, - Size = new Float2(110, 60), + Size = new Float2(140, 60), ConnectionsHints = ConnectionsHint.Numeric, IndependentBoxes = new[] { 0 }, DependentBoxes = new[] { 1, 2, 3 }, From 9a7fb827381b9b951dbe7c6ffd7b3ce0e5db5df8 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 16 Sep 2023 18:38:24 +0300 Subject: [PATCH 015/188] Fix tests --- Source/Engine/Tests/TestScripting.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Tests/TestScripting.cs b/Source/Engine/Tests/TestScripting.cs index fd94b8017..93c6637fe 100644 --- a/Source/Engine/Tests/TestScripting.cs +++ b/Source/Engine/Tests/TestScripting.cs @@ -19,7 +19,7 @@ namespace FlaxEngine.Tests { var result = 0; var libraryName = "FlaxEngine"; - var library = NativeLibrary.Load(Interop.NativeInterop.nativeLibraryPaths[libraryName]); + var library = NativeLibrary.Load(Interop.NativeInterop.libraryPaths[libraryName]); if (library == IntPtr.Zero) return -1; var types = typeof(FlaxEngine.Object).Assembly.GetTypes(); From 9931a5c02660bca47eeaea50e0cae2d4906daa21 Mon Sep 17 00:00:00 2001 From: MineBill Date: Sat, 16 Sep 2023 19:07:23 +0300 Subject: [PATCH 016/188] Don't treat '.' in folder names as extensions. --- Source/Editor/Modules/ContentEditingModule.cs | 8 +++++++- Source/Editor/Windows/ContentWindow.cs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Modules/ContentEditingModule.cs b/Source/Editor/Modules/ContentEditingModule.cs index 3db27af40..a6d8132f0 100644 --- a/Source/Editor/Modules/ContentEditingModule.cs +++ b/Source/Editor/Modules/ContentEditingModule.cs @@ -90,6 +90,12 @@ namespace FlaxEditor.Modules hint = "Too long name."; return false; } + + if (item.IsFolder && shortName.EndsWith(".")) + { + hint = "Name cannot end with '.'"; + return false; + } // Find invalid characters if (Utilities.Utils.HasInvalidPathChar(shortName)) @@ -120,7 +126,7 @@ namespace FlaxEditor.Modules // Cache data string sourcePath = item.Path; string sourceFolder = System.IO.Path.GetDirectoryName(sourcePath); - string extension = System.IO.Path.GetExtension(sourcePath); + string extension = item.IsFolder ? "" : System.IO.Path.GetExtension(sourcePath); string destinationPath = StringUtils.CombinePaths(sourceFolder, shortName + extension); if (item.IsFolder) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 0f3c12286..d29775a8b 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -520,7 +520,7 @@ namespace FlaxEditor.Windows } // Cache data - string extension = Path.GetExtension(item.Path); + string extension = item.IsFolder ? "" : Path.GetExtension(item.Path); var newPath = StringUtils.CombinePaths(item.ParentFolder.Path, newShortName + extension); // Check if was renaming mock element From dabbd7bc6a1f04fe57970ceed12f9c4dcfad388c Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 16 Sep 2023 11:46:36 -0500 Subject: [PATCH 017/188] Add highlighting when mouse is over slider thumb. --- Source/Engine/UI/GUI/Common/Slider.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/Slider.cs b/Source/Engine/UI/GUI/Common/Slider.cs index d7324ae0e..7ea2accef 100644 --- a/Source/Engine/UI/GUI/Common/Slider.cs +++ b/Source/Engine/UI/GUI/Common/Slider.cs @@ -60,6 +60,7 @@ public class Slider : ContainerControl private float _thumbCenter; private Float2 _thumbSize = new Float2(16, 16); private bool _isSliding; + private bool _mouseOverThumb; /// /// Gets or sets the value (normalized to range 0-100). @@ -163,21 +164,27 @@ public class Slider : ContainerControl public IBrush FillTrackBrush { get; set; } /// - /// The color of the slider thumb when it's not selected + /// The color of the slider thumb when it's not selected. /// [EditorDisplay("Thumb Style"), EditorOrder(2030), Tooltip("The color of the slider thumb when it's not selected."), ExpandGroups] public Color ThumbColor { get; set; } + + /// + /// The color of the slider thumb when it's highlighted. + /// + [EditorDisplay("Thumb Style"), EditorOrder(2031), Tooltip("The color of the slider thumb when it's highlighted.")] + public Color ThumbColorHighlighted { get; set; } /// - /// The color of the slider thumb when it's selected + /// The color of the slider thumb when it's selected. /// - [EditorDisplay("Thumb Style"), EditorOrder(2031), Tooltip("The color of the slider thumb when it's selected.")] + [EditorDisplay("Thumb Style"), EditorOrder(2032), Tooltip("The color of the slider thumb when it's selected.")] public Color ThumbColorSelected { get; set; } /// /// Gets or sets the brush used for slider thumb drawing. /// - [EditorDisplay("Thumb Style"), EditorOrder(2032), Tooltip("The brush of the slider thumb.")] + [EditorDisplay("Thumb Style"), EditorOrder(2033), Tooltip("The brush of the slider thumb.")] public IBrush ThumbBrush { get; set; } /// @@ -222,6 +229,7 @@ public class Slider : ContainerControl TrackFillLineColor = style.LightBackground; ThumbColor = style.BackgroundNormal; ThumbColorSelected = style.BackgroundSelected; + ThumbColorHighlighted = style.BackgroundHighlighted; UpdateThumb(); } @@ -270,7 +278,7 @@ public class Slider : ContainerControl } // Draw thumb - var thumbColor = _isSliding ? ThumbColorSelected : ThumbColor; + var thumbColor = _isSliding ? ThumbColorSelected : (_mouseOverThumb ? ThumbColorHighlighted : ThumbColor); if (ThumbBrush != null) ThumbBrush.Draw(_thumbRect, thumbColor); else @@ -317,6 +325,7 @@ public class Slider : ContainerControl /// public override void OnMouseMove(Float2 location) { + _mouseOverThumb = _thumbRect.Contains(location); if (_isSliding) { // Update sliding From 7a73c04688e2169ad04d77530e50218e6355254d Mon Sep 17 00:00:00 2001 From: Andrej Stojkovikj Date: Sat, 16 Sep 2023 21:18:00 +0200 Subject: [PATCH 018/188] SmoothDamp implementation for Vector2 and Vector3 --- Source/Engine/Core/Math/Vector2.cs | 86 ++++++++++++++++++++++++++ Source/Engine/Core/Math/Vector3.cs | 98 ++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+) diff --git a/Source/Engine/Core/Math/Vector2.cs b/Source/Engine/Core/Math/Vector2.cs index 2af365638..4b6fb5e59 100644 --- a/Source/Engine/Core/Math/Vector2.cs +++ b/Source/Engine/Core/Math/Vector2.cs @@ -58,6 +58,7 @@ using Mathr = FlaxEngine.Mathf; */ using System; using System.Globalization; +using System.ComponentModel; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -953,6 +954,91 @@ namespace FlaxEngine return result; } + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + /// Defines the upper limit on the speed of the Smooth Damp. + public static Vector2 SmoothDamp(Vector2 current, Vector2 target, ref Vector2 currentVelocity, float smoothTime, float maxSpeed) + { + return SmoothDamp(current, target, ref currentVelocity, smoothTime, maxSpeed, Time.DeltaTime); + } + + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + public static Vector2 SmoothDamp(Vector2 current, Vector2 target, ref Vector2 currentVelocity, float smoothTime) + { + return SmoothDamp(current, target, ref currentVelocity, smoothTime, float.PositiveInfinity, Time.DeltaTime); + } + + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + /// Defines the upper limit on the speed of the Smooth Damp. + /// Delta Time, represents the time elapsed since last frame. + public static Vector2 SmoothDamp(Vector2 current, Vector2 target, ref Vector2 currentVelocity, float smoothTime, [DefaultValue("float.PositiveInfinity")] float maxSpeed, [DefaultValue("Time.DeltaTime")] float deltaTime) + { + smoothTime = Mathf.Max(0.0001f, smoothTime); + float a = 2f / smoothTime; + float b = a * deltaTime; + float e = 1f / (1f + b + 0.48f * b * b + 0.235f * b * b * b); + + float change_x = current.X - target.X; + float change_y = current.Y - target.Y; + Vector2 originalTo = target; + + float change = maxSpeed * smoothTime; + float changeSq = change * change; + float sqrmag = change_x * change_x + change_y * change_y; + if (sqrmag > changeSq) + { + var mag = (float)Math.Sqrt(sqrmag); + change_x = change_x / mag * change; + change_y = change_y / mag * change; + } + + target.X = current.X - change_x; + target.Y = current.Y - change_y; + + float temp_x = (currentVelocity.X + a * change_x) * deltaTime; + float temp_y = (currentVelocity.Y + a * change_y) * deltaTime; + + currentVelocity.X = (currentVelocity.X - a * temp_x) * e; + currentVelocity.Y = (currentVelocity.Y - a * temp_y) * e; + + float output_x = target.X + (change_x + temp_x) * e; + float output_y = target.Y + (change_y + temp_y) * e; + + float x1 = originalTo.X - current.X; + float y1 = originalTo.Y - current.Y; + + float x2 = output_x - originalTo.X; + float y2 = output_y - originalTo.Y; + + if (x1 * x2 + y1 * y2 > 0) + { + output_x = originalTo.X; + output_y = originalTo.Y; + + currentVelocity.X = (output_x - originalTo.X) / deltaTime; + currentVelocity.Y = (output_y - originalTo.Y) / deltaTime; + } + + return new Vector2(output_x, output_y); + } + /// /// Performs a cubic interpolation between two vectors. /// diff --git a/Source/Engine/Core/Math/Vector3.cs b/Source/Engine/Core/Math/Vector3.cs index 4442d3334..c1ea6bb67 100644 --- a/Source/Engine/Core/Math/Vector3.cs +++ b/Source/Engine/Core/Math/Vector3.cs @@ -58,6 +58,7 @@ using Mathr = FlaxEngine.Mathf; */ using System; using System.Globalization; +using System.ComponentModel; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -1042,6 +1043,103 @@ namespace FlaxEngine return result; } + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + /// Defines the upper limit on the speed of the Smooth Damp. + public static Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime, float maxSpeed) + { + return SmoothDamp(current, target, ref currentVelocity, smoothTime, maxSpeed, Time.DeltaTime); + } + + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + public static Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime) + { + return SmoothDamp(current, target, ref currentVelocity, smoothTime, float.PositiveInfinity, Time.DeltaTime); + } + + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + /// Defines the upper limit on the speed of the Smooth Damp. + /// Delta Time, represents the time elapsed since last frame. + public static Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime, [DefaultValue("float.PositiveInfinity")] float maxSpeed, [DefaultValue("Time.DeltaTime")] float deltaTime) + { + smoothTime = Mathf.Max(0.0001f, smoothTime); + float a = 2f / smoothTime; + float b = a * deltaTime; + float e = 1f / (1f + b + 0.48f * b * b + 0.235f * b * b * b); + + float change_x = current.X - target.X; + float change_y = current.Y - target.Y; + float change_z = current.Z - target.Z; + + Vector3 originalTo = target; + + float maxChangeSpeed = maxSpeed * smoothTime; + float changeSq = maxChangeSpeed * maxChangeSpeed; + float sqrMag = change_x * change_x + change_y * change_y + change_z * change_z; + if (sqrMag > changeSq) + { + var mag = (float)Math.Sqrt(sqrMag); + change_x = change_x / mag * maxChangeSpeed; + change_y = change_y / mag * maxChangeSpeed; + change_z = change_z / mag * maxChangeSpeed; + } + + target.X = current.X - change_x; + target.Y = current.Y - change_y; + target.Z = current.Z - change_z; + + float temp_x = (currentVelocity.X + a * change_x) * deltaTime; + float temp_y = (currentVelocity.Y + a * change_y) * deltaTime; + float temp_z = (currentVelocity.Z + a * change_z) * deltaTime; + + currentVelocity.X = (currentVelocity.X - a * temp_x) * e; + currentVelocity.Y = (currentVelocity.Y - a * temp_y) * e; + currentVelocity.Z = (currentVelocity.Z - a * temp_z) * e; + + float output_x = target.X + (change_x + temp_x) * e; + float output_y = target.Y + (change_y + temp_y) * e; + float output_z = target.Z + (change_z + temp_z) * e; + + float x1 = originalTo.X - current.X; + float y1 = originalTo.Y - current.Y; + float z1 = originalTo.Z - current.Z; + + float x2 = output_x - originalTo.X; + float y2 = output_y - originalTo.Y; + float z2 = output_z - originalTo.Z; + + if (x1 * x2 + y1 * y2 + z1 * z2 > 0) + { + output_x = originalTo.X; + output_y = originalTo.Y; + output_z = originalTo.Z; + + currentVelocity.X = (output_x - originalTo.X) / deltaTime; + currentVelocity.Y = (output_y - originalTo.Y) / deltaTime; + currentVelocity.Z = (output_z - originalTo.Z) / deltaTime; + } + + return new Vector3(output_x, output_y, output_z); + } + + /// /// Performs a cubic interpolation between two vectors. /// From 1d08e4b1b8517fc673585c44ead34df243be4790 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 16 Sep 2023 14:28:21 -0500 Subject: [PATCH 019/188] Add several fixes to button sizes when fonts are larger. --- .../CustomEditors/Dedicated/ScriptsEditor.cs | 16 ++++++++++------ .../CustomEditors/Dedicated/UIControlEditor.cs | 6 ++++-- .../Editors/ActorTransformEditor.cs | 8 ++++---- Source/Editor/CustomEditors/Editors/TagEditor.cs | 7 ++++++- Source/Editor/Tools/Foliage/FoliageTab.cs | 11 ++++++++++- Source/Editor/Tools/Terrain/CarveTab.cs | 11 ++++++++++- 6 files changed, 44 insertions(+), 15 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index bf82e19da..d84414fcf 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -25,6 +25,7 @@ namespace FlaxEditor.CustomEditors.Dedicated private DragHandlers _dragHandlers; private DragScriptItems _dragScripts; private DragAssets _dragAssets; + private Button _addScriptsButton; /// /// The parent scripts editor. @@ -40,16 +41,19 @@ namespace FlaxEditor.CustomEditors.Dedicated AutoFocus = false; // Add script button - float addScriptButtonWidth = 60.0f; - var addScriptButton = new Button + var buttonText = "Add script"; + var textSize = Style.Current.FontMedium.MeasureText(buttonText); + float addScriptButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; + var buttonHeight = (textSize.Y < 18) ? 18 : textSize.Y + 4; + _addScriptsButton = new Button { TooltipText = "Add new scripts to the actor", AnchorPreset = AnchorPresets.MiddleCenter, - Text = "Add script", + Text = buttonText, Parent = this, - Bounds = new Rectangle((Width - addScriptButtonWidth) / 2, 1, addScriptButtonWidth, 18), + Bounds = new Rectangle((Width - addScriptButtonWidth) / 2, 1, addScriptButtonWidth, buttonHeight), }; - addScriptButton.ButtonClicked += OnAddScriptButtonClicked; + _addScriptsButton.ButtonClicked += OnAddScriptButtonClicked; } private void OnAddScriptButtonClicked(Button button) @@ -82,7 +86,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var size = Size; // Info - Render2D.DrawText(style.FontSmall, "Drag scripts here", new Rectangle(2, 22, size.X - 4, size.Y - 4 - 20), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, "Drag scripts here", new Rectangle(2, _addScriptsButton.Height + 4, size.X - 4, size.Y - 4 - 20), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); // Check if drag is over if (IsDragOver && _dragHandlers != null && _dragHandlers.HasValidDrag) diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index fde4967e8..5215e23a4 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -422,12 +422,14 @@ namespace FlaxEditor.CustomEditors.Dedicated // Set control type button var space = layout.Space(20); - float setTypeButtonWidth = 60.0f; + var buttonText = "Set Type"; + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); + float setTypeButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; var setTypeButton = new Button { TooltipText = "Sets the control to the given type", AnchorPreset = AnchorPresets.MiddleCenter, - Text = "Set Type", + Text = buttonText, Parent = space.Spacer, Bounds = new Rectangle((space.Spacer.Width - setTypeButtonWidth) / 2, 1, setTypeButtonWidth, 18), }; diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index cb4e7b9c1..4aa02ac78 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -88,20 +88,20 @@ namespace FlaxEditor.CustomEditors.Editors LinkValues = Editor.Instance.Windows.PropertiesWin.ScaleLinked; // Add button with the link icon + _linkButton = new Button { BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Link32), Parent = LinkedLabel, Width = 18, Height = 18, - AnchorPreset = AnchorPresets.TopLeft, + AnchorPreset = AnchorPresets.MiddleLeft, }; _linkButton.Clicked += ToggleLink; ToggleEnabled(); SetLinkStyle(); - var x = LinkedLabel.Text.Value.Length * 7 + 5; - _linkButton.LocalX += x; - _linkButton.LocalY += 1; + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(LinkedLabel.Text.Value); + _linkButton.LocalX += textSize.X + 10; LinkedLabel.SetupContextMenu += (label, menu, editor) => { menu.AddSeparator(); diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index 3d2dd86aa..d69571dee 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -623,13 +623,18 @@ namespace FlaxEditor.CustomEditors.Editors { _label = layout.ClickableLabel(GetText(out _)).CustomControl; _label.RightClick += ShowPicker; + var buttonText = "..."; var button = new Button { Size = new Float2(16.0f), - Text = "...", + Text = buttonText, TooltipText = "Edit...", Parent = _label, }; + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); + if (textSize.Y > button.Width) + button.Width = textSize.Y + 2; + button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true); button.Clicked += ShowPicker; } diff --git a/Source/Editor/Tools/Foliage/FoliageTab.cs b/Source/Editor/Tools/Foliage/FoliageTab.cs index 814376c75..10358a8c0 100644 --- a/Source/Editor/Tools/Foliage/FoliageTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTab.cs @@ -137,14 +137,23 @@ namespace FlaxEditor.Tools.Foliage Offsets = Margin.Zero, Parent = _noFoliagePanel }; + + var buttonText = "Create new foliage"; _createNewFoliage = new Button { - Text = "Create new foliage", + Text = buttonText, AnchorPreset = AnchorPresets.MiddleCenter, Offsets = new Margin(-60, 120, -12, 24), Parent = _noFoliagePanel, Enabled = false }; + var textSize = Style.Current.FontMedium.MeasureText(buttonText); + if (_createNewFoliage.Width < textSize.X) + { + _createNewFoliage.LocalX -= (textSize.X - _createNewFoliage.Width) / 2; + _createNewFoliage.Width = textSize.X + 6; + } + _createNewFoliage.Clicked += OnCreateNewFoliageClicked; } diff --git a/Source/Editor/Tools/Terrain/CarveTab.cs b/Source/Editor/Tools/Terrain/CarveTab.cs index 6bee0bd92..000f90817 100644 --- a/Source/Editor/Tools/Terrain/CarveTab.cs +++ b/Source/Editor/Tools/Terrain/CarveTab.cs @@ -95,14 +95,23 @@ namespace FlaxEditor.Tools.Terrain Offsets = Margin.Zero, Parent = _noTerrainPanel }; + + var buttonText = "Create new terrain"; _createTerrainButton = new Button { - Text = "Create new terrain", + Text = buttonText, AnchorPreset = AnchorPresets.MiddleCenter, Offsets = new Margin(-60, 120, -12, 24), Parent = _noTerrainPanel, Enabled = false }; + var textSize = Style.Current.FontMedium.MeasureText(buttonText); + if (_createTerrainButton.Width < textSize.X) + { + _createTerrainButton.LocalX -= (textSize.X - _createTerrainButton.Width) / 2; + _createTerrainButton.Width = textSize.X + 6; + } + _createTerrainButton.Clicked += OnCreateNewTerrainClicked; } From f4b7ae41a987a0077d077f01c8f0a619b5f02e7a Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 16 Sep 2023 14:51:47 -0500 Subject: [PATCH 020/188] Fix profiler rows with larger font sizes. --- Source/Editor/GUI/Row.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Editor/GUI/Row.cs b/Source/Editor/GUI/Row.cs index 8dad8b20d..b07d693e5 100644 --- a/Source/Editor/GUI/Row.cs +++ b/Source/Editor/GUI/Row.cs @@ -37,6 +37,9 @@ namespace FlaxEditor.GUI : base(0, 0, 100, height) { Depth = -1; + + if (Height < Style.Current.FontMedium.Height) + Height = Style.Current.FontMedium.Height + 4; } /// From e9bafed2c76cda023a1d26d0cbabe1a9a5e27adb Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 16 Sep 2023 15:22:15 -0500 Subject: [PATCH 021/188] Fix about dialog button --- Source/Editor/Windows/AboutDialog.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs index 4b9595445..b059dadcb 100644 --- a/Source/Editor/Windows/AboutDialog.cs +++ b/Source/Editor/Windows/AboutDialog.cs @@ -52,9 +52,11 @@ namespace FlaxEditor.Windows VerticalAlignment = TextAlignment.Near, Parent = this }; - var copyVersionButton = new Button(Width - 104, 6, 100, 20) + var buttonText = "Copy version info"; + var fontSize = Style.Current.FontMedium.MeasureText(buttonText); + var copyVersionButton = new Button(Width - fontSize.X - 8, 6, fontSize.X + 4, 20) { - Text = "Copy version info", + Text = buttonText, TooltipText = "Copies the current engine version information to system clipboard.", Parent = this }; From d87a60de4848c1e9c7312d518b6c1c92e925095e Mon Sep 17 00:00:00 2001 From: Andrej Stojkovikj Date: Sat, 16 Sep 2023 22:27:12 +0200 Subject: [PATCH 022/188] Naming scheme for Vector2 fix --- Source/Engine/Core/Math/Vector2.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Core/Math/Vector2.cs b/Source/Engine/Core/Math/Vector2.cs index 4b6fb5e59..9fe3c6944 100644 --- a/Source/Engine/Core/Math/Vector2.cs +++ b/Source/Engine/Core/Math/Vector2.cs @@ -1001,12 +1001,12 @@ namespace FlaxEngine float change = maxSpeed * smoothTime; float changeSq = change * change; - float sqrmag = change_x * change_x + change_y * change_y; - if (sqrmag > changeSq) + float sqrDist = change_x * change_x + change_y * change_y; + if (sqrDist > changeSq) { - var mag = (float)Math.Sqrt(sqrmag); - change_x = change_x / mag * change; - change_y = change_y / mag * change; + var dist = (float)Math.Sqrt(sqrDist); + change_x = change_x / dist * change; + change_y = change_y / dist * change; } target.X = current.X - change_x; From ab2d60e6d2693e7b9da179324f23c549747769cd Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 16 Sep 2023 15:30:49 -0500 Subject: [PATCH 023/188] Fix viewport options extras locations. --- Source/Editor/Viewport/EditorViewport.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index ad7e06ba5..6d92dce3c 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -441,6 +441,9 @@ namespace FlaxEditor.Viewport if (useWidgets) { + var largestText = "Invert Panning"; + var textSize = Style.Current.FontMedium.MeasureText(largestText); + var xLocationForExtras = textSize.X + 5; // Camera speed widget var camSpeed = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); var camSpeedCM = new ContextMenu(); @@ -541,7 +544,7 @@ namespace FlaxEditor.Viewport { var ortho = ViewWidgetButtonMenu.AddButton("Orthographic"); ortho.CloseMenuOnClick = false; - var orthoValue = new CheckBox(90, 2, _isOrtho) + var orthoValue = new CheckBox(xLocationForExtras, 2, _isOrtho) { Parent = ortho }; @@ -581,7 +584,7 @@ namespace FlaxEditor.Viewport { var fov = ViewWidgetButtonMenu.AddButton("Field Of View"); fov.CloseMenuOnClick = false; - var fovValue = new FloatValueBox(1, 90, 2, 70.0f, 35.0f, 160.0f, 0.1f) + var fovValue = new FloatValueBox(1, xLocationForExtras, 2, 70.0f, 35.0f, 160.0f, 0.1f) { Parent = fov }; @@ -598,7 +601,7 @@ namespace FlaxEditor.Viewport { var orthoSize = ViewWidgetButtonMenu.AddButton("Ortho Scale"); orthoSize.CloseMenuOnClick = false; - var orthoSizeValue = new FloatValueBox(_orthoSize, 90, 2, 70.0f, 0.001f, 100000.0f, 0.01f) + var orthoSizeValue = new FloatValueBox(_orthoSize, xLocationForExtras, 2, 70.0f, 0.001f, 100000.0f, 0.01f) { Parent = orthoSize }; @@ -615,7 +618,7 @@ namespace FlaxEditor.Viewport { var nearPlane = ViewWidgetButtonMenu.AddButton("Near Plane"); nearPlane.CloseMenuOnClick = false; - var nearPlaneValue = new FloatValueBox(2.0f, 90, 2, 70.0f, 0.001f, 1000.0f) + var nearPlaneValue = new FloatValueBox(2.0f, xLocationForExtras, 2, 70.0f, 0.001f, 1000.0f) { Parent = nearPlane }; @@ -627,7 +630,7 @@ namespace FlaxEditor.Viewport { var farPlane = ViewWidgetButtonMenu.AddButton("Far Plane"); farPlane.CloseMenuOnClick = false; - var farPlaneValue = new FloatValueBox(1000, 90, 2, 70.0f, 10.0f) + var farPlaneValue = new FloatValueBox(1000, xLocationForExtras, 2, 70.0f, 10.0f) { Parent = farPlane }; @@ -639,7 +642,7 @@ namespace FlaxEditor.Viewport { var brightness = ViewWidgetButtonMenu.AddButton("Brightness"); brightness.CloseMenuOnClick = false; - var brightnessValue = new FloatValueBox(1.0f, 90, 2, 70.0f, 0.001f, 10.0f, 0.001f) + var brightnessValue = new FloatValueBox(1.0f, xLocationForExtras, 2, 70.0f, 0.001f, 10.0f, 0.001f) { Parent = brightness }; @@ -651,7 +654,7 @@ namespace FlaxEditor.Viewport { var resolution = ViewWidgetButtonMenu.AddButton("Resolution"); resolution.CloseMenuOnClick = false; - var resolutionValue = new FloatValueBox(1.0f, 90, 2, 70.0f, 0.1f, 4.0f, 0.001f) + var resolutionValue = new FloatValueBox(1.0f, xLocationForExtras, 2, 70.0f, 0.1f, 4.0f, 0.001f) { Parent = resolution }; @@ -663,7 +666,7 @@ namespace FlaxEditor.Viewport { var invert = ViewWidgetButtonMenu.AddButton("Invert Panning"); invert.CloseMenuOnClick = false; - var invertValue = new CheckBox(90, 2, _invertPanning) + var invertValue = new CheckBox(xLocationForExtras, 2, _invertPanning) { Parent = invert }; From bdd182f3da273321cc3514624e1537eb5bc56c5a Mon Sep 17 00:00:00 2001 From: Andrew Spiering Date: Sat, 16 Sep 2023 18:23:27 -0700 Subject: [PATCH 024/188] Fixing x64 editor/runtime on arm64 macs * This allows for a much more broader check for the dotnet sdk runtime to support running x64 binaries on arm64 --- Source/Engine/Scripting/Runtime/DotNet.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 1bcef50b8..2727593a0 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -1541,7 +1541,16 @@ bool InitHostfxr() get_hostfxr_params.size = sizeof(hostfxr_initialize_parameters); get_hostfxr_params.assembly_path = libraryPath.Get(); #if PLATFORM_MAC - get_hostfxr_params.dotnet_root = "/usr/local/share/dotnet"; + ::String macOSDotnetRoot = TEXT("/usr/local/share/dotnet"); +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(_M_X64) + // When emulating x64 on arm + const ::String dotnetRootEmulated = macOSDotnetRoot / TEXT("x64"); + if (FileSystem::FileExists(dotnetRootEmulated / TEXT("dotnet"))) { + macOSDotnetRoot = dotnetRootEmulated; + } +#endif + const FLAX_CORECLR_STRING& finalDotnetRootPath = FLAX_CORECLR_STRING(macOSDotnetRoot); + get_hostfxr_params.dotnet_root = finalDotnetRootPath.Get(); #else get_hostfxr_params.dotnet_root = nullptr; #endif From 831500faa7d6dd08956774492a7d8be9f67a2d6e Mon Sep 17 00:00:00 2001 From: Andrej Stojkovikj Date: Sun, 17 Sep 2023 23:16:08 +0200 Subject: [PATCH 025/188] Fix type definition for Vector2 and Vector3 --- Source/Engine/Core/Math/Vector2.cs | 30 ++++++++++++++-------------- Source/Engine/Core/Math/Vector3.cs | 32 +++++++++++++++--------------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Source/Engine/Core/Math/Vector2.cs b/Source/Engine/Core/Math/Vector2.cs index 9fe3c6944..cb0cd17d1 100644 --- a/Source/Engine/Core/Math/Vector2.cs +++ b/Source/Engine/Core/Math/Vector2.cs @@ -995,37 +995,37 @@ namespace FlaxEngine float b = a * deltaTime; float e = 1f / (1f + b + 0.48f * b * b + 0.235f * b * b * b); - float change_x = current.X - target.X; - float change_y = current.Y - target.Y; + Real change_x = current.X - target.X; + Real change_y = current.Y - target.Y; Vector2 originalTo = target; - float change = maxSpeed * smoothTime; - float changeSq = change * change; + float maxChangeSpeed = maxSpeed * smoothTime; + float changeSq = maxChangeSpeed * maxChangeSpeed; float sqrDist = change_x * change_x + change_y * change_y; if (sqrDist > changeSq) { - var dist = (float)Math.Sqrt(sqrDist); - change_x = change_x / dist * change; - change_y = change_y / dist * change; + var dist = (Real)Math.Sqrt(sqrDist); + change_x = change_x / dist * maxChangeSpeed; + change_y = change_y / dist * maxChangeSpeed; } target.X = current.X - change_x; target.Y = current.Y - change_y; - float temp_x = (currentVelocity.X + a * change_x) * deltaTime; - float temp_y = (currentVelocity.Y + a * change_y) * deltaTime; + Real temp_x = (currentVelocity.X + a * change_x) * deltaTime; + Real temp_y = (currentVelocity.Y + a * change_y) * deltaTime; currentVelocity.X = (currentVelocity.X - a * temp_x) * e; currentVelocity.Y = (currentVelocity.Y - a * temp_y) * e; - float output_x = target.X + (change_x + temp_x) * e; - float output_y = target.Y + (change_y + temp_y) * e; + Real output_x = target.X + (change_x + temp_x) * e; + Real output_y = target.Y + (change_y + temp_y) * e; - float x1 = originalTo.X - current.X; - float y1 = originalTo.Y - current.Y; + Real x1 = originalTo.X - current.X; + Real y1 = originalTo.Y - current.Y; - float x2 = output_x - originalTo.X; - float y2 = output_y - originalTo.Y; + Real x2 = output_x - originalTo.X; + Real y2 = output_y - originalTo.Y; if (x1 * x2 + y1 * y2 > 0) { diff --git a/Source/Engine/Core/Math/Vector3.cs b/Source/Engine/Core/Math/Vector3.cs index c1ea6bb67..53ec824fe 100644 --- a/Source/Engine/Core/Math/Vector3.cs +++ b/Source/Engine/Core/Math/Vector3.cs @@ -1084,9 +1084,9 @@ namespace FlaxEngine float b = a * deltaTime; float e = 1f / (1f + b + 0.48f * b * b + 0.235f * b * b * b); - float change_x = current.X - target.X; - float change_y = current.Y - target.Y; - float change_z = current.Z - target.Z; + Real change_x = current.X - target.X; + Real change_y = current.Y - target.Y; + Real change_z = current.Z - target.Z; Vector3 originalTo = target; @@ -1095,7 +1095,7 @@ namespace FlaxEngine float sqrMag = change_x * change_x + change_y * change_y + change_z * change_z; if (sqrMag > changeSq) { - var mag = (float)Math.Sqrt(sqrMag); + var mag = (Real)Math.Sqrt(sqrMag); change_x = change_x / mag * maxChangeSpeed; change_y = change_y / mag * maxChangeSpeed; change_z = change_z / mag * maxChangeSpeed; @@ -1105,25 +1105,25 @@ namespace FlaxEngine target.Y = current.Y - change_y; target.Z = current.Z - change_z; - float temp_x = (currentVelocity.X + a * change_x) * deltaTime; - float temp_y = (currentVelocity.Y + a * change_y) * deltaTime; - float temp_z = (currentVelocity.Z + a * change_z) * deltaTime; + Real temp_x = (currentVelocity.X + a * change_x) * deltaTime; + Real temp_y = (currentVelocity.Y + a * change_y) * deltaTime; + Real temp_z = (currentVelocity.Z + a * change_z) * deltaTime; currentVelocity.X = (currentVelocity.X - a * temp_x) * e; currentVelocity.Y = (currentVelocity.Y - a * temp_y) * e; currentVelocity.Z = (currentVelocity.Z - a * temp_z) * e; - float output_x = target.X + (change_x + temp_x) * e; - float output_y = target.Y + (change_y + temp_y) * e; - float output_z = target.Z + (change_z + temp_z) * e; + Real output_x = target.X + (change_x + temp_x) * e; + Real output_y = target.Y + (change_y + temp_y) * e; + Real output_z = target.Z + (change_z + temp_z) * e; - float x1 = originalTo.X - current.X; - float y1 = originalTo.Y - current.Y; - float z1 = originalTo.Z - current.Z; + Real x1 = originalTo.X - current.X; + Real y1 = originalTo.Y - current.Y; + Real z1 = originalTo.Z - current.Z; - float x2 = output_x - originalTo.X; - float y2 = output_y - originalTo.Y; - float z2 = output_z - originalTo.Z; + Real x2 = output_x - originalTo.X; + Real y2 = output_y - originalTo.Y; + Real z2 = output_z - originalTo.Z; if (x1 * x2 + y1 * y2 + z1 * z2 > 0) { From 76945b9144bf75ca10d913ea1cd29dd772313170 Mon Sep 17 00:00:00 2001 From: Andrew Spiering Date: Sun, 17 Sep 2023 14:56:22 -0700 Subject: [PATCH 026/188] MacOS Rider Support * Adding in code to properly look for rider on macOS * Also fixing an issue in the macOS CreateProcess where spaces where not properly escaped for popen(which one should likely use NSTask long term) --- .../Scripting/CodeEditors/RiderCodeEditor.cpp | 17 +++++++++++++++++ Source/Engine/Platform/Mac/MacPlatform.cpp | 3 +++ 2 files changed, 20 insertions(+) diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp index e28d1e381..41b91a629 100644 --- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp @@ -210,6 +210,23 @@ void RiderCodeEditor::FindEditors(Array* output) TEXT("flatpak run com.jetbrains.Rider")); #endif +#if PLATFORM_MAC + String applicationSupportFolder; + FileSystem::GetSpecialFolderPath(SpecialFolder::ProgramData, applicationSupportFolder); + + Array subMacDirectories; + + FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/Rider/ch-0/")); + FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/Rider/ch-1/")); + + for (auto directory : subMacDirectories) { + String riderAppDirectory = directory / TEXT("Rider.app/Contents/Resources"); + SearchDirectory(&installations, riderAppDirectory); + } + // Check the local installer version + SearchDirectory(&installations, TEXT("/Applications/Rider.app/Contents/Resources")); +#endif + for (auto directory : subDirectories) SearchDirectory(&installations, directory); diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp index 46b978e1d..8cba5e1dc 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.cpp +++ b/Source/Engine/Platform/Mac/MacPlatform.cpp @@ -462,6 +462,9 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings) } } + // Sanatize the string if the exePath has spaces with properly espcaped spaces for popen + exePath.Replace(TEXT(" "), TEXT("\\ ")); + const String cmdLine = exePath + TEXT(" ") + settings.Arguments; const StringAsANSI<> cmdLineAnsi(*cmdLine, cmdLine.Length()); FILE* pipe = popen(cmdLineAnsi.Get(), "r"); From 183ab7738fe55778ee693a4288a1d69a8f9f3bd7 Mon Sep 17 00:00:00 2001 From: Andrew Spiering Date: Sun, 17 Sep 2023 15:42:26 -0700 Subject: [PATCH 027/188] Fixing Flex Build issues on M1/2 macs * This resolves some issues where if you are building the actual C# dlls you also need them to be x64 based if you are targeting an x64 based target. This is a little complicated here because we set all this up ahead of time assuming that all the targets are compatible but in this case they are not so, just following what other places in the code are doing around this specifically dotnet AOT. --- Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs | 2 ++ Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs index 43c54a92d..5cbb9b661 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs @@ -207,6 +207,8 @@ namespace Flax.Build case TargetPlatform.Mac: { #if USE_NETCORE + dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet"); + cscPath = Path.Combine(dotnetSdk.RootPath, $"sdk/{dotnetSdk.VersionName}/Roslyn/bincore/csc.dll"); referenceAssemblies = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/ref/net{runtimeVersionShort}/"); referenceAnalyzers = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/analyzers/dotnet/cs/"); diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs index d3a706a43..c4894201b 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs @@ -212,6 +212,16 @@ namespace Flax.Build ridFallback = ""; if (string.IsNullOrEmpty(dotnetPath)) dotnetPath = "/usr/local/share/dotnet/"; + + // So there is not a real great way to do this but if the first thing in this list is x64 + // then lets assume we are building for it and thus should using that rid and that dotnet path + if (Configuration.BuildArchitectures != null && Configuration.BuildArchitectures[0] == TargetArchitecture.x64) + { + rid = $"osx-64"; + dotnetPath = Path.Combine(dotnetPath, "x64"); + architecture = TargetArchitecture.x64; + } + break; } default: throw new InvalidPlatformException(platform); From 69e54d7f88186ee7faf003d9bcd353b04e254aea Mon Sep 17 00:00:00 2001 From: Andrew Spiering Date: Sun, 17 Sep 2023 15:53:48 -0700 Subject: [PATCH 028/188] Fixing an issue if running an x64 machine already --- Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs index c4894201b..df7342420 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs @@ -215,7 +215,7 @@ namespace Flax.Build // So there is not a real great way to do this but if the first thing in this list is x64 // then lets assume we are building for it and thus should using that rid and that dotnet path - if (Configuration.BuildArchitectures != null && Configuration.BuildArchitectures[0] == TargetArchitecture.x64) + if (architecture == TargetArchitecture.ARM64 && (Configuration.BuildArchitectures != null && Configuration.BuildArchitectures[0] == TargetArchitecture.x64)) { rid = $"osx-64"; dotnetPath = Path.Combine(dotnetPath, "x64"); From 824ee9ec7f374a58547cc3a8e2fe4369a227420f Mon Sep 17 00:00:00 2001 From: Andrew Spiering Date: Sun, 17 Sep 2023 18:48:54 -0700 Subject: [PATCH 029/188] Fixing a typo :/ --- Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs index df7342420..fd2eb354d 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs @@ -217,7 +217,7 @@ namespace Flax.Build // then lets assume we are building for it and thus should using that rid and that dotnet path if (architecture == TargetArchitecture.ARM64 && (Configuration.BuildArchitectures != null && Configuration.BuildArchitectures[0] == TargetArchitecture.x64)) { - rid = $"osx-64"; + rid = $"osx-x64"; dotnetPath = Path.Combine(dotnetPath, "x64"); architecture = TargetArchitecture.x64; } From 3ac00b1e4e502dce2ee5d7f9b821bfe0131ff32e Mon Sep 17 00:00:00 2001 From: Andrew Spiering Date: Sun, 17 Sep 2023 20:37:37 -0700 Subject: [PATCH 030/188] Fixing a crash when generic classes spanned across different assemblies This code attempts to resolve an issue where if you had a class in Assembly A and it was generic and you had a class that inherited from it in Assembly B it would not properly resolve these classes, also added an error check for if I was not able to find the TypeIndex then log it and return thus also preventing a crash when it goes to resolve the NativeType --- Source/Engine/Scripting/BinaryModule.cpp | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 6ffdb14a9..667f41d85 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -1009,12 +1009,37 @@ void ManagedBinaryModule::InitType(MClass* mclass) } if (baseType.Module == this) InitType(baseClass); // Ensure base is initialized before + baseType.Module->TypeNameToTypeIndex.TryGet(baseClass->GetFullName(), *(int32*)&baseType.TypeIndex); + + // So we must special case this flow of a generic class of which its possible the generic base class is not + // in the same module + if (baseType.TypeIndex == -1 && baseClass->IsGeneric()) + { + auto genericNameIndex = baseClass->GetFullName().FindLast('`'); + // we add 2 because of the way generic names work its `N + auto genericClassName = baseClass->GetFullName().Substring(0, genericNameIndex + 2); + + // We check for the generic class name instead of the baseclass fullname + baseType.Module->TypeNameToTypeIndex.TryGet(genericClassName, *(int32*)&baseType.TypeIndex); + } + if (!baseType) { LOG(Error, "Missing base class for managed class {0} from assembly {1}.", String(typeName), Assembly->ToString()); return; } + + if (baseType.TypeIndex == -1) + { + if (baseType.Module) + LOG(Error, "Missing base class for managed class {0} from assembly {1}.", String(baseClass->GetFullName()), baseType.Module->GetName().ToString()); + else + // Not sure this can happen but never hurts to account for it + LOG(Error, "Missing base class for managed class {0} from unknown assembly.", String(baseClass->GetFullName())); + return; + } + ScriptingTypeHandle nativeType = baseType; while (true) { From 9f4429f87c8aa2f1c57c25e1eec3e51385c14e49 Mon Sep 17 00:00:00 2001 From: Andrew Spiering Date: Sun, 17 Sep 2023 22:26:50 -0700 Subject: [PATCH 031/188] When building the engine if you don't have iOS SDK Installed then don't build it Currently when you try and build macOS editor it assumes you also want to build iOS because of the way this check works which assumes if you have Xcode Installed you are ready to go. This really should not be the case, so instead lets check to see if you have the iophonesdk installed for your current Xcode if not then skip it. --- .../Flax.Build/Platforms/iOS/iOSPlatform.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs b/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs index 2d7a3372a..379616e5a 100644 --- a/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs @@ -1,5 +1,8 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System; +using System.IO; + namespace Flax.Build.Platforms { /// @@ -11,6 +14,9 @@ namespace Flax.Build.Platforms /// public override TargetPlatform Target => TargetPlatform.iOS; + /// + public override bool HasRequiredSDKsInstalled { get; } + /// public override bool HasDynamicCodeExecutionSupport => false; @@ -21,11 +27,19 @@ namespace Flax.Build.Platforms { if (Platform.BuildTargetPlatform != TargetPlatform.Mac) return; - if (!HasRequiredSDKsInstalled) + + if (!XCode.Instance.IsValid) { Log.Warning("Missing XCode. Cannot build for iOS platform."); return; } + + // We should check and see if the actual iphoneSDK is installed + string iphoneSDKPath = Utilities.ReadProcessOutput("/usr/bin/xcrun", "--sdk iphoneos --show-sdk-path"); + if (string.IsNullOrEmpty(iphoneSDKPath) || !Directory.Exists(iphoneSDKPath)) { + Log.Warning("Missing iPhoneSDK. Cannot build for iOS platform."); + HasRequiredSDKsInstalled = false; + } } /// From 97f595922eef0199ae06f0157c3eb4ae9e0fd817 Mon Sep 17 00:00:00 2001 From: Andrew Spiering Date: Sun, 17 Sep 2023 23:11:23 -0700 Subject: [PATCH 032/188] Fixing broken build Have to actually set it to true --- Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs b/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs index 379616e5a..49c3ec406 100644 --- a/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs @@ -40,6 +40,8 @@ namespace Flax.Build.Platforms Log.Warning("Missing iPhoneSDK. Cannot build for iOS platform."); HasRequiredSDKsInstalled = false; } + else + HasRequiredSDKsInstalled = true; } /// From e38a8bda7a18c7d0e5dfc3408d892f4f8b3ed3b4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 18 Sep 2023 11:26:56 +0200 Subject: [PATCH 033/188] Codestyle fix for #1425 --- Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs b/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs index 49c3ec406..d4eaa5006 100644 --- a/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs @@ -1,6 +1,5 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. -using System; using System.IO; namespace Flax.Build.Platforms @@ -27,7 +26,6 @@ namespace Flax.Build.Platforms { if (Platform.BuildTargetPlatform != TargetPlatform.Mac) return; - if (!XCode.Instance.IsValid) { Log.Warning("Missing XCode. Cannot build for iOS platform."); @@ -36,11 +34,12 @@ namespace Flax.Build.Platforms // We should check and see if the actual iphoneSDK is installed string iphoneSDKPath = Utilities.ReadProcessOutput("/usr/bin/xcrun", "--sdk iphoneos --show-sdk-path"); - if (string.IsNullOrEmpty(iphoneSDKPath) || !Directory.Exists(iphoneSDKPath)) { + if (string.IsNullOrEmpty(iphoneSDKPath) || !Directory.Exists(iphoneSDKPath)) + { Log.Warning("Missing iPhoneSDK. Cannot build for iOS platform."); HasRequiredSDKsInstalled = false; } - else + else HasRequiredSDKsInstalled = true; } From 79bf226fe1070fcea642a2f02b7cb049cdf43c96 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 18 Sep 2023 12:11:28 +0200 Subject: [PATCH 034/188] Codestyle fix --- Source/Engine/Scripting/BinaryModule.cpp | 3 +-- Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs | 1 - Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs | 5 ++--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 667f41d85..8f1bb27d0 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -1012,8 +1012,7 @@ void ManagedBinaryModule::InitType(MClass* mclass) baseType.Module->TypeNameToTypeIndex.TryGet(baseClass->GetFullName(), *(int32*)&baseType.TypeIndex); - // So we must special case this flow of a generic class of which its possible the generic base class is not - // in the same module + // So we must special case this flow of a generic class of which its possible the generic base class is not in the same module if (baseType.TypeIndex == -1 && baseClass->IsGeneric()) { auto genericNameIndex = baseClass->GetFullName().FindLast('`'); diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs index 5cbb9b661..c64ee38b3 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs @@ -208,7 +208,6 @@ namespace Flax.Build { #if USE_NETCORE dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet"); - cscPath = Path.Combine(dotnetSdk.RootPath, $"sdk/{dotnetSdk.VersionName}/Roslyn/bincore/csc.dll"); referenceAssemblies = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/ref/net{runtimeVersionShort}/"); referenceAnalyzers = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/analyzers/dotnet/cs/"); diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs index fd2eb354d..dee92d384 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs @@ -213,11 +213,10 @@ namespace Flax.Build if (string.IsNullOrEmpty(dotnetPath)) dotnetPath = "/usr/local/share/dotnet/"; - // So there is not a real great way to do this but if the first thing in this list is x64 - // then lets assume we are building for it and thus should using that rid and that dotnet path + // Use x64 when cross-compiling from ARM64 if (architecture == TargetArchitecture.ARM64 && (Configuration.BuildArchitectures != null && Configuration.BuildArchitectures[0] == TargetArchitecture.x64)) { - rid = $"osx-x64"; + rid = "osx-x64"; dotnetPath = Path.Combine(dotnetPath, "x64"); architecture = TargetArchitecture.x64; } From 03b8b9f73f28d79485039d518d2a7713a1ee3c6b Mon Sep 17 00:00:00 2001 From: Walrusking Date: Sat, 16 Sep 2023 13:48:49 -0400 Subject: [PATCH 035/188] Fix rider search detecting any jetbrains product as rider --- Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp index 41b91a629..5ed73cedf 100644 --- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp @@ -44,6 +44,10 @@ namespace if (document.HasParseError()) return; + // Check if this is actually rider and not another jetbrains product + if (document.FindMember("name")->value != "JetBrains Rider") + return; + // Find version auto versionMember = document.FindMember("version"); if (versionMember == document.MemberEnd()) From 4b0e70aa06ef8df3d7950d58f4c7450674673b31 Mon Sep 17 00:00:00 2001 From: Walrusking Date: Fri, 15 Sep 2023 01:31:33 -0400 Subject: [PATCH 036/188] Fixes Rider 2022 not detected when installed with toolbox --- Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp index 5ed73cedf..9764b06a6 100644 --- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp @@ -191,6 +191,7 @@ void RiderCodeEditor::FindEditors(Array* output) SearchRegistry(&installations, HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\WOW6432Node\\JetBrains\\JetBrains Rider")); // Versions installed via JetBrains Toolbox + FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("Programs")); FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains\\Toolbox\\apps\\Rider\\ch-0\\")); FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains\\Toolbox\\apps\\Rider\\ch-1\\")); // Beta versions #endif From 4fe02a24e952ae90499019993ac7b2bbc960bd35 Mon Sep 17 00:00:00 2001 From: elestrago Date: Fri, 15 Sep 2023 02:35:20 +0200 Subject: [PATCH 037/188] Fix find Rider versions 2022 and later --- Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp index 9764b06a6..66065f0f3 100644 --- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp @@ -206,6 +206,7 @@ void RiderCodeEditor::FindEditors(Array* output) FileSystem::GetChildDirectories(subDirectories, TEXT("/opt/")); // Versions installed via JetBrains Toolbox + SearchDirectory(&installations, localAppDataPath / TEXT(".local/share/JetBrains/Toolbox/apps/rider/")); FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT(".local/share/JetBrains/Toolbox/apps/Rider/ch-0")); FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT(".local/share/JetBrains/Toolbox/apps/Rider/ch-1")); // Beta versions From 960b825b4cd7168cc12df041e92cb9b60c30fe50 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 18 Sep 2023 12:21:12 +0200 Subject: [PATCH 038/188] Code style fix --- .../Scripting/CodeEditors/RiderCodeEditor.cpp | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp index 66065f0f3..05608d71e 100644 --- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp @@ -24,7 +24,8 @@ namespace String version; RiderInstallation(const String& path_, const String& version_) - : path(path_), version(version_) + : path(path_) + , version(version_) { } }; @@ -145,14 +146,14 @@ bool sortInstallations(RiderInstallation* const& i1, RiderInstallation* const& i int32 version2[3] = { 0 }; StringUtils::Parse(values1[0].Get(), &version1[0]); StringUtils::Parse(values1[1].Get(), &version1[1]); - - if(values1.Count() > 2) + + if (values1.Count() > 2) StringUtils::Parse(values1[2].Get(), &version1[2]); - + StringUtils::Parse(values2[0].Get(), &version2[0]); StringUtils::Parse(values2[1].Get(), &version2[1]); - - if(values2.Count() > 2) + + if (values2.Count() > 2) StringUtils::Parse(values2[2].Get(), &version2[2]); // Compare by MAJOR.MINOR.BUILD @@ -178,7 +179,7 @@ void RiderCodeEditor::FindEditors(Array* output) String localAppDataPath; FileSystem::GetSpecialFolderPath(SpecialFolder::LocalAppData, localAppDataPath); - + #if PLATFORM_WINDOWS // Lookup from all known registry locations SearchRegistry(&installations, HKEY_CURRENT_USER, TEXT("SOFTWARE\\WOW6432Node\\JetBrains\\Rider for Unreal Engine")); @@ -219,21 +220,21 @@ void RiderCodeEditor::FindEditors(Array* output) #if PLATFORM_MAC String applicationSupportFolder; FileSystem::GetSpecialFolderPath(SpecialFolder::ProgramData, applicationSupportFolder); - - Array subMacDirectories; + Array subMacDirectories; FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/Rider/ch-0/")); FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/Rider/ch-1/")); - - for (auto directory : subMacDirectories) { + for (const String& directory : subMacDirectories) + { String riderAppDirectory = directory / TEXT("Rider.app/Contents/Resources"); SearchDirectory(&installations, riderAppDirectory); } + // Check the local installer version SearchDirectory(&installations, TEXT("/Applications/Rider.app/Contents/Resources")); #endif - for (auto directory : subDirectories) + for (const String& directory : subDirectories) SearchDirectory(&installations, directory); // Sort found installations by version number From 10dc06be9bc9e085a83bd5c7fe6347eba6ee5c95 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 18 Sep 2023 19:23:10 +0200 Subject: [PATCH 039/188] Fix crash if `OpenAL` internal device name is all whitespaces --- Source/Engine/Core/Types/String.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Core/Types/String.cpp b/Source/Engine/Core/Types/String.cpp index e53f04af7..98b2c08dd 100644 --- a/Source/Engine/Core/Types/String.cpp +++ b/Source/Engine/Core/Types/String.cpp @@ -298,8 +298,10 @@ String String::TrimTrailing() const end--; } - ASSERT_LOW_LAYER(end >= start); - return Substring(start, end - start + 1); + const int32 count = end - start + 1; + if (start >= 0 && start + count <= Length() && count >= 0) + return String(_data + start, count); + return Empty; } String& String::operator/=(const Char* str) From 0083ebd8871b45a3a5053f6f0258d6ed2396c7ba Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 19 Sep 2023 09:32:37 +0200 Subject: [PATCH 040/188] Fix missing C# static fields in native scripting api --- Source/Engine/Engine/NativeInterop.Unmanaged.cs | 6 +++--- Source/Engine/Scripting/Runtime/DotNet.cpp | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index e96839546..670b7fff5 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -297,7 +297,7 @@ namespace FlaxEngine.Interop internal static void GetClassFields(ManagedHandle typeHandle, NativeFieldDefinitions** classFields, int* classFieldsCount) { Type type = Unsafe.As(typeHandle.Target); - var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + var fields = type.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); NativeFieldDefinitions* arr = (NativeFieldDefinitions*)NativeAlloc(fields.Length, Unsafe.SizeOf()); for (int i = 0; i < fields.Length; i++) @@ -331,7 +331,7 @@ namespace FlaxEngine.Interop internal static void GetClassProperties(ManagedHandle typeHandle, NativePropertyDefinitions** classProperties, int* classPropertiesCount) { Type type = Unsafe.As(typeHandle.Target); - var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + var properties = type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); var arr = (NativePropertyDefinitions*)NativeAlloc(properties.Length, Unsafe.SizeOf()); for (int i = 0; i < properties.Length; i++) @@ -804,8 +804,8 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static IntPtr FieldGetValueBoxed(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle) { - object fieldOwner = fieldOwnerHandle.Target; FieldHolder field = Unsafe.As(fieldHandle.Target); + object fieldOwner = field.field.IsStatic ? null : fieldOwnerHandle.Target; object fieldValue = field.field.GetValue(fieldOwner); return Invoker.MarshalReturnValueGeneric(field.field.FieldType, fieldValue); } diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 8c87a5748..2157e8dff 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -905,7 +905,6 @@ const Array& MClass::GetMethods() const NativeMethodDefinitions& definition = methods[i]; MMethod* method = New(const_cast(this), StringAnsi(definition.name), definition.handle, definition.numParameters, definition.methodAttributes); _methods.Add(method); - MCore::GC::FreeMemory((void*)definition.name); } MCore::GC::FreeMemory(methods); @@ -939,7 +938,6 @@ const Array& MClass::GetFields() const NativeFieldDefinitions& definition = fields[i]; MField* field = New(const_cast(this), definition.fieldHandle, definition.name, definition.fieldType, definition.fieldAttributes); _fields.Add(field); - MCore::GC::FreeMemory((void*)definition.name); } MCore::GC::FreeMemory(fields); @@ -984,7 +982,6 @@ const Array& MClass::GetProperties() const const NativePropertyDefinitions& definition = foundProperties[i]; MProperty* property = New(const_cast(this), definition.name, definition.getterHandle, definition.setterHandle, definition.getterAttributes, definition.setterAttributes); _properties.Add(property); - MCore::GC::FreeMemory((void*)definition.name); } MCore::GC::FreeMemory(foundProperties); From bb5ff740bf7b53ebd6b324f10ba353f2f7146e46 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 19 Sep 2023 13:34:32 +0200 Subject: [PATCH 041/188] Update `recastnavigation` lib to `1.6` --- Source/Engine/Navigation/NavMeshRuntime.cpp | 1 + .../recastnavigation/DetourAssert.h | 2 +- .../recastnavigation/DetourCommon.cpp | 9 +- .../recastnavigation/DetourCommon.h | 5 +- .../recastnavigation/DetourCrowd.cpp | 1 - .../ThirdParty/recastnavigation/DetourCrowd.h | 6 +- .../recastnavigation/DetourNavMesh.cpp | 8 +- .../recastnavigation/DetourNavMesh.h | 20 +- .../recastnavigation/DetourNavMeshQuery.cpp | 46 +- .../recastnavigation/DetourNavMeshQuery.h | 9 +- .../ThirdParty/recastnavigation/DetourNode.h | 2 +- .../recastnavigation/DetourTileCache.cpp | 5 + .../recastnavigation/DetourTileCache.h | 18 +- .../DetourTileCacheBuilder.cpp | 15 +- .../recastnavigation/DetourTileCacheBuilder.h | 4 +- Source/ThirdParty/recastnavigation/Recast.cpp | 637 ++++++------ Source/ThirdParty/recastnavigation/Recast.h | 974 ++++++++++-------- .../recastnavigation/RecastAlloc.cpp | 19 +- .../ThirdParty/recastnavigation/RecastAlloc.h | 35 +- .../recastnavigation/RecastArea.cpp | 1 - .../recastnavigation/RecastAssert.cpp | 2 +- .../recastnavigation/RecastAssert.h | 13 +- .../recastnavigation/RecastContour.cpp | 11 +- .../recastnavigation/RecastFilter.cpp | 198 ++-- .../recastnavigation/RecastLayers.cpp | 20 +- .../recastnavigation/RecastMesh.cpp | 7 +- .../recastnavigation/RecastMeshDetail.cpp | 3 +- .../recastnavigation/RecastRasterization.cpp | 664 +++++++----- .../recastnavigation/RecastRegion.cpp | 1 - 29 files changed, 1461 insertions(+), 1275 deletions(-) diff --git a/Source/Engine/Navigation/NavMeshRuntime.cpp b/Source/Engine/Navigation/NavMeshRuntime.cpp index b09f8346a..acb73a8d5 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.cpp +++ b/Source/Engine/Navigation/NavMeshRuntime.cpp @@ -21,6 +21,7 @@ namespace FORCE_INLINE void InitFilter(dtQueryFilter& filter) { Platform::MemoryCopy(filter.m_areaCost, NavMeshRuntime::NavAreasCosts, sizeof(NavMeshRuntime::NavAreasCosts)); + static_assert(sizeof(dtQueryFilter::m_areaCost) == sizeof(NavMeshRuntime::NavAreasCosts), "Invalid navmesh area cost list."); } } diff --git a/Source/ThirdParty/recastnavigation/DetourAssert.h b/Source/ThirdParty/recastnavigation/DetourAssert.h index e05fd66fa..038d538a5 100644 --- a/Source/ThirdParty/recastnavigation/DetourAssert.h +++ b/Source/ThirdParty/recastnavigation/DetourAssert.h @@ -24,7 +24,7 @@ #ifdef NDEBUG -// From http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ +// From https://web.archive.org/web/20210117002833/http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ # define dtAssert(x) do { (void)sizeof(x); } while((void)(__LINE__==-1),false) #else diff --git a/Source/ThirdParty/recastnavigation/DetourCommon.cpp b/Source/ThirdParty/recastnavigation/DetourCommon.cpp index 7abdff072..b8457828e 100644 --- a/Source/ThirdParty/recastnavigation/DetourCommon.cpp +++ b/Source/ThirdParty/recastnavigation/DetourCommon.cpp @@ -112,7 +112,7 @@ bool dtIntersectSegmentPoly2D(const float* p0, const float* p1, float& tmin, float& tmax, int& segMin, int& segMax) { - static const float EPS = 0.00000001f; + static const float EPS = 0.000001f; tmin = 0; tmax = 1; @@ -183,13 +183,6 @@ float dtDistancePtSegSqr2D(const float* pt, const float* p, const float* q, floa return dx*dx + dz*dz; } -float dtDistancePtPtSqr2D(const float* pt, const float* p) -{ - float dx = pt[0] - p[0]; - float dz = pt[2] - p[2]; - return dx*dx + dz*dz; -} - void dtCalcPolyCenter(float* tc, const unsigned short* idx, int nidx, const float* verts) { tc[0] = 0.0f; diff --git a/Source/ThirdParty/recastnavigation/DetourCommon.h b/Source/ThirdParty/recastnavigation/DetourCommon.h index a2597f3a2..d54bc7edc 100644 --- a/Source/ThirdParty/recastnavigation/DetourCommon.h +++ b/Source/ThirdParty/recastnavigation/DetourCommon.h @@ -37,7 +37,6 @@ feature to find minor members. /// Used to ignore a function parameter. VS complains about unused parameters /// and this silences the warning. -/// @param [in] _ Unused parameter template void dtIgnoreUnused(const T&) { } /// Swaps the values of the two parameters. @@ -319,7 +318,7 @@ inline float dtVdot2D(const float* u, const float* v) /// Derives the xz-plane 2D perp product of the two vectors. (uz*vx - ux*vz) /// @param[in] u The LHV vector [(x, y, z)] /// @param[in] v The RHV vector [(x, y, z)] -/// @return The dot product on the xz-plane. +/// @return The perp dot product on the xz-plane. /// /// The vectors are projected onto the xz-plane, so the y-values are ignored. inline float dtVperp2D(const float* u, const float* v) @@ -417,8 +416,6 @@ bool dtDistancePtPolyEdgesSqr(const float* pt, const float* verts, const int nve float dtDistancePtSegSqr2D(const float* pt, const float* p, const float* q, float& t); -float dtDistancePtPtSqr2D(const float* pt, const float* p); - /// Derives the centroid of a convex polygon. /// @param[out] tc The centroid of the polgyon. [(x, y, z)] /// @param[in] idx The polygon indices. [(vertIndex) * @p nidx] diff --git a/Source/ThirdParty/recastnavigation/DetourCrowd.cpp b/Source/ThirdParty/recastnavigation/DetourCrowd.cpp index 3f0311f7f..d7cdae0b9 100644 --- a/Source/ThirdParty/recastnavigation/DetourCrowd.cpp +++ b/Source/ThirdParty/recastnavigation/DetourCrowd.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include diff --git a/Source/ThirdParty/recastnavigation/DetourCrowd.h b/Source/ThirdParty/recastnavigation/DetourCrowd.h index 952050878..854546fc1 100644 --- a/Source/ThirdParty/recastnavigation/DetourCrowd.h +++ b/Source/ThirdParty/recastnavigation/DetourCrowd.h @@ -66,7 +66,7 @@ enum CrowdAgentState { DT_CROWDAGENT_STATE_INVALID, ///< The agent is not in a valid state. DT_CROWDAGENT_STATE_WALKING, ///< The agent is traversing a normal navigation mesh polygon. - DT_CROWDAGENT_STATE_OFFMESH, ///< The agent is traversing an off-mesh connection. + DT_CROWDAGENT_STATE_OFFMESH ///< The agent is traversing an off-mesh connection. }; /// Configuration parameters for a crowd agent. @@ -108,7 +108,7 @@ enum MoveRequestState DT_CROWDAGENT_TARGET_REQUESTING, DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE, DT_CROWDAGENT_TARGET_WAITING_FOR_PATH, - DT_CROWDAGENT_TARGET_VELOCITY, + DT_CROWDAGENT_TARGET_VELOCITY }; /// Represents an agent managed by a #dtCrowd object. @@ -188,7 +188,7 @@ enum UpdateFlags DT_CROWD_OBSTACLE_AVOIDANCE = 2, DT_CROWD_SEPARATION = 4, DT_CROWD_OPTIMIZE_VIS = 8, ///< Use #dtPathCorridor::optimizePathVisibility() to optimize the agent path. - DT_CROWD_OPTIMIZE_TOPO = 16, ///< Use dtPathCorridor::optimizePathTopology() to optimize the agent path. + DT_CROWD_OPTIMIZE_TOPO = 16 ///< Use dtPathCorridor::optimizePathTopology() to optimize the agent path. }; struct dtCrowdAgentDebugInfo diff --git a/Source/ThirdParty/recastnavigation/DetourNavMesh.cpp b/Source/ThirdParty/recastnavigation/DetourNavMesh.cpp index b119cd541..2240526eb 100644 --- a/Source/ThirdParty/recastnavigation/DetourNavMesh.cpp +++ b/Source/ThirdParty/recastnavigation/DetourNavMesh.cpp @@ -433,8 +433,8 @@ void dtNavMesh::connectExtLinks(dtMeshTile* tile, dtMeshTile* target, int side) float tmax = (neia[k*2+1]-va[2]) / (vb[2]-va[2]); if (tmin > tmax) dtSwap(tmin,tmax); - link->bmin = (unsigned char)(dtClamp(tmin, 0.0f, 1.0f)*255.0f); - link->bmax = (unsigned char)(dtClamp(tmax, 0.0f, 1.0f)*255.0f); + link->bmin = (unsigned char)roundf(dtClamp(tmin, 0.0f, 1.0f)*255.0f); + link->bmax = (unsigned char)roundf(dtClamp(tmax, 0.0f, 1.0f)*255.0f); } else if (dir == 2 || dir == 6) { @@ -442,8 +442,8 @@ void dtNavMesh::connectExtLinks(dtMeshTile* tile, dtMeshTile* target, int side) float tmax = (neia[k*2+1]-va[0]) / (vb[0]-va[0]); if (tmin > tmax) dtSwap(tmin,tmax); - link->bmin = (unsigned char)(dtClamp(tmin, 0.0f, 1.0f)*255.0f); - link->bmax = (unsigned char)(dtClamp(tmax, 0.0f, 1.0f)*255.0f); + link->bmin = (unsigned char)roundf(dtClamp(tmin, 0.0f, 1.0f)*255.0f); + link->bmax = (unsigned char)roundf(dtClamp(tmax, 0.0f, 1.0f)*255.0f); } } } diff --git a/Source/ThirdParty/recastnavigation/DetourNavMesh.h b/Source/ThirdParty/recastnavigation/DetourNavMesh.h index 9ac1dc8d6..4c1277d78 100644 --- a/Source/ThirdParty/recastnavigation/DetourNavMesh.h +++ b/Source/ThirdParty/recastnavigation/DetourNavMesh.h @@ -99,7 +99,7 @@ static const int DT_MAX_AREAS = 64; enum dtTileFlags { /// The navigation mesh owns the tile memory and is responsible for freeing it. - DT_TILE_FREE_DATA = 0x01, + DT_TILE_FREE_DATA = 0x01 }; /// Vertex flags returned by dtNavMeshQuery::findStraightPath. @@ -107,32 +107,32 @@ enum dtStraightPathFlags { DT_STRAIGHTPATH_START = 0x01, ///< The vertex is the start position in the path. DT_STRAIGHTPATH_END = 0x02, ///< The vertex is the end position in the path. - DT_STRAIGHTPATH_OFFMESH_CONNECTION = 0x04, ///< The vertex is the start of an off-mesh connection. + DT_STRAIGHTPATH_OFFMESH_CONNECTION = 0x04 ///< The vertex is the start of an off-mesh connection. }; /// Options for dtNavMeshQuery::findStraightPath. enum dtStraightPathOptions { DT_STRAIGHTPATH_AREA_CROSSINGS = 0x01, ///< Add a vertex at every polygon edge crossing where area changes. - DT_STRAIGHTPATH_ALL_CROSSINGS = 0x02, ///< Add a vertex at every polygon edge crossing. + DT_STRAIGHTPATH_ALL_CROSSINGS = 0x02 ///< Add a vertex at every polygon edge crossing. }; /// Options for dtNavMeshQuery::initSlicedFindPath and updateSlicedFindPath enum dtFindPathOptions { - DT_FINDPATH_ANY_ANGLE = 0x02, ///< use raycasts during pathfind to "shortcut" (raycast still consider costs) + DT_FINDPATH_ANY_ANGLE = 0x02 ///< use raycasts during pathfind to "shortcut" (raycast still consider costs) }; /// Options for dtNavMeshQuery::raycast enum dtRaycastOptions { - DT_RAYCAST_USE_COSTS = 0x01, ///< Raycast should calculate movement cost along the ray and fill RaycastHit::cost + DT_RAYCAST_USE_COSTS = 0x01 ///< Raycast should calculate movement cost along the ray and fill RaycastHit::cost }; enum dtDetailTriEdgeFlags { - DT_DETAIL_EDGE_BOUNDARY = 0x01, ///< Detail triangle edge is part of the poly boundary + DT_DETAIL_EDGE_BOUNDARY = 0x01 ///< Detail triangle edge is part of the poly boundary }; @@ -146,7 +146,7 @@ enum dtPolyTypes /// The polygon is a standard convex polygon that is part of the surface of the mesh. DT_POLYTYPE_GROUND = 0, /// The polygon is an off-mesh connection consisting of two vertices. - DT_POLYTYPE_OFFMESH_CONNECTION = 1, + DT_POLYTYPE_OFFMESH_CONNECTION = 1 }; @@ -285,7 +285,7 @@ struct dtMeshTile unsigned int linksFreeList; ///< Index to the next free link. dtMeshHeader* header; ///< The tile header. dtPoly* polys; ///< The tile polygons. [Size: dtMeshHeader::polyCount] - float* verts; ///< The tile vertices. [Size: dtMeshHeader::vertCount] + float* verts; ///< The tile vertices. [(x, y, z) * dtMeshHeader::vertCount] dtLink* links; ///< The tile links. [Size: dtMeshHeader::maxLinkCount] dtPolyDetail* detailMeshes; ///< The tile's detail sub-meshes. [Size: dtMeshHeader::detailMeshCount] @@ -312,8 +312,8 @@ private: }; /// Get flags for edge in detail triangle. -/// @param triFlags[in] The flags for the triangle (last component of detail vertices above). -/// @param edgeIndex[in] The index of the first vertex of the edge. For instance, if 0, +/// @param[in] triFlags The flags for the triangle (last component of detail vertices above). +/// @param[in] edgeIndex The index of the first vertex of the edge. For instance, if 0, /// returns flags for edge AB. inline int dtGetDetailTriEdgeFlags(unsigned char triFlags, int edgeIndex) { diff --git a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.cpp b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.cpp index 1e519d211..cebe26b5d 100644 --- a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.cpp +++ b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.cpp @@ -117,6 +117,11 @@ void dtFreeNavMeshQuery(dtNavMeshQuery* navmesh) dtFree(navmesh); } +dtPolyQuery::~dtPolyQuery() +{ + // Defined out of line to fix the weak v-tables warning +} + ////////////////////////////////////////////////////////////////////////////////////////// /// @class dtNavMeshQuery @@ -301,11 +306,7 @@ dtStatus dtNavMeshQuery::findRandomPoint(const dtQueryFilter* filter, float (*fr float pt[3]; dtRandomPointInConvexPoly(verts, poly->vertCount, areas, s, t, pt); - float h = 0.0f; - dtStatus status = getPolyHeight(polyRef, pt, &h); - if (dtStatusFailed(status)) - return status; - pt[1] = h; + closestPointOnPoly(polyRef, pt, pt, NULL); dtVcopy(randomPt, pt); *randomRef = polyRef; @@ -482,25 +483,18 @@ dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const f dtVcopy(&verts[j*3],v); } + const float s = frand(); + const float t = frand(); + float pt[3]; - do - { - const float s = frand(); - const float t = frand(); - dtRandomPointInConvexPoly(verts, randomPoly->vertCount, areas, s, t, pt); - } - while (dtDistancePtPtSqr2D(centerPos, pt) > radiusSqr); - - float h = 0.0f; - dtStatus stat = getPolyHeight(randomPolyRef, pt, &h); - if (dtStatusFailed(status)) - return stat; - pt[1] = h; + dtRandomPointInConvexPoly(verts, randomPoly->vertCount, areas, s, t, pt); + + closestPointOnPoly(randomPolyRef, pt, pt, NULL); dtVcopy(randomPt, pt); *randomRef = randomPolyRef; - return DT_SUCCESS; + return status; } @@ -641,6 +635,8 @@ public: { } + virtual ~dtFindNearestPolyQuery(); + dtPolyRef nearestRef() const { return m_nearestRef; } const float* nearestPoint() const { return m_nearestPoint; } bool isOverPoly() const { return m_overPoly; } @@ -683,6 +679,11 @@ public: } }; +dtFindNearestPolyQuery::~dtFindNearestPolyQuery() +{ + // Defined out of line to fix the weak v-tables warning +} + /// @par /// /// @note If the search box does not intersect any polygons the search will @@ -858,6 +859,8 @@ public: { } + virtual ~dtCollectPolysQuery(); + int numCollected() const { return m_numCollected; } bool overflowed() const { return m_overflow; } @@ -879,6 +882,11 @@ public: } }; +dtCollectPolysQuery::~dtCollectPolysQuery() +{ + // Defined out of line to fix the weak v-tables warning +} + /// @par /// /// If no polygons are found, the function will return #DT_SUCCESS with a diff --git a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h index 43c7268f2..244b20065 100644 --- a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h +++ b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h @@ -153,7 +153,7 @@ struct dtRaycastHit class dtPolyQuery { public: - virtual ~dtPolyQuery() { } + virtual ~dtPolyQuery(); /// Called for each batch of unique polygons touched by the search area in dtNavMeshQuery::queryPolygons. /// This can be called multiple times for a single query. @@ -176,7 +176,7 @@ public: dtStatus init(const dtNavMesh* nav, const int maxNodes); /// @name Standard Pathfinding Functions - // /@{ + /// @{ /// Finds a path from the start polygon to the end polygon. /// @param[in] startRef The refrence id of the start polygon. @@ -397,9 +397,9 @@ public: /// @param[in] startPos A position within the start polygon representing /// the start of the ray. [(x, y, z)] /// @param[in] endPos The position to cast the ray toward. [(x, y, z)] + /// @param[in] filter The polygon filter to apply to the query. /// @param[out] t The hit parameter. (FLT_MAX if no wall hit.) /// @param[out] hitNormal The normal of the nearest wall hit. [(x, y, z)] - /// @param[in] filter The polygon filter to apply to the query. /// @param[out] path The reference ids of the visited polygons. [opt] /// @param[out] pathCount The number of visited polygons. [opt] /// @param[in] maxPath The maximum number of polygons the @p path array can hold. @@ -415,7 +415,7 @@ public: /// the start of the ray. [(x, y, z)] /// @param[in] endPos The position to cast the ray toward. [(x, y, z)] /// @param[in] filter The polygon filter to apply to the query. - /// @param[in] flags govern how the raycast behaves. See dtRaycastOptions + /// @param[in] options govern how the raycast behaves. See dtRaycastOptions /// @param[out] hit Pointer to a raycast hit structure which will be filled by the results. /// @param[in] prevRef parent of start ref. Used during for cost calculation [opt] /// @returns The status flags for the query. @@ -466,6 +466,7 @@ public: /// The location is not exactly constrained by the circle, but it limits the visited polygons. /// @param[in] startRef The reference id of the polygon where the search starts. /// @param[in] centerPos The center of the search circle. [(x, y, z)] + /// @param[in] maxRadius The radius of the search circle. [Units: wu] /// @param[in] filter The polygon filter to apply to the query. /// @param[in] frand Function returning a random number [0..1). /// @param[out] randomRef The reference id of the random location. diff --git a/Source/ThirdParty/recastnavigation/DetourNode.h b/Source/ThirdParty/recastnavigation/DetourNode.h index db0974708..8918d4686 100644 --- a/Source/ThirdParty/recastnavigation/DetourNode.h +++ b/Source/ThirdParty/recastnavigation/DetourNode.h @@ -25,7 +25,7 @@ enum dtNodeFlags { DT_NODE_OPEN = 0x01, DT_NODE_CLOSED = 0x02, - DT_NODE_PARENT_DETACHED = 0x04, // parent of the node is not adjacent. Found using raycast. + DT_NODE_PARENT_DETACHED = 0x04 // parent of the node is not adjacent. Found using raycast. }; typedef unsigned short dtNodeIndex; diff --git a/Source/ThirdParty/recastnavigation/DetourTileCache.cpp b/Source/ThirdParty/recastnavigation/DetourTileCache.cpp index a82cd1350..6f97ab5b7 100644 --- a/Source/ThirdParty/recastnavigation/DetourTileCache.cpp +++ b/Source/ThirdParty/recastnavigation/DetourTileCache.cpp @@ -239,6 +239,11 @@ const dtTileCacheObstacle* dtTileCache::getObstacleByRef(dtObstacleRef ref) return ob; } +dtTileCacheMeshProcess::~dtTileCacheMeshProcess() +{ + // Defined out of line to fix the weak v-tables warning +} + dtStatus dtTileCache::addTile(unsigned char* data, const int dataSize, unsigned char flags, dtCompressedTileRef* result) { // Make sure the data is in right format. diff --git a/Source/ThirdParty/recastnavigation/DetourTileCache.h b/Source/ThirdParty/recastnavigation/DetourTileCache.h index 75713366d..0d346f17c 100644 --- a/Source/ThirdParty/recastnavigation/DetourTileCache.h +++ b/Source/ThirdParty/recastnavigation/DetourTileCache.h @@ -3,16 +3,13 @@ #include "DetourStatus.h" - - typedef unsigned int dtObstacleRef; - typedef unsigned int dtCompressedTileRef; /// Flags for addTile enum dtCompressedTileFlags { - DT_COMPRESSEDTILE_FREE_DATA = 0x01, ///< Navmesh owns the tile memory and should free it. + DT_COMPRESSEDTILE_FREE_DATA = 0x01 ///< Navmesh owns the tile memory and should free it. }; struct dtCompressedTile @@ -32,14 +29,14 @@ enum ObstacleState DT_OBSTACLE_EMPTY, DT_OBSTACLE_PROCESSING, DT_OBSTACLE_PROCESSED, - DT_OBSTACLE_REMOVING, + DT_OBSTACLE_REMOVING }; enum ObstacleType { DT_OBSTACLE_CYLINDER, DT_OBSTACLE_BOX, // AABB - DT_OBSTACLE_ORIENTED_BOX, // OBB + DT_OBSTACLE_ORIENTED_BOX // OBB }; struct dtObstacleCylinder @@ -97,13 +94,10 @@ struct dtTileCacheParams struct dtTileCacheMeshProcess { - virtual ~dtTileCacheMeshProcess() { } - - virtual void process(struct dtNavMeshCreateParams* params, - unsigned char* polyAreas, unsigned short* polyFlags) = 0; + virtual ~dtTileCacheMeshProcess(); + virtual void process(struct dtNavMeshCreateParams* params, unsigned char* polyAreas, unsigned short* polyFlags) = 0; }; - class dtTileCache { public: @@ -219,7 +213,7 @@ private: enum ObstacleRequestAction { REQUEST_ADD, - REQUEST_REMOVE, + REQUEST_REMOVE }; struct ObstacleRequest diff --git a/Source/ThirdParty/recastnavigation/DetourTileCacheBuilder.cpp b/Source/ThirdParty/recastnavigation/DetourTileCacheBuilder.cpp index 21a59f1a6..dbc09eb48 100644 --- a/Source/ThirdParty/recastnavigation/DetourTileCacheBuilder.cpp +++ b/Source/ThirdParty/recastnavigation/DetourTileCacheBuilder.cpp @@ -23,6 +23,15 @@ #include "DetourTileCacheBuilder.h" #include +dtTileCacheAlloc::~dtTileCacheAlloc() +{ + // Defined out of line to fix the weak v-tables warning +} + +dtTileCacheCompressor::~dtTileCacheCompressor() +{ + // Defined out of line to fix the weak v-tables warning +} template class dtFixedArray { @@ -881,7 +890,7 @@ static bool buildMeshAdjacency(dtTileCacheAlloc* alloc, const dtTileCacheContourSet& lcset) { // Based on code by Eric Lengyel from: - // http://www.terathon.com/code/edges.php + // https://web.archive.org/web/20080704083314/http://www.terathon.com/code/edges.php const int maxEdgeCount = npolys*MAX_VERTS_PER_POLY; dtFixedArray firstEdge(alloc, nverts + maxEdgeCount); @@ -1399,7 +1408,6 @@ static void pushBack(unsigned short v, unsigned short* arr, int& an) static bool canRemoveVertex(dtTileCachePolyMesh& mesh, const unsigned short rem) { // Count number of polygons to remove. - int numRemovedVerts = 0; int numTouchedVerts = 0; int numRemainingEdges = 0; for (int i = 0; i < mesh.npolys; ++i) @@ -1419,7 +1427,6 @@ static bool canRemoveVertex(dtTileCachePolyMesh& mesh, const unsigned short rem) } if (numRemoved) { - numRemovedVerts += numRemoved; numRemainingEdges += numVerts-(numRemoved+1); } } @@ -1551,7 +1558,7 @@ static dtStatus removeVertex(dtTileCachePolyMesh& mesh, const unsigned short rem } // Remove vertex. - for (int i = (int)rem; i < mesh.nverts; ++i) + for (int i = (int)rem; i < mesh.nverts - 1; ++i) { mesh.verts[i*3+0] = mesh.verts[(i+1)*3+0]; mesh.verts[i*3+1] = mesh.verts[(i+1)*3+1]; diff --git a/Source/ThirdParty/recastnavigation/DetourTileCacheBuilder.h b/Source/ThirdParty/recastnavigation/DetourTileCacheBuilder.h index ff6109193..9a6844c65 100644 --- a/Source/ThirdParty/recastnavigation/DetourTileCacheBuilder.h +++ b/Source/ThirdParty/recastnavigation/DetourTileCacheBuilder.h @@ -78,7 +78,7 @@ struct dtTileCachePolyMesh struct dtTileCacheAlloc { - virtual ~dtTileCacheAlloc() {} + virtual ~dtTileCacheAlloc(); virtual void reset() {} @@ -95,7 +95,7 @@ struct dtTileCacheAlloc struct dtTileCacheCompressor { - virtual ~dtTileCacheCompressor() { } + virtual ~dtTileCacheCompressor(); virtual int maxCompressedSize(const int bufferSize) = 0; virtual dtStatus compress(const unsigned char* buffer, const int bufferSize, diff --git a/Source/ThirdParty/recastnavigation/Recast.cpp b/Source/ThirdParty/recastnavigation/Recast.cpp index 1b71710cd..d75a9f59f 100644 --- a/Source/ThirdParty/recastnavigation/Recast.cpp +++ b/Source/ThirdParty/recastnavigation/Recast.cpp @@ -16,98 +16,93 @@ // 3. This notice may not be removed or altered from any source distribution. // -#include -#define _USE_MATH_DEFINES -#include -#include -#include -#include -#include #include "Recast.h" #include "RecastAlloc.h" #include "RecastAssert.h" +#include +#include +#include +#include + namespace { /// Allocates and constructs an object of the given type, returning a pointer. -/// TODO: Support constructor args. -/// @param[in] hint Hint to the allocator. -template -T* rcNew(rcAllocHint hint) { - T* ptr = (T*)rcAlloc(sizeof(T), hint); +/// @param[in] allocLifetime Allocation lifetime hint +template +T* rcNew(const rcAllocHint allocLifetime) +{ + T* ptr = (T*)rcAlloc(sizeof(T), allocLifetime); ::new(rcNewTag(), (void*)ptr) T(); return ptr; } /// Destroys and frees an object allocated with rcNew. /// @param[in] ptr The object pointer to delete. -template -void rcDelete(T* ptr) { - if (ptr) { +template +void rcDelete(T* ptr) +{ + if (ptr) + { ptr->~T(); rcFree((void*)ptr); } } -} // namespace - +} // anonymous namespace float rcSqrt(float x) { return sqrtf(x); } -/// @class rcContext -/// @par -/// -/// This class does not provide logging or timer functionality on its -/// own. Both must be provided by a concrete implementation -/// by overriding the protected member functions. Also, this class does not -/// provide an interface for extracting log messages. (Only adding them.) -/// So concrete implementations must provide one. -/// -/// If no logging or timers are required, just pass an instance of this -/// class through the Recast build process. -/// - -/// @par -/// -/// Example: -/// @code -/// // Where ctx is an instance of rcContext and filepath is a char array. -/// ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", filepath); -/// @endcode void rcContext::log(const rcLogCategory category, const char* format, ...) { if (!m_logEnabled) + { return; + } static const int MSG_SIZE = 512; char msg[MSG_SIZE]; - va_list ap; - va_start(ap, format); - int len = vsnprintf(msg, MSG_SIZE, format, ap); + va_list argList; + va_start(argList, format); + int len = vsnprintf(msg, MSG_SIZE, format, argList); if (len >= MSG_SIZE) { - len = MSG_SIZE-1; - msg[MSG_SIZE-1] = '\0'; + len = MSG_SIZE - 1; + msg[MSG_SIZE - 1] = '\0'; + + const char* errorMessage = "Log message was truncated"; + doLog(RC_LOG_ERROR, errorMessage, (int)strlen(errorMessage)); } - va_end(ap); + va_end(argList); doLog(category, msg, len); } +void rcContext::doResetLog() +{ + // Defined out of line to fix the weak v-tables warning +} + rcHeightfield* rcAllocHeightfield() { return rcNew(RC_ALLOC_PERM); } + +void rcFreeHeightField(rcHeightfield* heightfield) +{ + rcDelete(heightfield); +} + rcHeightfield::rcHeightfield() - : width() - , height() - , bmin() - , bmax() - , cs() - , ch() - , spans() - , pools() - , freelist() +: width() +, height() +, bmin() +, bmax() +, cs() +, ch() +, spans() +, pools() +, freelist() { } @@ -124,40 +119,36 @@ rcHeightfield::~rcHeightfield() } } -void rcFreeHeightField(rcHeightfield* hf) -{ - rcDelete(hf); -} - rcCompactHeightfield* rcAllocCompactHeightfield() { return rcNew(RC_ALLOC_PERM); } -void rcFreeCompactHeightfield(rcCompactHeightfield* chf) +void rcFreeCompactHeightfield(rcCompactHeightfield* compactHeightfield) { - rcDelete(chf); + rcDelete(compactHeightfield); } rcCompactHeightfield::rcCompactHeightfield() - : width(), - height(), - spanCount(), - walkableHeight(), - walkableClimb(), - borderSize(), - maxDistance(), - maxRegions(), - bmin(), - bmax(), - cs(), - ch(), - cells(), - spans(), - dist(), - areas() +: width() +, height() +, spanCount() +, walkableHeight() +, walkableClimb() +, borderSize() +, maxDistance() +, maxRegions() +, bmin() +, bmax() +, cs() +, ch() +, cells() +, spans() +, dist() +, areas() { } + rcCompactHeightfield::~rcCompactHeightfield() { rcFree(cells); @@ -170,13 +161,18 @@ rcHeightfieldLayerSet* rcAllocHeightfieldLayerSet() { return rcNew(RC_ALLOC_PERM); } -void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* lset) + +void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* layerSet) { - rcDelete(lset); + rcDelete(layerSet); } rcHeightfieldLayerSet::rcHeightfieldLayerSet() - : layers(), nlayers() {} +: layers() +, nlayers() +{ +} + rcHeightfieldLayerSet::~rcHeightfieldLayerSet() { for (int i = 0; i < nlayers; ++i) @@ -193,22 +189,26 @@ rcContourSet* rcAllocContourSet() { return rcNew(RC_ALLOC_PERM); } -void rcFreeContourSet(rcContourSet* cset) + +void rcFreeContourSet(rcContourSet* contourSet) { - rcDelete(cset); + rcDelete(contourSet); } rcContourSet::rcContourSet() - : conts(), - nconts(), - bmin(), - bmax(), - cs(), - ch(), - width(), - height(), - borderSize(), - maxError() {} +: conts() +, nconts() +, bmin() +, bmax() +, cs() +, ch() +, width() +, height() +, borderSize() +, maxError() +{ +} + rcContourSet::~rcContourSet() { for (int i = 0; i < nconts; ++i) @@ -219,32 +219,34 @@ rcContourSet::~rcContourSet() rcFree(conts); } - rcPolyMesh* rcAllocPolyMesh() { return rcNew(RC_ALLOC_PERM); } -void rcFreePolyMesh(rcPolyMesh* pmesh) + +void rcFreePolyMesh(rcPolyMesh* polyMesh) { - rcDelete(pmesh); + rcDelete(polyMesh); } rcPolyMesh::rcPolyMesh() - : verts(), - polys(), - regs(), - flags(), - areas(), - nverts(), - npolys(), - maxpolys(), - nvp(), - bmin(), - bmax(), - cs(), - ch(), - borderSize(), - maxEdgeError() {} +: verts() +, polys() +, regs() +, flags() +, areas() +, nverts() +, npolys() +, maxpolys() +, nvp() +, bmin() +, bmax() +, cs() +, ch() +, borderSize() +, maxEdgeError() +{ +} rcPolyMesh::~rcPolyMesh() { @@ -257,319 +259,284 @@ rcPolyMesh::~rcPolyMesh() rcPolyMeshDetail* rcAllocPolyMeshDetail() { - rcPolyMeshDetail* dmesh = (rcPolyMeshDetail*)rcAlloc(sizeof(rcPolyMeshDetail), RC_ALLOC_PERM); - memset(dmesh, 0, sizeof(rcPolyMeshDetail)); - return dmesh; + return rcNew(RC_ALLOC_PERM); } -void rcFreePolyMeshDetail(rcPolyMeshDetail* dmesh) +void rcFreePolyMeshDetail(rcPolyMeshDetail* detailMesh) { - if (!dmesh) return; - rcFree(dmesh->meshes); - rcFree(dmesh->verts); - rcFree(dmesh->tris); - rcFree(dmesh); + if (detailMesh == NULL) + { + return; + } + rcFree(detailMesh->meshes); + rcFree(detailMesh->verts); + rcFree(detailMesh->tris); + rcFree(detailMesh); } -void rcCalcBounds(const float* verts, int nv, float* bmin, float* bmax) +rcPolyMeshDetail::rcPolyMeshDetail() +: meshes() +, verts() +, tris() +, nmeshes() +, nverts() +, ntris() +{ +} + +void rcCalcBounds(const float* verts, int numVerts, float* minBounds, float* maxBounds) { // Calculate bounding box. - rcVcopy(bmin, verts); - rcVcopy(bmax, verts); - for (int i = 1; i < nv; ++i) + rcVcopy(minBounds, verts); + rcVcopy(maxBounds, verts); + for (int i = 1; i < numVerts; ++i) { - const float* v = &verts[i*3]; - rcVmin(bmin, v); - rcVmax(bmax, v); + const float* v = &verts[i * 3]; + rcVmin(minBounds, v); + rcVmax(maxBounds, v); } } -void rcCalcGridSize(const float* bmin, const float* bmax, float cs, int* w, int* h) +void rcCalcGridSize(const float* minBounds, const float* maxBounds, const float cellSize, int* sizeX, int* sizeZ) { - *w = (int)((bmax[0] - bmin[0])/cs+0.5f); - *h = (int)((bmax[2] - bmin[2])/cs+0.5f); + *sizeX = (int)((maxBounds[0] - minBounds[0]) / cellSize + 0.5f); + *sizeZ = (int)((maxBounds[2] - minBounds[2]) / cellSize + 0.5f); } -/// @par -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcAllocHeightfield, rcHeightfield -bool rcCreateHeightfield(rcContext* ctx, rcHeightfield& hf, int width, int height, - const float* bmin, const float* bmax, - float cs, float ch) +bool rcCreateHeightfield(rcContext* context, rcHeightfield& heightfield, int sizeX, int sizeZ, + const float* minBounds, const float* maxBounds, + float cellSize, float cellHeight) { - rcIgnoreUnused(ctx); - - hf.width = width; - hf.height = height; - rcVcopy(hf.bmin, bmin); - rcVcopy(hf.bmax, bmax); - hf.cs = cs; - hf.ch = ch; - hf.spans = (rcSpan**)rcAlloc(sizeof(rcSpan*)*hf.width*hf.height, RC_ALLOC_PERM); - if (!hf.spans) + rcIgnoreUnused(context); + + heightfield.width = sizeX; + heightfield.height = sizeZ; + rcVcopy(heightfield.bmin, minBounds); + rcVcopy(heightfield.bmax, maxBounds); + heightfield.cs = cellSize; + heightfield.ch = cellHeight; + heightfield.spans = (rcSpan**)rcAlloc(sizeof(rcSpan*) * heightfield.width * heightfield.height, RC_ALLOC_PERM); + if (!heightfield.spans) + { return false; - memset(hf.spans, 0, sizeof(rcSpan*)*hf.width*hf.height); + } + memset(heightfield.spans, 0, sizeof(rcSpan*) * heightfield.width * heightfield.height); return true; } -static void calcTriNormal(const float* v0, const float* v1, const float* v2, float* norm) +static void calcTriNormal(const float* v0, const float* v1, const float* v2, float* faceNormal) { float e0[3], e1[3]; rcVsub(e0, v1, v0); rcVsub(e1, v2, v0); - rcVcross(norm, e0, e1); - rcVnormalize(norm); + rcVcross(faceNormal, e0, e1); + rcVnormalize(faceNormal); } -/// @par -/// -/// Only sets the area id's for the walkable triangles. Does not alter the -/// area id's for unwalkable triangles. -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles -void rcMarkWalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, - const float* verts, int nv, - const int* tris, int nt, - unsigned char* areas) +void rcMarkWalkableTriangles(rcContext* context, const float walkableSlopeAngle, + const float* verts, const int numVerts, + const int* tris, const int numTris, + unsigned char* triAreaIDs) { - rcIgnoreUnused(ctx); - rcIgnoreUnused(nv); - - const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI); + rcIgnoreUnused(context); + rcIgnoreUnused(numVerts); + + const float walkableThr = cosf(walkableSlopeAngle / 180.0f * RC_PI); float norm[3]; - - for (int i = 0; i < nt; ++i) + + for (int i = 0; i < numTris; ++i) { - const int* tri = &tris[i*3]; - calcTriNormal(&verts[tri[0]*3], &verts[tri[1]*3], &verts[tri[2]*3], norm); + const int* tri = &tris[i * 3]; + calcTriNormal(&verts[tri[0] * 3], &verts[tri[1] * 3], &verts[tri[2] * 3], norm); // Check if the face is walkable. if (norm[1] > walkableThr) - areas[i] = RC_WALKABLE_AREA; - } -} - -/// @par -/// -/// Only sets the area id's for the unwalkable triangles. Does not alter the -/// area id's for walkable triangles. -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles -void rcClearUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, - const float* verts, int /*nv*/, - const int* tris, int nt, - unsigned char* areas) -{ - rcIgnoreUnused(ctx); - - const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI); - - float norm[3]; - - for (int i = 0; i < nt; ++i) - { - const int* tri = &tris[i*3]; - calcTriNormal(&verts[tri[0]*3], &verts[tri[1]*3], &verts[tri[2]*3], norm); - // Check if the face is walkable. - if (norm[1] <= walkableThr) - areas[i] = RC_NULL_AREA; - } -} - -int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf) -{ - rcIgnoreUnused(ctx); - - const int w = hf.width; - const int h = hf.height; - int spanCount = 0; - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) { - for (rcSpan* s = hf.spans[x + y*w]; s; s = s->next) + triAreaIDs[i] = RC_WALKABLE_AREA; + } + } +} + +void rcClearUnwalkableTriangles(rcContext* context, const float walkableSlopeAngle, + const float* verts, int numVerts, + const int* tris, int numTris, + unsigned char* triAreaIDs) +{ + rcIgnoreUnused(context); + rcIgnoreUnused(numVerts); + + // The minimum Y value for a face normal of a triangle with a walkable slope. + const float walkableLimitY = cosf(walkableSlopeAngle / 180.0f * RC_PI); + + float faceNormal[3]; + for (int i = 0; i < numTris; ++i) + { + const int* tri = &tris[i * 3]; + calcTriNormal(&verts[tri[0] * 3], &verts[tri[1] * 3], &verts[tri[2] * 3], faceNormal); + // Check if the face is walkable. + if (faceNormal[1] <= walkableLimitY) + { + triAreaIDs[i] = RC_NULL_AREA; + } + } +} + +int rcGetHeightFieldSpanCount(rcContext* context, const rcHeightfield& heightfield) +{ + rcIgnoreUnused(context); + + const int numCols = heightfield.width * heightfield.height; + int spanCount = 0; + for (int columnIndex = 0; columnIndex < numCols; ++columnIndex) + { + for (rcSpan* span = heightfield.spans[columnIndex]; span != NULL; span = span->next) + { + if (span->area != RC_NULL_AREA) { - if (s->area != RC_NULL_AREA) - spanCount++; + spanCount++; } } } return spanCount; } -/// @par -/// -/// This is just the beginning of the process of fully building a compact heightfield. -/// Various filters may be applied, then the distance field and regions built. -/// E.g: #rcBuildDistanceField and #rcBuildRegions -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig -bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb, - rcHeightfield& hf, rcCompactHeightfield& chf) +bool rcBuildCompactHeightfield(rcContext* context, const int walkableHeight, const int walkableClimb, + const rcHeightfield& heightfield, rcCompactHeightfield& compactHeightfield) { - rcAssert(ctx); - - rcScopedTimer timer(ctx, RC_TIMER_BUILD_COMPACTHEIGHTFIELD); - - const int w = hf.width; - const int h = hf.height; - const int spanCount = rcGetHeightFieldSpanCount(ctx, hf); + rcAssert(context); + + rcScopedTimer timer(context, RC_TIMER_BUILD_COMPACTHEIGHTFIELD); + + const int xSize = heightfield.width; + const int zSize = heightfield.height; + const int spanCount = rcGetHeightFieldSpanCount(context, heightfield); // Fill in header. - chf.width = w; - chf.height = h; - chf.spanCount = spanCount; - chf.walkableHeight = walkableHeight; - chf.walkableClimb = walkableClimb; - chf.maxRegions = 0; - rcVcopy(chf.bmin, hf.bmin); - rcVcopy(chf.bmax, hf.bmax); - chf.bmax[1] += walkableHeight*hf.ch; - chf.cs = hf.cs; - chf.ch = hf.ch; - chf.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell)*w*h, RC_ALLOC_PERM); - if (!chf.cells) + compactHeightfield.width = xSize; + compactHeightfield.height = zSize; + compactHeightfield.spanCount = spanCount; + compactHeightfield.walkableHeight = walkableHeight; + compactHeightfield.walkableClimb = walkableClimb; + compactHeightfield.maxRegions = 0; + rcVcopy(compactHeightfield.bmin, heightfield.bmin); + rcVcopy(compactHeightfield.bmax, heightfield.bmax); + compactHeightfield.bmax[1] += walkableHeight * heightfield.ch; + compactHeightfield.cs = heightfield.cs; + compactHeightfield.ch = heightfield.ch; + compactHeightfield.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell) * xSize * zSize, RC_ALLOC_PERM); + if (!compactHeightfield.cells) { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.cells' (%d)", w*h); + context->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.cells' (%d)", xSize * zSize); return false; } - memset(chf.cells, 0, sizeof(rcCompactCell)*w*h); - chf.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan)*spanCount, RC_ALLOC_PERM); - if (!chf.spans) + memset(compactHeightfield.cells, 0, sizeof(rcCompactCell) * xSize * zSize); + compactHeightfield.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan) * spanCount, RC_ALLOC_PERM); + if (!compactHeightfield.spans) { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.spans' (%d)", spanCount); + context->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.spans' (%d)", spanCount); return false; } - memset(chf.spans, 0, sizeof(rcCompactSpan)*spanCount); - chf.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*spanCount, RC_ALLOC_PERM); - if (!chf.areas) + memset(compactHeightfield.spans, 0, sizeof(rcCompactSpan) * spanCount); + compactHeightfield.areas = (unsigned char*)rcAlloc(sizeof(unsigned char) * spanCount, RC_ALLOC_PERM); + if (!compactHeightfield.areas) { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.areas' (%d)", spanCount); + context->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.areas' (%d)", spanCount); return false; } - memset(chf.areas, RC_NULL_AREA, sizeof(unsigned char)*spanCount); - + memset(compactHeightfield.areas, RC_NULL_AREA, sizeof(unsigned char) * spanCount); + const int MAX_HEIGHT = 0xffff; - + // Fill in cells and spans. - int idx = 0; - for (int y = 0; y < h; ++y) + int currentCellIndex = 0; + const int numColumns = xSize * zSize; + for (int columnIndex = 0; columnIndex < numColumns; ++columnIndex) { - for (int x = 0; x < w; ++x) + const rcSpan* span = heightfield.spans[columnIndex]; + + // If there are no spans at this cell, just leave the data to index=0, count=0. + if (span == NULL) { - const rcSpan* s = hf.spans[x + y*w]; - // If there are no spans at this cell, just leave the data to index=0, count=0. - if (!s) continue; - rcCompactCell& c = chf.cells[x+y*w]; - c.index = idx; - c.count = 0; - while (s) + continue; + } + + rcCompactCell& cell = compactHeightfield.cells[columnIndex]; + cell.index = currentCellIndex; + cell.count = 0; + + for (; span != NULL; span = span->next) + { + if (span->area != RC_NULL_AREA) { - if (s->area != RC_NULL_AREA) - { - const int bot = (int)s->smax; - const int top = s->next ? (int)s->next->smin : MAX_HEIGHT; - chf.spans[idx].y = (unsigned short)rcClamp(bot, 0, 0xffff); - chf.spans[idx].h = (unsigned char)rcClamp(top - bot, 0, 0xff); - chf.areas[idx] = s->area; - idx++; - c.count++; - } - s = s->next; + const int bot = (int)span->smax; + const int top = span->next ? (int)span->next->smin : MAX_HEIGHT; + compactHeightfield.spans[currentCellIndex].y = (unsigned short)rcClamp(bot, 0, 0xffff); + compactHeightfield.spans[currentCellIndex].h = (unsigned char)rcClamp(top - bot, 0, 0xff); + compactHeightfield.areas[currentCellIndex] = span->area; + currentCellIndex++; + cell.count++; } } } - + // Find neighbour connections. - const int MAX_LAYERS = RC_NOT_CONNECTED-1; - int tooHighNeighbour = 0; - for (int y = 0; y < h; ++y) + const int MAX_LAYERS = RC_NOT_CONNECTED - 1; + int maxLayerIndex = 0; + const int zStride = xSize; // for readability + for (int z = 0; z < zSize; ++z) { - for (int x = 0; x < w; ++x) + for (int x = 0; x < xSize; ++x) { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + const rcCompactCell& cell = compactHeightfield.cells[x + z * zStride]; + for (int i = (int)cell.index, ni = (int)(cell.index + cell.count); i < ni; ++i) { - rcCompactSpan& s = chf.spans[i]; - + rcCompactSpan& span = compactHeightfield.spans[i]; + for (int dir = 0; dir < 4; ++dir) { - rcSetCon(s, dir, RC_NOT_CONNECTED); - const int nx = x + rcGetDirOffsetX(dir); - const int ny = y + rcGetDirOffsetY(dir); + rcSetCon(span, dir, RC_NOT_CONNECTED); + const int neighborX = x + rcGetDirOffsetX(dir); + const int neighborZ = z + rcGetDirOffsetY(dir); // First check that the neighbour cell is in bounds. - if (nx < 0 || ny < 0 || nx >= w || ny >= h) + if (neighborX < 0 || neighborZ < 0 || neighborX >= xSize || neighborZ >= zSize) + { continue; - + } + // Iterate over all neighbour spans and check if any of the is // accessible from current cell. - const rcCompactCell& nc = chf.cells[nx+ny*w]; - for (int k = (int)nc.index, nk = (int)(nc.index+nc.count); k < nk; ++k) + const rcCompactCell& neighborCell = compactHeightfield.cells[neighborX + neighborZ * zStride]; + for (int k = (int)neighborCell.index, nk = (int)(neighborCell.index + neighborCell.count); k < nk; ++k) { - const rcCompactSpan& ns = chf.spans[k]; - const int bot = rcMax(s.y, ns.y); - const int top = rcMin(s.y+s.h, ns.y+ns.h); + const rcCompactSpan& neighborSpan = compactHeightfield.spans[k]; + const int bot = rcMax(span.y, neighborSpan.y); + const int top = rcMin(span.y + span.h, neighborSpan.y + neighborSpan.h); // Check that the gap between the spans is walkable, // and that the climb height between the gaps is not too high. - if ((top - bot) >= walkableHeight && rcAbs((int)ns.y - (int)s.y) <= walkableClimb) + if ((top - bot) >= walkableHeight && rcAbs((int)neighborSpan.y - (int)span.y) <= walkableClimb) { // Mark direction as walkable. - const int lidx = k - (int)nc.index; - if (lidx < 0 || lidx > MAX_LAYERS) + const int layerIndex = k - (int)neighborCell.index; + if (layerIndex < 0 || layerIndex > MAX_LAYERS) { - tooHighNeighbour = rcMax(tooHighNeighbour, lidx); + maxLayerIndex = rcMax(maxLayerIndex, layerIndex); continue; } - rcSetCon(s, dir, lidx); + rcSetCon(span, dir, layerIndex); break; } } - } } } } - - if (tooHighNeighbour > MAX_LAYERS) + + if (maxLayerIndex > MAX_LAYERS) { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Heightfield has too many layers %d (max: %d)", - tooHighNeighbour, MAX_LAYERS); + context->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Heightfield has too many layers %d (max: %d)", + maxLayerIndex, MAX_LAYERS); } - + return true; } - -/* -static int getHeightfieldMemoryUsage(const rcHeightfield& hf) -{ - int size = 0; - size += sizeof(hf); - size += hf.width * hf.height * sizeof(rcSpan*); - - rcSpanPool* pool = hf.pools; - while (pool) - { - size += (sizeof(rcSpanPool) - sizeof(rcSpan)) + sizeof(rcSpan)*RC_SPANS_PER_POOL; - pool = pool->next; - } - return size; -} - -static int getCompactHeightFieldMemoryusage(const rcCompactHeightfield& chf) -{ - int size = 0; - size += sizeof(rcCompactHeightfield); - size += sizeof(rcCompactSpan) * chf.spanCount; - size += sizeof(rcCompactCell) * chf.width * chf.height; - return size; -} -*/ diff --git a/Source/ThirdParty/recastnavigation/Recast.h b/Source/ThirdParty/recastnavigation/Recast.h index 4d557389b..9def8fd2a 100644 --- a/Source/ThirdParty/recastnavigation/Recast.h +++ b/Source/ThirdParty/recastnavigation/Recast.h @@ -22,13 +22,16 @@ /// The value of PI used by Recast. static const float RC_PI = 3.14159265f; +/// Used to ignore unused function parameters and silence any compiler warnings. +template void rcIgnoreUnused(const T&) { } + /// Recast log categories. /// @see rcContext enum rcLogCategory { RC_LOG_PROGRESS = 1, ///< A progress log entry. RC_LOG_WARNING, ///< A warning log entry. - RC_LOG_ERROR, ///< An error log entry. + RC_LOG_ERROR ///< An error log entry. }; /// Recast performance timer categories. @@ -97,12 +100,21 @@ enum rcTimerLabel /// Provides an interface for optional logging and performance tracking of the Recast /// build process. +/// +/// This class does not provide logging or timer functionality on its +/// own. Both must be provided by a concrete implementation +/// by overriding the protected member functions. Also, this class does not +/// provide an interface for extracting log messages. (Only adding them.) +/// So concrete implementations must provide one. +/// +/// If no logging or timers are required, just pass an instance of this +/// class through the Recast build process. +/// /// @ingroup recast class rcContext { public: - - /// Contructor. + /// Constructor. /// @param[in] state TRUE if the logging and performance timers should be enabled. [Default: true] inline rcContext(bool state = true) : m_logEnabled(state), m_timerEnabled(state) {} virtual ~rcContext() {} @@ -115,56 +127,62 @@ public: inline void resetLog() { if (m_logEnabled) doResetLog(); } /// Logs a message. - /// @param[in] category The category of the message. - /// @param[in] format The message. + /// + /// Example: + /// @code + /// // Where ctx is an instance of rcContext and filepath is a char array. + /// ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", filepath); + /// @endcode + /// + /// @param[in] category The category of the message. + /// @param[in] format The message. void log(const rcLogCategory category, const char* format, ...); /// Enables or disables the performance timers. /// @param[in] state TRUE if timers should be enabled. inline void enableTimer(bool state) { m_timerEnabled = state; } - /// Clears all peformance timers. (Resets all to unused.) + /// Clears all performance timers. (Resets all to unused.) inline void resetTimers() { if (m_timerEnabled) doResetTimers(); } /// Starts the specified performance timer. - /// @param label The category of the timer. + /// @param label The category of the timer. inline void startTimer(const rcTimerLabel label) { if (m_timerEnabled) doStartTimer(label); } /// Stops the specified performance timer. - /// @param label The category of the timer. + /// @param label The category of the timer. inline void stopTimer(const rcTimerLabel label) { if (m_timerEnabled) doStopTimer(label); } /// Returns the total accumulated time of the specified performance timer. - /// @param label The category of the timer. - /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. + /// @param label The category of the timer. + /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. inline int getAccumulatedTime(const rcTimerLabel label) const { return m_timerEnabled ? doGetAccumulatedTime(label) : -1; } protected: - /// Clears all log entries. - virtual void doResetLog() {} + virtual void doResetLog(); /// Logs a message. - /// @param[in] category The category of the message. - /// @param[in] msg The formatted message. - /// @param[in] len The length of the formatted message. - virtual void doLog(const rcLogCategory /*category*/, const char* /*msg*/, const int /*len*/) {} + /// @param[in] category The category of the message. + /// @param[in] msg The formatted message. + /// @param[in] len The length of the formatted message. + virtual void doLog(const rcLogCategory category, const char* msg, const int len) { rcIgnoreUnused(category); rcIgnoreUnused(msg); rcIgnoreUnused(len); } /// Clears all timers. (Resets all to unused.) virtual void doResetTimers() {} /// Starts the specified performance timer. - /// @param[in] label The category of timer. - virtual void doStartTimer(const rcTimerLabel /*label*/) {} + /// @param[in] label The category of timer. + virtual void doStartTimer(const rcTimerLabel label) { rcIgnoreUnused(label); } /// Stops the specified performance timer. - /// @param[in] label The category of the timer. - virtual void doStopTimer(const rcTimerLabel /*label*/) {} + /// @param[in] label The category of the timer. + virtual void doStopTimer(const rcTimerLabel label) { rcIgnoreUnused(label); } /// Returns the total accumulated time of the specified performance timer. - /// @param[in] label The category of the timer. - /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. - virtual int doGetAccumulatedTime(const rcTimerLabel /*label*/) const { return -1; } + /// @param[in] label The category of the timer. + /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. + virtual int doGetAccumulatedTime(const rcTimerLabel label) const { rcIgnoreUnused(label); return -1; } /// True if logging is enabled. bool m_logEnabled; @@ -238,7 +256,7 @@ struct rcConfig /// The maximum allowed length for contour edges along the border of the mesh. [Limit: >=0] [Units: vx] int maxEdgeLen; - /// The maximum distance a simplfied contour's border edges should deviate + /// The maximum distance a simplified contour's border edges should deviate /// the original raw contour. [Limit: >=0] [Units: vx] float maxSimplificationError; @@ -334,6 +352,7 @@ struct rcCompactHeightfield { rcCompactHeightfield(); ~rcCompactHeightfield(); + int width; ///< The width of the heightfield. (Along the x-axis in cell units.) int height; ///< The height of the heightfield. (Along the z-axis in cell units.) int spanCount; ///< The number of spans in the heightfield. @@ -350,6 +369,11 @@ struct rcCompactHeightfield rcCompactSpan* spans; ///< Array of spans. [Size: #spanCount] unsigned short* dist; ///< Array containing border distance data. [Size: #spanCount] unsigned char* areas; ///< Array containing area id data. [Size: #spanCount] + +private: + // Explicitly-disabled copy constructor and copy assignment operator. + rcCompactHeightfield(const rcCompactHeightfield&); + rcCompactHeightfield& operator=(const rcCompactHeightfield&); }; /// Represents a heightfield layer within a layer set. @@ -380,8 +404,14 @@ struct rcHeightfieldLayerSet { rcHeightfieldLayerSet(); ~rcHeightfieldLayerSet(); + rcHeightfieldLayer* layers; ///< The layers in the set. [Size: #nlayers] int nlayers; ///< The number of layers in the set. + +private: + // Explicitly-disabled copy constructor and copy assignment operator. + rcHeightfieldLayerSet(const rcHeightfieldLayerSet&); + rcHeightfieldLayerSet& operator=(const rcHeightfieldLayerSet&); }; /// Represents a simple, non-overlapping contour in field space. @@ -401,6 +431,7 @@ struct rcContourSet { rcContourSet(); ~rcContourSet(); + rcContour* conts; ///< An array of the contours in the set. [Size: #nconts] int nconts; ///< The number of contours in the set. float bmin[3]; ///< The minimum bounds in world space. [(x, y, z)] @@ -411,6 +442,11 @@ struct rcContourSet int height; ///< The height of the set. (Along the z-axis in cell units.) int borderSize; ///< The AABB border size used to generate the source data from which the contours were derived. float maxError; ///< The max edge error that this contour set was simplified with. + +private: + // Explicitly-disabled copy constructor and copy assignment operator. + rcContourSet(const rcContourSet&); + rcContourSet& operator=(const rcContourSet&); }; /// Represents a polygon mesh suitable for use in building a navigation mesh. @@ -419,6 +455,7 @@ struct rcPolyMesh { rcPolyMesh(); ~rcPolyMesh(); + unsigned short* verts; ///< The mesh vertices. [Form: (x, y, z) * #nverts] unsigned short* polys; ///< Polygon and neighbor data. [Length: #maxpolys * 2 * #nvp] unsigned short* regs; ///< The region id assigned to each polygon. [Length: #maxpolys] @@ -434,6 +471,11 @@ struct rcPolyMesh float ch; ///< The height of each cell. (The minimum increment along the y-axis.) int borderSize; ///< The AABB border size used to generate the source data from which the mesh was derived. float maxEdgeError; ///< The max error of the polygon edges in the mesh. + +private: + // Explicitly-disabled copy constructor and copy assignment operator. + rcPolyMesh(const rcPolyMesh&); + rcPolyMesh& operator=(const rcPolyMesh&); }; /// Contains triangle meshes that represent detailed height data associated @@ -441,12 +483,19 @@ struct rcPolyMesh /// @ingroup recast struct rcPolyMeshDetail { + rcPolyMeshDetail(); + unsigned int* meshes; ///< The sub-mesh data. [Size: 4*#nmeshes] float* verts; ///< The mesh vertices. [Size: 3*#nverts] unsigned char* tris; ///< The mesh triangles. [Size: 4*#ntris] int nmeshes; ///< The number of sub-meshes defined by #meshes. int nverts; ///< The number of vertices in #verts. int ntris; ///< The number of triangles in #tris. + +private: + // Explicitly-disabled copy constructor and copy assignment operator. + rcPolyMeshDetail(const rcPolyMeshDetail&); + rcPolyMeshDetail& operator=(const rcPolyMeshDetail&); }; /// @name Allocation Functions @@ -455,82 +504,82 @@ struct rcPolyMeshDetail /// @{ /// Allocates a heightfield object using the Recast allocator. -/// @return A heightfield that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcCreateHeightfield, rcFreeHeightField +/// @return A heightfield that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcCreateHeightfield, rcFreeHeightField rcHeightfield* rcAllocHeightfield(); /// Frees the specified heightfield object using the Recast allocator. -/// @param[in] hf A heightfield allocated using #rcAllocHeightfield -/// @ingroup recast -/// @see rcAllocHeightfield -void rcFreeHeightField(rcHeightfield* hf); +/// @param[in] heightfield A heightfield allocated using #rcAllocHeightfield +/// @ingroup recast +/// @see rcAllocHeightfield +void rcFreeHeightField(rcHeightfield* heightfield); /// Allocates a compact heightfield object using the Recast allocator. -/// @return A compact heightfield that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcBuildCompactHeightfield, rcFreeCompactHeightfield +/// @return A compact heightfield that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcBuildCompactHeightfield, rcFreeCompactHeightfield rcCompactHeightfield* rcAllocCompactHeightfield(); /// Frees the specified compact heightfield object using the Recast allocator. -/// @param[in] chf A compact heightfield allocated using #rcAllocCompactHeightfield -/// @ingroup recast -/// @see rcAllocCompactHeightfield -void rcFreeCompactHeightfield(rcCompactHeightfield* chf); +/// @param[in] compactHeightfield A compact heightfield allocated using #rcAllocCompactHeightfield +/// @ingroup recast +/// @see rcAllocCompactHeightfield +void rcFreeCompactHeightfield(rcCompactHeightfield* compactHeightfield); /// Allocates a heightfield layer set using the Recast allocator. -/// @return A heightfield layer set that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcBuildHeightfieldLayers, rcFreeHeightfieldLayerSet +/// @return A heightfield layer set that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcBuildHeightfieldLayers, rcFreeHeightfieldLayerSet rcHeightfieldLayerSet* rcAllocHeightfieldLayerSet(); /// Frees the specified heightfield layer set using the Recast allocator. -/// @param[in] lset A heightfield layer set allocated using #rcAllocHeightfieldLayerSet -/// @ingroup recast -/// @see rcAllocHeightfieldLayerSet -void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* lset); +/// @param[in] layerSet A heightfield layer set allocated using #rcAllocHeightfieldLayerSet +/// @ingroup recast +/// @see rcAllocHeightfieldLayerSet +void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* layerSet); /// Allocates a contour set object using the Recast allocator. -/// @return A contour set that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcBuildContours, rcFreeContourSet +/// @return A contour set that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcBuildContours, rcFreeContourSet rcContourSet* rcAllocContourSet(); /// Frees the specified contour set using the Recast allocator. -/// @param[in] cset A contour set allocated using #rcAllocContourSet -/// @ingroup recast -/// @see rcAllocContourSet -void rcFreeContourSet(rcContourSet* cset); +/// @param[in] contourSet A contour set allocated using #rcAllocContourSet +/// @ingroup recast +/// @see rcAllocContourSet +void rcFreeContourSet(rcContourSet* contourSet); /// Allocates a polygon mesh object using the Recast allocator. -/// @return A polygon mesh that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcBuildPolyMesh, rcFreePolyMesh +/// @return A polygon mesh that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcBuildPolyMesh, rcFreePolyMesh rcPolyMesh* rcAllocPolyMesh(); /// Frees the specified polygon mesh using the Recast allocator. -/// @param[in] pmesh A polygon mesh allocated using #rcAllocPolyMesh -/// @ingroup recast -/// @see rcAllocPolyMesh -void rcFreePolyMesh(rcPolyMesh* pmesh); +/// @param[in] polyMesh A polygon mesh allocated using #rcAllocPolyMesh +/// @ingroup recast +/// @see rcAllocPolyMesh +void rcFreePolyMesh(rcPolyMesh* polyMesh); /// Allocates a detail mesh object using the Recast allocator. -/// @return A detail mesh that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcBuildPolyMeshDetail, rcFreePolyMeshDetail +/// @return A detail mesh that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcBuildPolyMeshDetail, rcFreePolyMeshDetail rcPolyMeshDetail* rcAllocPolyMeshDetail(); /// Frees the specified detail mesh using the Recast allocator. -/// @param[in] dmesh A detail mesh allocated using #rcAllocPolyMeshDetail -/// @ingroup recast -/// @see rcAllocPolyMeshDetail -void rcFreePolyMeshDetail(rcPolyMeshDetail* dmesh); +/// @param[in] detailMesh A detail mesh allocated using #rcAllocPolyMeshDetail +/// @ingroup recast +/// @see rcAllocPolyMeshDetail +void rcFreePolyMeshDetail(rcPolyMeshDetail* detailMesh); /// @} -/// Heighfield border flag. +/// Heightfield border flag. /// If a heightfield region ID has this bit set, then the region is a border -/// region and its spans are considered unwalkable. +/// region and its spans are considered un-walkable. /// (Used during the region and contour build process.) /// @see rcCompactSpan::reg static const unsigned short RC_BORDER_REG = 0x8000; @@ -564,7 +613,7 @@ static const int RC_AREA_BORDER = 0x20000; enum rcBuildContoursFlags { RC_CONTOUR_TESS_WALL_EDGES = 0x01, ///< Tessellate solid (impassable) edges during contour simplification. - RC_CONTOUR_TESS_AREA_EDGES = 0x02, ///< Tessellate edges between areas during contour simplification. + RC_CONTOUR_TESS_AREA_EDGES = 0x02 ///< Tessellate edges between areas during contour simplification. }; /// Applied to the region id field of contour vertices in order to extract the region id. @@ -580,7 +629,7 @@ static const unsigned short RC_MESH_NULL_IDX = 0xffff; /// Represents the null area. /// When a data element is given this value it is considered to no longer be -/// assigned to a usable area. (E.g. It is unwalkable.) +/// assigned to a usable area. (E.g. It is un-walkable.) static const unsigned char RC_NULL_AREA = 0; /// The default area id used to indicate a walkable polygon. @@ -595,44 +644,42 @@ static const int RC_NOT_CONNECTED = 0x3f; /// @name General helper functions /// @{ -/// Used to ignore a function parameter. VS complains about unused parameters -/// and this silences the warning. -/// @param [in] _ Unused parameter -template void rcIgnoreUnused(const T&) { } - /// Swaps the values of the two parameters. -/// @param[in,out] a Value A -/// @param[in,out] b Value B +/// @param[in,out] a Value A +/// @param[in,out] b Value B template inline void rcSwap(T& a, T& b) { T t = a; a = b; b = t; } /// Returns the minimum of two values. -/// @param[in] a Value A -/// @param[in] b Value B -/// @return The minimum of the two values. +/// @param[in] a Value A +/// @param[in] b Value B +/// @return The minimum of the two values. template inline T rcMin(T a, T b) { return a < b ? a : b; } /// Returns the maximum of two values. -/// @param[in] a Value A -/// @param[in] b Value B -/// @return The maximum of the two values. +/// @param[in] a Value A +/// @param[in] b Value B +/// @return The maximum of the two values. template inline T rcMax(T a, T b) { return a > b ? a : b; } /// Returns the absolute value. -/// @param[in] a The value. -/// @return The absolute value of the specified value. +/// @param[in] a The value. +/// @return The absolute value of the specified value. template inline T rcAbs(T a) { return a < 0 ? -a : a; } /// Returns the square of the value. -/// @param[in] a The value. -/// @return The square of the value. +/// @param[in] a The value. +/// @return The square of the value. template inline T rcSqr(T a) { return a*a; } /// Clamps the value to the specified range. -/// @param[in] v The value to clamp. -/// @param[in] mn The minimum permitted return value. -/// @param[in] mx The maximum permitted return value. -/// @return The value, clamped to the specified range. -template inline T rcClamp(T v, T mn, T mx) { return v < mn ? mn : (v > mx ? mx : v); } +/// @param[in] value The value to clamp. +/// @param[in] minInclusive The minimum permitted return value. +/// @param[in] maxInclusive The maximum permitted return value. +/// @return The value, clamped to the specified range. +template inline T rcClamp(T value, T minInclusive, T maxInclusive) +{ + return value < minInclusive ? minInclusive: (value > maxInclusive ? maxInclusive : value); +} /// Returns the square root of the value. /// @param[in] x The value. @@ -644,9 +691,9 @@ float rcSqrt(float x); /// @{ /// Derives the cross product of two vectors. (@p v1 x @p v2) -/// @param[out] dest The cross product. [(x, y, z)] -/// @param[in] v1 A Vector [(x, y, z)] -/// @param[in] v2 A vector [(x, y, z)] +/// @param[out] dest The cross product. [(x, y, z)] +/// @param[in] v1 A Vector [(x, y, z)] +/// @param[in] v2 A vector [(x, y, z)] inline void rcVcross(float* dest, const float* v1, const float* v2) { dest[0] = v1[1]*v2[2] - v1[2]*v2[1]; @@ -655,8 +702,8 @@ inline void rcVcross(float* dest, const float* v1, const float* v2) } /// Derives the dot product of two vectors. (@p v1 . @p v2) -/// @param[in] v1 A Vector [(x, y, z)] -/// @param[in] v2 A vector [(x, y, z)] +/// @param[in] v1 A Vector [(x, y, z)] +/// @param[in] v2 A vector [(x, y, z)] /// @return The dot product. inline float rcVdot(const float* v1, const float* v2) { @@ -664,10 +711,10 @@ inline float rcVdot(const float* v1, const float* v2) } /// Performs a scaled vector addition. (@p v1 + (@p v2 * @p s)) -/// @param[out] dest The result vector. [(x, y, z)] -/// @param[in] v1 The base vector. [(x, y, z)] -/// @param[in] v2 The vector to scale and add to @p v1. [(x, y, z)] -/// @param[in] s The amount to scale @p v2 by before adding to @p v1. +/// @param[out] dest The result vector. [(x, y, z)] +/// @param[in] v1 The base vector. [(x, y, z)] +/// @param[in] v2 The vector to scale and add to @p v1. [(x, y, z)] +/// @param[in] s The amount to scale @p v2 by before adding to @p v1. inline void rcVmad(float* dest, const float* v1, const float* v2, const float s) { dest[0] = v1[0]+v2[0]*s; @@ -676,9 +723,9 @@ inline void rcVmad(float* dest, const float* v1, const float* v2, const float s) } /// Performs a vector addition. (@p v1 + @p v2) -/// @param[out] dest The result vector. [(x, y, z)] -/// @param[in] v1 The base vector. [(x, y, z)] -/// @param[in] v2 The vector to add to @p v1. [(x, y, z)] +/// @param[out] dest The result vector. [(x, y, z)] +/// @param[in] v1 The base vector. [(x, y, z)] +/// @param[in] v2 The vector to add to @p v1. [(x, y, z)] inline void rcVadd(float* dest, const float* v1, const float* v2) { dest[0] = v1[0]+v2[0]; @@ -687,9 +734,9 @@ inline void rcVadd(float* dest, const float* v1, const float* v2) } /// Performs a vector subtraction. (@p v1 - @p v2) -/// @param[out] dest The result vector. [(x, y, z)] -/// @param[in] v1 The base vector. [(x, y, z)] -/// @param[in] v2 The vector to subtract from @p v1. [(x, y, z)] +/// @param[out] dest The result vector. [(x, y, z)] +/// @param[in] v1 The base vector. [(x, y, z)] +/// @param[in] v2 The vector to subtract from @p v1. [(x, y, z)] inline void rcVsub(float* dest, const float* v1, const float* v2) { dest[0] = v1[0]-v2[0]; @@ -698,8 +745,8 @@ inline void rcVsub(float* dest, const float* v1, const float* v2) } /// Selects the minimum value of each element from the specified vectors. -/// @param[in,out] mn A vector. (Will be updated with the result.) [(x, y, z)] -/// @param[in] v A vector. [(x, y, z)] +/// @param[in,out] mn A vector. (Will be updated with the result.) [(x, y, z)] +/// @param[in] v A vector. [(x, y, z)] inline void rcVmin(float* mn, const float* v) { mn[0] = rcMin(mn[0], v[0]); @@ -708,8 +755,8 @@ inline void rcVmin(float* mn, const float* v) } /// Selects the maximum value of each element from the specified vectors. -/// @param[in,out] mx A vector. (Will be updated with the result.) [(x, y, z)] -/// @param[in] v A vector. [(x, y, z)] +/// @param[in,out] mx A vector. (Will be updated with the result.) [(x, y, z)] +/// @param[in] v A vector. [(x, y, z)] inline void rcVmax(float* mx, const float* v) { mx[0] = rcMax(mx[0], v[0]); @@ -718,8 +765,8 @@ inline void rcVmax(float* mx, const float* v) } /// Performs a vector copy. -/// @param[out] dest The result. [(x, y, z)] -/// @param[in] v The vector to copy. [(x, y, z)] +/// @param[out] dest The result. [(x, y, z)] +/// @param[in] v The vector to copy. [(x, y, z)] inline void rcVcopy(float* dest, const float* v) { dest[0] = v[0]; @@ -728,8 +775,8 @@ inline void rcVcopy(float* dest, const float* v) } /// Returns the distance between two points. -/// @param[in] v1 A point. [(x, y, z)] -/// @param[in] v2 A point. [(x, y, z)] +/// @param[in] v1 A point. [(x, y, z)] +/// @param[in] v2 A point. [(x, y, z)] /// @return The distance between the two points. inline float rcVdist(const float* v1, const float* v2) { @@ -740,8 +787,8 @@ inline float rcVdist(const float* v1, const float* v2) } /// Returns the square of the distance between two points. -/// @param[in] v1 A point. [(x, y, z)] -/// @param[in] v2 A point. [(x, y, z)] +/// @param[in] v1 A point. [(x, y, z)] +/// @param[in] v2 A point. [(x, y, z)] /// @return The square of the distance between the two points. inline float rcVdistSqr(const float* v1, const float* v2) { @@ -752,7 +799,7 @@ inline float rcVdistSqr(const float* v1, const float* v2) } /// Normalizes the vector. -/// @param[in,out] v The vector to normalize. [(x, y, z)] +/// @param[in,out] v The vector to normalize. [(x, y, z)] inline void rcVnormalize(float* v) { float d = 1.0f / rcSqrt(rcSqr(v[0]) + rcSqr(v[1]) + rcSqr(v[2])); @@ -767,174 +814,249 @@ inline void rcVnormalize(float* v) /// @{ /// Calculates the bounding box of an array of vertices. -/// @ingroup recast -/// @param[in] verts An array of vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices in the @p verts array. -/// @param[out] bmin The minimum bounds of the AABB. [(x, y, z)] [Units: wu] -/// @param[out] bmax The maximum bounds of the AABB. [(x, y, z)] [Units: wu] -void rcCalcBounds(const float* verts, int nv, float* bmin, float* bmax); +/// @ingroup recast +/// @param[in] verts An array of vertices. [(x, y, z) * @p nv] +/// @param[in] numVerts The number of vertices in the @p verts array. +/// @param[out] minBounds The minimum bounds of the AABB. [(x, y, z)] [Units: wu] +/// @param[out] maxBounds The maximum bounds of the AABB. [(x, y, z)] [Units: wu] +void rcCalcBounds(const float* verts, int numVerts, float* minBounds, float* maxBounds); /// Calculates the grid size based on the bounding box and grid cell size. -/// @ingroup recast -/// @param[in] bmin The minimum bounds of the AABB. [(x, y, z)] [Units: wu] -/// @param[in] bmax The maximum bounds of the AABB. [(x, y, z)] [Units: wu] -/// @param[in] cs The xz-plane cell size. [Limit: > 0] [Units: wu] -/// @param[out] w The width along the x-axis. [Limit: >= 0] [Units: vx] -/// @param[out] h The height along the z-axis. [Limit: >= 0] [Units: vx] -void rcCalcGridSize(const float* bmin, const float* bmax, float cs, int* w, int* h); +/// @ingroup recast +/// @param[in] minBounds The minimum bounds of the AABB. [(x, y, z)] [Units: wu] +/// @param[in] maxBounds The maximum bounds of the AABB. [(x, y, z)] [Units: wu] +/// @param[in] cellSize The xz-plane cell size. [Limit: > 0] [Units: wu] +/// @param[out] sizeX The width along the x-axis. [Limit: >= 0] [Units: vx] +/// @param[out] sizeZ The height along the z-axis. [Limit: >= 0] [Units: vx] +void rcCalcGridSize(const float* minBounds, const float* maxBounds, float cellSize, int* sizeX, int* sizeZ); /// Initializes a new heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] hf The allocated heightfield to initialize. -/// @param[in] width The width of the field along the x-axis. [Limit: >= 0] [Units: vx] -/// @param[in] height The height of the field along the z-axis. [Limit: >= 0] [Units: vx] -/// @param[in] bmin The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu] -/// @param[in] bmax The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu] -/// @param[in] cs The xz-plane cell size to use for the field. [Limit: > 0] [Units: wu] -/// @param[in] ch The y-axis cell size to use for field. [Limit: > 0] [Units: wu] -/// @returns True if the operation completed successfully. -bool rcCreateHeightfield(rcContext* ctx, rcHeightfield& hf, int width, int height, - const float* bmin, const float* bmax, - float cs, float ch); +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcAllocHeightfield, rcHeightfield +/// @ingroup recast +/// +/// @param[in,out] context The build context to use during the operation. +/// @param[in,out] heightfield The allocated heightfield to initialize. +/// @param[in] sizeX The width of the field along the x-axis. [Limit: >= 0] [Units: vx] +/// @param[in] sizeZ The height of the field along the z-axis. [Limit: >= 0] [Units: vx] +/// @param[in] minBounds The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu] +/// @param[in] maxBounds The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu] +/// @param[in] cellSize The xz-plane cell size to use for the field. [Limit: > 0] [Units: wu] +/// @param[in] cellHeight The y-axis cell size to use for field. [Limit: > 0] [Units: wu] +/// @returns True if the operation completed successfully. +bool rcCreateHeightfield(rcContext* context, rcHeightfield& heightfield, int sizeX, int sizeZ, + const float* minBounds, const float* maxBounds, + float cellSize, float cellHeight); /// Sets the area id of all triangles with a slope below the specified value /// to #RC_WALKABLE_AREA. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. -/// [Limits: 0 <= value < 90] [Units: Degrees] -/// @param[in] verts The vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices. -/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] -/// @param[in] nt The number of triangles. -/// @param[out] areas The triangle area ids. [Length: >= @p nt] -void rcMarkWalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, const float* verts, int nv, - const int* tris, int nt, unsigned char* areas); +/// +/// Only sets the area id's for the walkable triangles. Does not alter the +/// area id's for un-walkable triangles. +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles +/// +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. +/// [Limits: 0 <= value < 90] [Units: Degrees] +/// @param[in] verts The vertices. [(x, y, z) * @p nv] +/// @param[in] numVerts The number of vertices. +/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] +/// @param[in] numTris The number of triangles. +/// @param[out] triAreaIDs The triangle area ids. [Length: >= @p nt] +void rcMarkWalkableTriangles(rcContext* context, float walkableSlopeAngle, const float* verts, int numVerts, + const int* tris, int numTris, unsigned char* triAreaIDs); /// Sets the area id of all triangles with a slope greater than or equal to the specified value to #RC_NULL_AREA. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. -/// [Limits: 0 <= value < 90] [Units: Degrees] -/// @param[in] verts The vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices. -/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] -/// @param[in] nt The number of triangles. -/// @param[out] areas The triangle area ids. [Length: >= @p nt] -void rcClearUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, const float* verts, int nv, - const int* tris, int nt, unsigned char* areas); +/// +/// Only sets the area id's for the un-walkable triangles. Does not alter the +/// area id's for walkable triangles. +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles +/// +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. +/// [Limits: 0 <= value < 90] [Units: Degrees] +/// @param[in] verts The vertices. [(x, y, z) * @p nv] +/// @param[in] numVerts The number of vertices. +/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] +/// @param[in] numTris The number of triangles. +/// @param[out] triAreaIDs The triangle area ids. [Length: >= @p nt] +void rcClearUnwalkableTriangles(rcContext* context, float walkableSlopeAngle, const float* verts, int numVerts, + const int* tris, int numTris, unsigned char* triAreaIDs); /// Adds a span to the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] hf An initialized heightfield. -/// @param[in] x The width index where the span is to be added. -/// [Limits: 0 <= value < rcHeightfield::width] -/// @param[in] y The height index where the span is to be added. -/// [Limits: 0 <= value < rcHeightfield::height] -/// @param[in] smin The minimum height of the span. [Limit: < @p smax] [Units: vx] -/// @param[in] smax The maximum height of the span. [Limit: <= #RC_SPAN_MAX_HEIGHT] [Units: vx] -/// @param[in] area The area id of the span. [Limit: <= #RC_WALKABLE_AREA) -/// @param[in] flagMergeThr The merge theshold. [Limit: >= 0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcAddSpan(rcContext* ctx, rcHeightfield& hf, const int x, const int y, - const unsigned short smin, const unsigned short smax, - const unsigned char area, const int flagMergeThr); +/// +/// The span addition can be set to favor flags. If the span is merged to +/// another span and the new @p spanMax is within @p flagMergeThreshold units +/// from the existing span, the span flags are merged. +/// +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in,out] heightfield An initialized heightfield. +/// @param[in] x The column x index where the span is to be added. +/// [Limits: 0 <= value < rcHeightfield::width] +/// @param[in] z The column z index where the span is to be added. +/// [Limits: 0 <= value < rcHeightfield::height] +/// @param[in] spanMin The minimum height of the span. [Limit: < @p spanMax] [Units: vx] +/// @param[in] spanMax The maximum height of the span. [Limit: <= #RC_SPAN_MAX_HEIGHT] [Units: vx] +/// @param[in] areaID The area id of the span. [Limit: <= #RC_WALKABLE_AREA) +/// @param[in] flagMergeThreshold The merge threshold. [Limit: >= 0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcAddSpan(rcContext* context, rcHeightfield& heightfield, + int x, int z, + unsigned short spanMin, unsigned short spanMax, + unsigned char areaID, int flagMergeThreshold); -/// Rasterizes a triangle into the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] v0 Triangle vertex 0 [(x, y, z)] -/// @param[in] v1 Triangle vertex 1 [(x, y, z)] -/// @param[in] v2 Triangle vertex 2 [(x, y, z)] -/// @param[in] area The area id of the triangle. [Limit: <= #RC_WALKABLE_AREA] -/// @param[in,out] solid An initialized heightfield. -/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. -/// [Limit: >= 0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const float* v2, - const unsigned char area, rcHeightfield& solid, - const int flagMergeThr = 1); +/// Rasterizes a single triangle into the specified heightfield. +/// +/// Calling this for each triangle in a mesh is less efficient than calling rcRasterizeTriangles +/// +/// No spans will be added if the triangle does not overlap the heightfield grid. +/// +/// @see rcHeightfield +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] v0 Triangle vertex 0 [(x, y, z)] +/// @param[in] v1 Triangle vertex 1 [(x, y, z)] +/// @param[in] v2 Triangle vertex 2 [(x, y, z)] +/// @param[in] areaID The area id of the triangle. [Limit: <= #RC_WALKABLE_AREA] +/// @param[in,out] heightfield An initialized heightfield. +/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag. +/// [Limit: >= 0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcRasterizeTriangle(rcContext* context, + const float* v0, const float* v1, const float* v2, + unsigned char areaID, rcHeightfield& heightfield, int flagMergeThreshold = 1); /// Rasterizes an indexed triangle mesh into the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] verts The vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices. -/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] -/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] -/// @param[in] nt The number of triangles. -/// @param[in,out] solid An initialized heightfield. -/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. -/// [Limit: >= 0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, - const int* tris, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr = 1); +/// +/// Spans will only be added for triangles that overlap the heightfield grid. +/// +/// @see rcHeightfield +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] verts The vertices. [(x, y, z) * @p nv] +/// @param[in] numVerts The number of vertices. (unused) TODO (graham): Remove in next major release +/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] +/// @param[in] triAreaIDs The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] +/// @param[in] numTris The number of triangles. +/// @param[in,out] heightfield An initialized heightfield. +/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag. +/// [Limit: >= 0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcRasterizeTriangles(rcContext* context, + const float* verts, int numVerts, + const int* tris, const unsigned char* triAreaIDs, int numTris, + rcHeightfield& heightfield, int flagMergeThreshold = 1); /// Rasterizes an indexed triangle mesh into the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] verts The vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices. -/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] -/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] -/// @param[in] nt The number of triangles. -/// @param[in,out] solid An initialized heightfield. -/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. -/// [Limit: >= 0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, - const unsigned short* tris, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr = 1); +/// +/// Spans will only be added for triangles that overlap the heightfield grid. +/// +/// @see rcHeightfield +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] verts The vertices. [(x, y, z) * @p nv] +/// @param[in] numVerts The number of vertices. (unused) TODO (graham): Remove in next major release +/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] +/// @param[in] triAreaIDs The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] +/// @param[in] numTris The number of triangles. +/// @param[in,out] heightfield An initialized heightfield. +/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag. +/// [Limit: >= 0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcRasterizeTriangles(rcContext* context, + const float* verts, int numVerts, + const unsigned short* tris, const unsigned char* triAreaIDs, int numTris, + rcHeightfield& heightfield, int flagMergeThreshold = 1); -/// Rasterizes triangles into the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] verts The triangle vertices. [(ax, ay, az, bx, by, bz, cx, by, cx) * @p nt] -/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] -/// @param[in] nt The number of triangles. -/// @param[in,out] solid An initialized heightfield. -/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. -/// [Limit: >= 0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr = 1); +/// Rasterizes a triangle list into the specified heightfield. +/// +/// Expects each triangle to be specified as three sequential vertices of 3 floats. +/// +/// Spans will only be added for triangles that overlap the heightfield grid. +/// +/// @see rcHeightfield +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] verts The triangle vertices. [(ax, ay, az, bx, by, bz, cx, by, cx) * @p nt] +/// @param[in] triAreaIDs The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] +/// @param[in] numTris The number of triangles. +/// @param[in,out] heightfield An initialized heightfield. +/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag. +/// [Limit: >= 0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcRasterizeTriangles(rcContext* context, + const float* verts, const unsigned char* triAreaIDs, int numTris, + rcHeightfield& heightfield, int flagMergeThreshold = 1); -/// Marks non-walkable spans as walkable if their maximum is within @p walkableClimp of a walkable neighbor. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. -/// [Limit: >=0] [Units: vx] -/// @param[in,out] solid A fully built heightfield. (All spans have been added.) -void rcFilterLowHangingWalkableObstacles(rcContext* ctx, const int walkableClimb, rcHeightfield& solid); +/// Marks non-walkable spans as walkable if their maximum is within @p walkableClimb of a walkable neighbor. +/// +/// Allows the formation of walkable regions that will flow over low lying +/// objects such as curbs, and up structures such as stairways. +/// +/// Two neighboring spans are walkable if: rcAbs(currentSpan.smax - neighborSpan.smax) < waklableClimb +/// +/// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call +/// #rcFilterLedgeSpans after calling this filter. +/// +/// @see rcHeightfield, rcConfig +/// +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. +/// [Limit: >=0] [Units: vx] +/// @param[in,out] heightfield A fully built heightfield. (All spans have been added.) +void rcFilterLowHangingWalkableObstacles(rcContext* context, int walkableClimb, rcHeightfield& heightfield); -/// Marks spans that are ledges as not-walkable. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to -/// be considered walkable. [Limit: >= 3] [Units: vx] -/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. -/// [Limit: >=0] [Units: vx] -/// @param[in,out] solid A fully built heightfield. (All spans have been added.) -void rcFilterLedgeSpans(rcContext* ctx, const int walkableHeight, - const int walkableClimb, rcHeightfield& solid); +/// Marks spans that are ledges as not-walkable. +/// +/// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb +/// from the current span's maximum. +/// This method removes the impact of the overestimation of conservative voxelization +/// so the resulting mesh will not have regions hanging in the air over ledges. +/// +/// A span is a ledge if: rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb +/// +/// @see rcHeightfield, rcConfig +/// +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to +/// be considered walkable. [Limit: >= 3] [Units: vx] +/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. +/// [Limit: >=0] [Units: vx] +/// @param[in,out] heightfield A fully built heightfield. (All spans have been added.) +void rcFilterLedgeSpans(rcContext* context, int walkableHeight, int walkableClimb, rcHeightfield& heightfield); -/// Marks walkable spans as not walkable if the clearence above the span is less than the specified height. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to -/// be considered walkable. [Limit: >= 3] [Units: vx] -/// @param[in,out] solid A fully built heightfield. (All spans have been added.) -void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeightfield& solid); +/// Marks walkable spans as not walkable if the clearance above the span is less than the specified height. +/// +/// For this filter, the clearance above the span is the distance from the span's +/// maximum to the next higher span's minimum. (Same grid column.) +/// +/// @see rcHeightfield, rcConfig +/// @ingroup recast +/// +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to +/// be considered walkable. [Limit: >= 3] [Units: vx] +/// @param[in,out] heightfield A fully built heightfield. (All spans have been added.) +void rcFilterWalkableLowHeightSpans(rcContext* context, int walkableHeight, rcHeightfield& heightfield); /// Returns the number of spans contained in the specified heightfield. /// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] hf An initialized heightfield. +/// @param[in,out] context The build context to use during the operation. +/// @param[in] heightfield An initialized heightfield. /// @returns The number of spans in the heightfield. -int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf); +int rcGetHeightFieldSpanCount(rcContext* context, const rcHeightfield& heightfield); /// @} /// @name Compact Heightfield Functions @@ -942,175 +1064,181 @@ int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf); /// @{ /// Builds a compact heightfield representing open space, from a heightfield representing solid space. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area -/// to be considered walkable. [Limit: >= 3] [Units: vx] -/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. -/// [Limit: >=0] [Units: vx] -/// @param[in] hf The heightfield to be compacted. -/// @param[out] chf The resulting compact heightfield. (Must be pre-allocated.) -/// @returns True if the operation completed successfully. -bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb, - rcHeightfield& hf, rcCompactHeightfield& chf); +/// +/// This is just the beginning of the process of fully building a compact heightfield. +/// Various filters may be applied, then the distance field and regions built. +/// E.g: #rcBuildDistanceField and #rcBuildRegions +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig +/// @ingroup recast +/// +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area +/// to be considered walkable. [Limit: >= 3] [Units: vx] +/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. +/// [Limit: >=0] [Units: vx] +/// @param[in] heightfield The heightfield to be compacted. +/// @param[out] compactHeightfield The resulting compact heightfield. (Must be pre-allocated.) +/// @returns True if the operation completed successfully. +bool rcBuildCompactHeightfield(rcContext* context, int walkableHeight, int walkableClimb, + const rcHeightfield& heightfield, rcCompactHeightfield& compactHeightfield); /// Erodes the walkable area within the heightfield by the specified radius. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] radius The radius of erosion. [Limits: 0 < value < 255] [Units: vx] -/// @param[in,out] chf The populated compact heightfield to erode. -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] radius The radius of erosion. [Limits: 0 < value < 255] [Units: vx] +/// @param[in,out] chf The populated compact heightfield to erode. +/// @returns True if the operation completed successfully. bool rcErodeWalkableArea(rcContext* ctx, int radius, rcCompactHeightfield& chf); /// Applies a median filter to walkable area types (based on area id), removing noise. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @returns True if the operation completed successfully. bool rcMedianFilterWalkableArea(rcContext* ctx, rcCompactHeightfield& chf); /// Applies an area id to all spans within the specified bounding box. (AABB) -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] bmin The minimum of the bounding box. [(x, y, z)] -/// @param[in] bmax The maximum of the bounding box. [(x, y, z)] -/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] -/// @param[in,out] chf A populated compact heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] bmin The minimum of the bounding box. [(x, y, z)] +/// @param[in] bmax The maximum of the bounding box. [(x, y, z)] +/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] +/// @param[in,out] chf A populated compact heightfield. void rcMarkBoxArea(rcContext* ctx, const float* bmin, const float* bmax, unsigned char areaId, rcCompactHeightfield& chf); /// Applies the area id to the all spans within the specified convex polygon. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] verts The vertices of the polygon [Fomr: (x, y, z) * @p nverts] -/// @param[in] nverts The number of vertices in the polygon. -/// @param[in] hmin The height of the base of the polygon. -/// @param[in] hmax The height of the top of the polygon. -/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] -/// @param[in,out] chf A populated compact heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] verts The vertices of the polygon [Fomr: (x, y, z) * @p nverts] +/// @param[in] nverts The number of vertices in the polygon. +/// @param[in] hmin The height of the base of the polygon. +/// @param[in] hmax The height of the top of the polygon. +/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] +/// @param[in,out] chf A populated compact heightfield. void rcMarkConvexPolyArea(rcContext* ctx, const float* verts, const int nverts, const float hmin, const float hmax, unsigned char areaId, rcCompactHeightfield& chf); /// Helper function to offset voncex polygons for rcMarkConvexPolyArea. -/// @ingroup recast -/// @param[in] verts The vertices of the polygon [Form: (x, y, z) * @p nverts] -/// @param[in] nverts The number of vertices in the polygon. -/// @param[out] outVerts The offset vertices (should hold up to 2 * @p nverts) [Form: (x, y, z) * return value] -/// @param[in] maxOutVerts The max number of vertices that can be stored to @p outVerts. -/// @returns Number of vertices in the offset polygon or 0 if too few vertices in @p outVerts. +/// @ingroup recast +/// @param[in] verts The vertices of the polygon [Form: (x, y, z) * @p nverts] +/// @param[in] nverts The number of vertices in the polygon. +/// @param[in] offset How much to offset the polygon by. [Units: wu] +/// @param[out] outVerts The offset vertices (should hold up to 2 * @p nverts) [Form: (x, y, z) * return value] +/// @param[in] maxOutVerts The max number of vertices that can be stored to @p outVerts. +/// @returns Number of vertices in the offset polygon or 0 if too few vertices in @p outVerts. int rcOffsetPoly(const float* verts, const int nverts, const float offset, float* outVerts, const int maxOutVerts); /// Applies the area id to all spans within the specified cylinder. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] pos The center of the base of the cylinder. [Form: (x, y, z)] -/// @param[in] r The radius of the cylinder. -/// @param[in] h The height of the cylinder. -/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] -/// @param[in,out] chf A populated compact heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] pos The center of the base of the cylinder. [Form: (x, y, z)] +/// @param[in] r The radius of the cylinder. +/// @param[in] h The height of the cylinder. +/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] +/// @param[in,out] chf A populated compact heightfield. void rcMarkCylinderArea(rcContext* ctx, const float* pos, const float r, const float h, unsigned char areaId, rcCompactHeightfield& chf); /// Builds the distance field for the specified compact heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @returns True if the operation completed successfully. bool rcBuildDistanceField(rcContext* ctx, rcCompactHeightfield& chf); /// Builds region data for the heightfield using watershed partitioning. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @param[in] borderSize The size of the non-navigable border around the heightfield. -/// [Limit: >=0] [Units: vx] -/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. -/// [Limit: >=0] [Units: vx]. -/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, -/// be merged with larger regions. [Limit: >=0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int minRegionArea, const int mergeRegionArea); +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @param[in] borderSize The size of the non-navigable border around the heightfield. +/// [Limit: >=0] [Units: vx] +/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. +/// [Limit: >=0] [Units: vx]. +/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, +/// be merged with larger regions. [Limit: >=0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, int borderSize, int minRegionArea, int mergeRegionArea); /// Builds region data for the heightfield by partitioning the heightfield in non-overlapping layers. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @param[in] borderSize The size of the non-navigable border around the heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @param[in] borderSize The size of the non-navigable border around the heightfield. /// [Limit: >=0] [Units: vx] -/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. +/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. /// [Limit: >=0] [Units: vx]. -/// @returns True if the operation completed successfully. -bool rcBuildLayerRegions(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int minRegionArea); +/// @returns True if the operation completed successfully. +bool rcBuildLayerRegions(rcContext* ctx, rcCompactHeightfield& chf, int borderSize, int minRegionArea); /// Builds region data for the heightfield using simple monotone partitioning. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @param[in] borderSize The size of the non-navigable border around the heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @param[in] borderSize The size of the non-navigable border around the heightfield. /// [Limit: >=0] [Units: vx] -/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. +/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. /// [Limit: >=0] [Units: vx]. -/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, +/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, /// be merged with larger regions. [Limit: >=0] [Units: vx] -/// @returns True if the operation completed successfully. +/// @returns True if the operation completed successfully. bool rcBuildRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int minRegionArea, const int mergeRegionArea); + int borderSize, int minRegionArea, int mergeRegionArea); /// Sets the neighbor connection data for the specified direction. -/// @param[in] s The span to update. -/// @param[in] dir The direction to set. [Limits: 0 <= value < 4] -/// @param[in] i The index of the neighbor span. -inline void rcSetCon(rcCompactSpan& s, int dir, int i) +/// @param[in] span The span to update. +/// @param[in] direction The direction to set. [Limits: 0 <= value < 4] +/// @param[in] neighborIndex The index of the neighbor span. +inline void rcSetCon(rcCompactSpan& span, int direction, int neighborIndex) { - const unsigned int shift = (unsigned int)dir*6; - unsigned int con = s.con; - s.con = (con & ~(0x3f << shift)) | (((unsigned int)i & 0x3f) << shift); + const unsigned int shift = (unsigned int)direction * 6; + const unsigned int con = span.con; + span.con = (con & ~(0x3f << shift)) | (((unsigned int)neighborIndex & 0x3f) << shift); } /// Gets neighbor connection data for the specified direction. -/// @param[in] s The span to check. -/// @param[in] dir The direction to check. [Limits: 0 <= value < 4] -/// @return The neighbor connection data for the specified direction, -/// or #RC_NOT_CONNECTED if there is no connection. -inline int rcGetCon(const rcCompactSpan& s, int dir) +/// @param[in] span The span to check. +/// @param[in] direction The direction to check. [Limits: 0 <= value < 4] +/// @return The neighbor connection data for the specified direction, or #RC_NOT_CONNECTED if there is no connection. +inline int rcGetCon(const rcCompactSpan& span, int direction) { - const unsigned int shift = (unsigned int)dir*6; - return (s.con >> shift) & 0x3f; + const unsigned int shift = (unsigned int)direction * 6; + return (span.con >> shift) & 0x3f; } /// Gets the standard width (x-axis) offset for the specified direction. -/// @param[in] dir The direction. [Limits: 0 <= value < 4] -/// @return The width offset to apply to the current cell position to move -/// in the direction. -inline int rcGetDirOffsetX(int dir) +/// @param[in] direction The direction. [Limits: 0 <= value < 4] +/// @return The width offset to apply to the current cell position to move in the direction. +inline int rcGetDirOffsetX(int direction) { static const int offset[4] = { -1, 0, 1, 0, }; - return offset[dir&0x03]; + return offset[direction & 0x03]; } +// TODO (graham): Rename this to rcGetDirOffsetZ /// Gets the standard height (z-axis) offset for the specified direction. -/// @param[in] dir The direction. [Limits: 0 <= value < 4] -/// @return The height offset to apply to the current cell position to move -/// in the direction. -inline int rcGetDirOffsetY(int dir) +/// @param[in] direction The direction. [Limits: 0 <= value < 4] +/// @return The height offset to apply to the current cell position to move in the direction. +inline int rcGetDirOffsetY(int direction) { static const int offset[4] = { 0, 1, 0, -1 }; - return offset[dir&0x03]; + return offset[direction & 0x03]; } /// Gets the direction for the specified offset. One of x and y should be 0. -/// @param[in] x The x offset. [Limits: -1 <= value <= 1] -/// @param[in] y The y offset. [Limits: -1 <= value <= 1] -/// @return The direction that represents the offset. -inline int rcGetDirForOffset(int x, int y) +/// @param[in] offsetX The x offset. [Limits: -1 <= value <= 1] +/// @param[in] offsetZ The z offset. [Limits: -1 <= value <= 1] +/// @return The direction that represents the offset. +inline int rcGetDirForOffset(int offsetX, int offsetZ) { static const int dirs[5] = { 3, 0, -1, 2, 1 }; - return dirs[((y+1)<<1)+x]; + return dirs[((offsetZ + 1) << 1) + offsetX]; } /// @} @@ -1119,43 +1247,43 @@ inline int rcGetDirForOffset(int x, int y) /// @{ /// Builds a layer set from the specified compact heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] chf A fully built compact heightfield. -/// @param[in] borderSize The size of the non-navigable border around the heightfield. [Limit: >=0] -/// [Units: vx] -/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area -/// to be considered walkable. [Limit: >= 3] [Units: vx] -/// @param[out] lset The resulting layer set. (Must be pre-allocated.) -/// @returns True if the operation completed successfully. -bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int walkableHeight, +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] chf A fully built compact heightfield. +/// @param[in] borderSize The size of the non-navigable border around the heightfield. [Limit: >=0] +/// [Units: vx] +/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area +/// to be considered walkable. [Limit: >= 3] [Units: vx] +/// @param[out] lset The resulting layer set. (Must be pre-allocated.) +/// @returns True if the operation completed successfully. +bool rcBuildHeightfieldLayers(rcContext* ctx, const rcCompactHeightfield& chf, + int borderSize, int walkableHeight, rcHeightfieldLayerSet& lset); /// Builds a contour set from the region outlines in the provided compact heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] chf A fully built compact heightfield. -/// @param[in] maxError The maximum distance a simplfied contour's border edges should deviate -/// the original raw contour. [Limit: >=0] [Units: wu] -/// @param[in] maxEdgeLen The maximum allowed length for contour edges along the border of the mesh. -/// [Limit: >=0] [Units: vx] -/// @param[out] cset The resulting contour set. (Must be pre-allocated.) -/// @param[in] buildFlags The build flags. (See: #rcBuildContoursFlags) -/// @returns True if the operation completed successfully. -bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, - const float maxError, const int maxEdgeLen, - rcContourSet& cset, const int buildFlags = RC_CONTOUR_TESS_WALL_EDGES); +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] chf A fully built compact heightfield. +/// @param[in] maxError The maximum distance a simplified contour's border edges should deviate +/// the original raw contour. [Limit: >=0] [Units: wu] +/// @param[in] maxEdgeLen The maximum allowed length for contour edges along the border of the mesh. +/// [Limit: >=0] [Units: vx] +/// @param[out] cset The resulting contour set. (Must be pre-allocated.) +/// @param[in] buildFlags The build flags. (See: #rcBuildContoursFlags) +/// @returns True if the operation completed successfully. +bool rcBuildContours(rcContext* ctx, const rcCompactHeightfield& chf, + float maxError, int maxEdgeLen, + rcContourSet& cset, int buildFlags = RC_CONTOUR_TESS_WALL_EDGES); /// Builds a polygon mesh from the provided contours. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] cset A fully built contour set. -/// @param[in] nvp The maximum number of vertices allowed for polygons generated during the -/// contour to polygon conversion process. [Limit: >= 3] -/// @param[out] mesh The resulting polygon mesh. (Must be re-allocated.) -/// @returns True if the operation completed successfully. -bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMesh& mesh); +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] cset A fully built contour set. +/// @param[in] nvp The maximum number of vertices allowed for polygons generated during the +/// contour to polygon conversion process. [Limit: >= 3] +/// @param[out] mesh The resulting polygon mesh. (Must be re-allocated.) +/// @returns True if the operation completed successfully. +bool rcBuildPolyMesh(rcContext* ctx, const rcContourSet& cset, const int nvp, rcPolyMesh& mesh); /// Merges multiple polygon meshes into a single mesh. /// @ingroup recast @@ -1167,34 +1295,34 @@ bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMe bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh); /// Builds a detail mesh from the provided polygon mesh. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] mesh A fully built polygon mesh. -/// @param[in] chf The compact heightfield used to build the polygon mesh. -/// @param[in] sampleDist Sets the distance to use when samping the heightfield. [Limit: >=0] [Units: wu] -/// @param[in] sampleMaxError The maximum distance the detail mesh surface should deviate from -/// heightfield data. [Limit: >=0] [Units: wu] -/// @param[out] dmesh The resulting detail mesh. (Must be pre-allocated.) -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] mesh A fully built polygon mesh. +/// @param[in] chf The compact heightfield used to build the polygon mesh. +/// @param[in] sampleDist Sets the distance to use when sampling the heightfield. [Limit: >=0] [Units: wu] +/// @param[in] sampleMaxError The maximum distance the detail mesh surface should deviate from +/// heightfield data. [Limit: >=0] [Units: wu] +/// @param[out] dmesh The resulting detail mesh. (Must be pre-allocated.) +/// @returns True if the operation completed successfully. bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompactHeightfield& chf, - const float sampleDist, const float sampleMaxError, + float sampleDist, float sampleMaxError, rcPolyMeshDetail& dmesh); /// Copies the poly mesh data from src to dst. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] src The source mesh to copy from. -/// @param[out] dst The resulting detail mesh. (Must be pre-allocated, must be empty mesh.) -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] src The source mesh to copy from. +/// @param[out] dst The resulting detail mesh. (Must be pre-allocated, must be empty mesh.) +/// @returns True if the operation completed successfully. bool rcCopyPolyMesh(rcContext* ctx, const rcPolyMesh& src, rcPolyMesh& dst); /// Merges multiple detail meshes into a single detail mesh. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] meshes An array of detail meshes to merge. [Size: @p nmeshes] -/// @param[in] nmeshes The number of detail meshes in the meshes array. -/// @param[out] mesh The resulting detail mesh. (Must be pre-allocated.) -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] meshes An array of detail meshes to merge. [Size: @p nmeshes] +/// @param[in] nmeshes The number of detail meshes in the meshes array. +/// @param[out] mesh The resulting detail mesh. (Must be pre-allocated.) +/// @returns True if the operation completed successfully. bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int nmeshes, rcPolyMeshDetail& mesh); /// @} diff --git a/Source/ThirdParty/recastnavigation/RecastAlloc.cpp b/Source/ThirdParty/recastnavigation/RecastAlloc.cpp index bdc366116..e919daf7b 100644 --- a/Source/ThirdParty/recastnavigation/RecastAlloc.cpp +++ b/Source/ThirdParty/recastnavigation/RecastAlloc.cpp @@ -16,12 +16,9 @@ // 3. This notice may not be removed or altered from any source distribution. // -#include -#include #include "RecastAlloc.h" -#include "RecastAssert.h" -static void *rcAllocDefault(size_t size, rcAllocHint) +static void* rcAllocDefault(size_t size, rcAllocHint) { return malloc(size); } @@ -34,27 +31,21 @@ static void rcFreeDefault(void *ptr) static rcAllocFunc* sRecastAllocFunc = rcAllocDefault; static rcFreeFunc* sRecastFreeFunc = rcFreeDefault; -/// @see rcAlloc, rcFree -void rcAllocSetCustom(rcAllocFunc *allocFunc, rcFreeFunc *freeFunc) +void rcAllocSetCustom(rcAllocFunc* allocFunc, rcFreeFunc* freeFunc) { sRecastAllocFunc = allocFunc ? allocFunc : rcAllocDefault; sRecastFreeFunc = freeFunc ? freeFunc : rcFreeDefault; } -/// @see rcAllocSetCustom void* rcAlloc(size_t size, rcAllocHint hint) { return sRecastAllocFunc(size, hint); } -/// @par -/// -/// @warning This function leaves the value of @p ptr unchanged. So it still -/// points to the same (now invalid) location, and not to null. -/// -/// @see rcAllocSetCustom void rcFree(void* ptr) { - if (ptr) + if (ptr != NULL) + { sRecastFreeFunc(ptr); + } } diff --git a/Source/ThirdParty/recastnavigation/RecastAlloc.h b/Source/ThirdParty/recastnavigation/RecastAlloc.h index 071278d65..1741de9f0 100644 --- a/Source/ThirdParty/recastnavigation/RecastAlloc.h +++ b/Source/ThirdParty/recastnavigation/RecastAlloc.h @@ -19,11 +19,11 @@ #ifndef RECASTALLOC_H #define RECASTALLOC_H -#include -#include - #include "RecastAssert.h" +#include +#include + /// Provides hint values to the memory allocator on how long the /// memory is expected to be used. enum rcAllocHint @@ -47,18 +47,27 @@ typedef void (rcFreeFunc)(void* ptr); /// Sets the base custom allocation functions to be used by Recast. /// @param[in] allocFunc The memory allocation function to be used by #rcAlloc /// @param[in] freeFunc The memory de-allocation function to be used by #rcFree +/// +/// @see rcAlloc, rcFree void rcAllocSetCustom(rcAllocFunc *allocFunc, rcFreeFunc *freeFunc); /// Allocates a memory block. -/// @param[in] size The size, in bytes of memory, to allocate. -/// @param[in] hint A hint to the allocator on how long the memory is expected to be in use. -/// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed. -/// @see rcFree +/// +/// @param[in] size The size, in bytes of memory, to allocate. +/// @param[in] hint A hint to the allocator on how long the memory is expected to be in use. +/// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed. +/// +/// @see rcFree, rcAllocSetCustom void* rcAlloc(size_t size, rcAllocHint hint); -/// Deallocates a memory block. -/// @param[in] ptr A pointer to a memory block previously allocated using #rcAlloc. -/// @see rcAlloc +/// Deallocates a memory block. If @p ptr is NULL, this does nothing. +/// +/// @warning This function leaves the value of @p ptr unchanged. So it still +/// points to the same (now invalid) location, and not to null. +/// +/// @param[in] ptr A pointer to a memory block previously allocated using #rcAlloc. +/// +/// @see rcAlloc, rcAllocSetCustom void rcFree(void* ptr); /// An implementation of operator new usable for placement new. The default one is part of STL (which we don't use). @@ -112,7 +121,7 @@ class rcVectorBase { typedef rcSizeType size_type; typedef T value_type; - rcVectorBase() : m_size(0), m_cap(0), m_data(0) {}; + rcVectorBase() : m_size(0), m_cap(0), m_data(0) {} rcVectorBase(const rcVectorBase& other) : m_size(0), m_cap(0), m_data(0) { assign(other.begin(), other.end()); } explicit rcVectorBase(rcSizeType count) : m_size(0), m_cap(0), m_data(0) { resize(count); } rcVectorBase(rcSizeType count, const T& value) : m_size(0), m_cap(0), m_data(0) { resize(count, value); } @@ -142,8 +151,8 @@ class rcVectorBase { const T& front() const { rcAssert(m_size); return m_data[0]; } T& front() { rcAssert(m_size); return m_data[0]; } - const T& back() const { rcAssert(m_size); return m_data[m_size - 1]; }; - T& back() { rcAssert(m_size); return m_data[m_size - 1]; }; + const T& back() const { rcAssert(m_size); return m_data[m_size - 1]; } + T& back() { rcAssert(m_size); return m_data[m_size - 1]; } const T* data() const { return m_data; } T* data() { return m_data; } diff --git a/Source/ThirdParty/recastnavigation/RecastArea.cpp b/Source/ThirdParty/recastnavigation/RecastArea.cpp index 97139cf99..07fb02189 100644 --- a/Source/ThirdParty/recastnavigation/RecastArea.cpp +++ b/Source/ThirdParty/recastnavigation/RecastArea.cpp @@ -17,7 +17,6 @@ // #include -#define _USE_MATH_DEFINES #include #include #include diff --git a/Source/ThirdParty/recastnavigation/RecastAssert.cpp b/Source/ThirdParty/recastnavigation/RecastAssert.cpp index 6297d4202..973b68112 100644 --- a/Source/ThirdParty/recastnavigation/RecastAssert.cpp +++ b/Source/ThirdParty/recastnavigation/RecastAssert.cpp @@ -22,7 +22,7 @@ static rcAssertFailFunc* sRecastAssertFailFunc = 0; -void rcAssertFailSetCustom(rcAssertFailFunc *assertFailFunc) +void rcAssertFailSetCustom(rcAssertFailFunc* assertFailFunc) { sRecastAssertFailFunc = assertFailFunc; } diff --git a/Source/ThirdParty/recastnavigation/RecastAssert.h b/Source/ThirdParty/recastnavigation/RecastAssert.h index e7cc10e49..81705bbe0 100644 --- a/Source/ThirdParty/recastnavigation/RecastAssert.h +++ b/Source/ThirdParty/recastnavigation/RecastAssert.h @@ -19,13 +19,10 @@ #ifndef RECASTASSERT_H #define RECASTASSERT_H -// Note: This header file's only purpose is to include define assert. -// Feel free to change the file and include your own implementation instead. - #ifdef NDEBUG -// From http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ -# define rcAssert(x) do { (void)sizeof(x); } while((void)(__LINE__==-1),false) +// From https://web.archive.org/web/20210117002833/http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ +# define rcAssert(x) do { (void)sizeof(x); } while ((void)(__LINE__==-1), false) #else @@ -38,7 +35,7 @@ typedef void (rcAssertFailFunc)(const char* expression, const char* file, int li /// Sets the base custom assertion failure function to be used by Recast. /// @param[in] assertFailFunc The function to be used in case of failure of #dtAssert -void rcAssertFailSetCustom(rcAssertFailFunc *assertFailFunc); +void rcAssertFailSetCustom(rcAssertFailFunc* assertFailFunc); /// Gets the base custom assertion failure function to be used by Recast. rcAssertFailFunc* rcAssertFailGetCustom(); @@ -47,8 +44,8 @@ rcAssertFailFunc* rcAssertFailGetCustom(); # define rcAssert(expression) \ { \ rcAssertFailFunc* failFunc = rcAssertFailGetCustom(); \ - if(failFunc == NULL) { assert(expression); } \ - else if(!(expression)) { (*failFunc)(#expression, __FILE__, __LINE__); } \ + if (failFunc == NULL) { assert(expression); } \ + else if (!(expression)) { (*failFunc)(#expression, __FILE__, __LINE__); } \ } #endif diff --git a/Source/ThirdParty/recastnavigation/RecastContour.cpp b/Source/ThirdParty/recastnavigation/RecastContour.cpp index 1293d4fbd..5508a98a7 100644 --- a/Source/ThirdParty/recastnavigation/RecastContour.cpp +++ b/Source/ThirdParty/recastnavigation/RecastContour.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include @@ -102,7 +101,7 @@ static int getCornerHeight(int x, int y, int i, int dir, } static void walkContour(int x, int y, int i, - rcCompactHeightfield& chf, + const rcCompactHeightfield& chf, unsigned char* flags, rcIntArray& points) { // Choose the first non-connected edge @@ -542,7 +541,7 @@ static bool vequal(const int* a, const int* b) return a[0] == b[0] && a[2] == b[2]; } -static bool intersectSegCountour(const int* d0, const int* d1, int i, int n, const int* verts) +static bool intersectSegContour(const int* d0, const int* d1, int i, int n, const int* verts) { // For each edge (k,k+1) of P for (int k = 0; k < n; k++) @@ -778,9 +777,9 @@ static void mergeRegionHoles(rcContext* ctx, rcContourRegion& region) for (int j = 0; j < ndiags; j++) { const int* pt = &outline->verts[diags[j].vert*4]; - bool intersect = intersectSegCountour(pt, corner, diags[i].vert, outline->nverts, outline->verts); + bool intersect = intersectSegContour(pt, corner, diags[i].vert, outline->nverts, outline->verts); for (int k = i; k < region.nholes && !intersect; k++) - intersect |= intersectSegCountour(pt, corner, -1, region.holes[k].contour->nverts, region.holes[k].contour->verts); + intersect |= intersectSegContour(pt, corner, -1, region.holes[k].contour->nverts, region.holes[k].contour->verts); if (!intersect) { index = diags[j].vert; @@ -821,7 +820,7 @@ static void mergeRegionHoles(rcContext* ctx, rcContourRegion& region) /// See the #rcConfig documentation for more information on the configuration parameters. /// /// @see rcAllocContourSet, rcCompactHeightfield, rcContourSet, rcConfig -bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, +bool rcBuildContours(rcContext* ctx, const rcCompactHeightfield& chf, const float maxError, const int maxEdgeLen, rcContourSet& cset, const int buildFlags) { diff --git a/Source/ThirdParty/recastnavigation/RecastFilter.cpp b/Source/ThirdParty/recastnavigation/RecastFilter.cpp index 9d3e63c48..b5adba43e 100644 --- a/Source/ThirdParty/recastnavigation/RecastFilter.cpp +++ b/Source/ThirdParty/recastnavigation/RecastFilter.cpp @@ -16,186 +16,168 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES -#include -#include #include "Recast.h" #include "RecastAssert.h" -/// @par -/// -/// Allows the formation of walkable regions that will flow over low lying -/// objects such as curbs, and up structures such as stairways. -/// -/// Two neighboring spans are walkable if: rcAbs(currentSpan.smax - neighborSpan.smax) < waklableClimb -/// -/// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call -/// #rcFilterLedgeSpans after calling this filter. -/// -/// @see rcHeightfield, rcConfig -void rcFilterLowHangingWalkableObstacles(rcContext* ctx, const int walkableClimb, rcHeightfield& solid) -{ - rcAssert(ctx); +#include - rcScopedTimer timer(ctx, RC_TIMER_FILTER_LOW_OBSTACLES); - - const int w = solid.width; - const int h = solid.height; - - for (int y = 0; y < h; ++y) +void rcFilterLowHangingWalkableObstacles(rcContext* context, const int walkableClimb, rcHeightfield& heightfield) +{ + rcAssert(context); + + rcScopedTimer timer(context, RC_TIMER_FILTER_LOW_OBSTACLES); + + const int xSize = heightfield.width; + const int zSize = heightfield.height; + + for (int z = 0; z < zSize; ++z) { - for (int x = 0; x < w; ++x) + for (int x = 0; x < xSize; ++x) { - rcSpan* ps = 0; - bool previousWalkable = false; + rcSpan* previousSpan = NULL; + bool previousWasWalkable = false; unsigned char previousArea = RC_NULL_AREA; - - for (rcSpan* s = solid.spans[x + y*w]; s; ps = s, s = s->next) + + for (rcSpan* span = heightfield.spans[x + z * xSize]; span != NULL; previousSpan = span, span = span->next) { - const bool walkable = s->area != RC_NULL_AREA; + const bool walkable = span->area != RC_NULL_AREA; // If current span is not walkable, but there is walkable // span just below it, mark the span above it walkable too. - if (!walkable && previousWalkable) + if (!walkable && previousWasWalkable) { - if (rcAbs((int)s->smax - (int)ps->smax) <= walkableClimb) - s->area = previousArea; + if (rcAbs((int)span->smax - (int)previousSpan->smax) <= walkableClimb) + { + span->area = previousArea; + } } // Copy walkable flag so that it cannot propagate // past multiple non-walkable objects. - previousWalkable = walkable; - previousArea = s->area; + previousWasWalkable = walkable; + previousArea = span->area; } } } } -/// @par -/// -/// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb -/// from the current span's maximum. -/// This method removes the impact of the overestimation of conservative voxelization -/// so the resulting mesh will not have regions hanging in the air over ledges. -/// -/// A span is a ledge if: rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb -/// -/// @see rcHeightfield, rcConfig -void rcFilterLedgeSpans(rcContext* ctx, const int walkableHeight, const int walkableClimb, - rcHeightfield& solid) +void rcFilterLedgeSpans(rcContext* context, const int walkableHeight, const int walkableClimb, + rcHeightfield& heightfield) { - rcAssert(ctx); + rcAssert(context); - rcScopedTimer timer(ctx, RC_TIMER_FILTER_BORDER); + rcScopedTimer timer(context, RC_TIMER_FILTER_BORDER); - const int w = solid.width; - const int h = solid.height; - const int MAX_HEIGHT = 0xffff; + const int xSize = heightfield.width; + const int zSize = heightfield.height; + const int MAX_HEIGHT = 0xffff; // TODO (graham): Move this to a more visible constant and update usages. // Mark border spans. - for (int y = 0; y < h; ++y) + for (int z = 0; z < zSize; ++z) { - for (int x = 0; x < w; ++x) + for (int x = 0; x < xSize; ++x) { - for (rcSpan* s = solid.spans[x + y*w]; s; s = s->next) + for (rcSpan* span = heightfield.spans[x + z * xSize]; span; span = span->next) { // Skip non walkable spans. - if (s->area == RC_NULL_AREA) + if (span->area == RC_NULL_AREA) + { continue; - - const int bot = (int)(s->smax); - const int top = s->next ? (int)(s->next->smin) : MAX_HEIGHT; - + } + + const int bot = (int)(span->smax); + const int top = span->next ? (int)(span->next->smin) : MAX_HEIGHT; + // Find neighbours minimum height. - int minh = MAX_HEIGHT; + int minNeighborHeight = MAX_HEIGHT; // Min and max height of accessible neighbours. - int asmin = s->smax; - int asmax = s->smax; + int accessibleNeighborMinHeight = span->smax; + int accessibleNeighborMaxHeight = span->smax; - for (int dir = 0; dir < 4; ++dir) + for (int direction = 0; direction < 4; ++direction) { - int dx = x + rcGetDirOffsetX(dir); - int dy = y + rcGetDirOffsetY(dir); + int dx = x + rcGetDirOffsetX(direction); + int dy = z + rcGetDirOffsetY(direction); // Skip neighbours which are out of bounds. - if (dx < 0 || dy < 0 || dx >= w || dy >= h) + if (dx < 0 || dy < 0 || dx >= xSize || dy >= zSize) { - minh = rcMin(minh, -walkableClimb - bot); + minNeighborHeight = rcMin(minNeighborHeight, -walkableClimb - bot); continue; } // From minus infinity to the first span. - rcSpan* ns = solid.spans[dx + dy*w]; - int nbot = -walkableClimb; - int ntop = ns ? (int)ns->smin : MAX_HEIGHT; - // Skip neightbour if the gap between the spans is too small. - if (rcMin(top,ntop) - rcMax(bot,nbot) > walkableHeight) - minh = rcMin(minh, nbot - bot); + const rcSpan* neighborSpan = heightfield.spans[dx + dy * xSize]; + int neighborBot = -walkableClimb; + int neighborTop = neighborSpan ? (int)neighborSpan->smin : MAX_HEIGHT; - // Rest of the spans. - for (ns = solid.spans[dx + dy*w]; ns; ns = ns->next) + // Skip neighbour if the gap between the spans is too small. + if (rcMin(top, neighborTop) - rcMax(bot, neighborBot) > walkableHeight) { - nbot = (int)ns->smax; - ntop = ns->next ? (int)ns->next->smin : MAX_HEIGHT; - // Skip neightbour if the gap between the spans is too small. - if (rcMin(top,ntop) - rcMax(bot,nbot) > walkableHeight) - { - minh = rcMin(minh, nbot - bot); + minNeighborHeight = rcMin(minNeighborHeight, neighborBot - bot); + } + + // Rest of the spans. + for (neighborSpan = heightfield.spans[dx + dy * xSize]; neighborSpan; neighborSpan = neighborSpan->next) + { + neighborBot = (int)neighborSpan->smax; + neighborTop = neighborSpan->next ? (int)neighborSpan->next->smin : MAX_HEIGHT; + // Skip neighbour if the gap between the spans is too small. + if (rcMin(top, neighborTop) - rcMax(bot, neighborBot) > walkableHeight) + { + minNeighborHeight = rcMin(minNeighborHeight, neighborBot - bot); + // Find min/max accessible neighbour height. - if (rcAbs(nbot - bot) <= walkableClimb) + if (rcAbs(neighborBot - bot) <= walkableClimb) { - if (nbot < asmin) asmin = nbot; - if (nbot > asmax) asmax = nbot; + if (neighborBot < accessibleNeighborMinHeight) accessibleNeighborMinHeight = neighborBot; + if (neighborBot > accessibleNeighborMaxHeight) accessibleNeighborMaxHeight = neighborBot; } - + } } } - + // The current span is close to a ledge if the drop to any // neighbour span is less than the walkableClimb. - if (minh < -walkableClimb) + if (minNeighborHeight < -walkableClimb) { - s->area = RC_NULL_AREA; + span->area = RC_NULL_AREA; } // If the difference between all neighbours is too large, // we are at steep slope, mark the span as ledge. - else if ((asmax - asmin) > walkableClimb) + else if ((accessibleNeighborMaxHeight - accessibleNeighborMinHeight) > walkableClimb) { - s->area = RC_NULL_AREA; + span->area = RC_NULL_AREA; } } } } } -/// @par -/// -/// For this filter, the clearance above the span is the distance from the span's -/// maximum to the next higher span's minimum. (Same grid column.) -/// -/// @see rcHeightfield, rcConfig -void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeightfield& solid) +void rcFilterWalkableLowHeightSpans(rcContext* context, const int walkableHeight, rcHeightfield& heightfield) { - rcAssert(ctx); + rcAssert(context); - rcScopedTimer timer(ctx, RC_TIMER_FILTER_WALKABLE); + rcScopedTimer timer(context, RC_TIMER_FILTER_WALKABLE); - const int w = solid.width; - const int h = solid.height; + const int xSize = heightfield.width; + const int zSize = heightfield.height; const int MAX_HEIGHT = 0xffff; // Remove walkable flag from spans which do not have enough // space above them for the agent to stand there. - for (int y = 0; y < h; ++y) + for (int z = 0; z < zSize; ++z) { - for (int x = 0; x < w; ++x) + for (int x = 0; x < xSize; ++x) { - for (rcSpan* s = solid.spans[x + y*w]; s; s = s->next) + for (rcSpan* span = heightfield.spans[x + z*xSize]; span; span = span->next) { - const int bot = (int)(s->smax); - const int top = s->next ? (int)(s->next->smin) : MAX_HEIGHT; - if ((top - bot) <= walkableHeight) - s->area = RC_NULL_AREA; + const int bot = (int)(span->smax); + const int top = span->next ? (int)(span->next->smin) : MAX_HEIGHT; + if ((top - bot) < walkableHeight) + { + span->area = RC_NULL_AREA; + } } } } diff --git a/Source/ThirdParty/recastnavigation/RecastLayers.cpp b/Source/ThirdParty/recastnavigation/RecastLayers.cpp index acc97e44f..ca37ebba7 100644 --- a/Source/ThirdParty/recastnavigation/RecastLayers.cpp +++ b/Source/ThirdParty/recastnavigation/RecastLayers.cpp @@ -17,7 +17,6 @@ // #include -#define _USE_MATH_DEFINES #include #include #include @@ -29,8 +28,21 @@ // Must be 255 or smaller (not 256) because layer IDs are stored as // a byte where 255 is a special value. -static const int RC_MAX_LAYERS = 63; -static const int RC_MAX_NEIS = 16; +#ifndef RC_MAX_LAYERS_DEF +#define RC_MAX_LAYERS_DEF 63 +#endif + +#if RC_MAX_LAYERS_DEF > 255 +#error RC_MAX_LAYERS_DEF must be 255 or smaller +#endif + +#ifndef RC_MAX_NEIS_DEF +#define RC_MAX_NEIS_DEF 16 +#endif + +// Keep type checking. +static const int RC_MAX_LAYERS = RC_MAX_LAYERS_DEF; +static const int RC_MAX_NEIS = RC_MAX_NEIS_DEF; struct rcLayerRegion { @@ -89,7 +101,7 @@ struct rcLayerSweepSpan /// See the #rcConfig documentation for more information on the configuration parameters. /// /// @see rcAllocHeightfieldLayerSet, rcCompactHeightfield, rcHeightfieldLayerSet, rcConfig -bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, +bool rcBuildHeightfieldLayers(rcContext* ctx, const rcCompactHeightfield& chf, const int borderSize, const int walkableHeight, rcHeightfieldLayerSet& lset) { diff --git a/Source/ThirdParty/recastnavigation/RecastMesh.cpp b/Source/ThirdParty/recastnavigation/RecastMesh.cpp index e99eaebb7..c2c0d5174 100644 --- a/Source/ThirdParty/recastnavigation/RecastMesh.cpp +++ b/Source/ThirdParty/recastnavigation/RecastMesh.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include @@ -35,7 +34,7 @@ static bool buildMeshAdjacency(unsigned short* polys, const int npolys, const int nverts, const int vertsPerPoly) { // Based on code by Eric Lengyel from: - // http://www.terathon.com/code/edges.php + // https://web.archive.org/web/20080704083314/http://www.terathon.com/code/edges.php int maxEdgeCount = npolys*vertsPerPoly; unsigned short* firstEdge = (unsigned short*)rcAlloc(sizeof(unsigned short)*(nverts + maxEdgeCount), RC_ALLOC_TEMP); @@ -566,7 +565,6 @@ static bool canRemoveVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned sho const int nvp = mesh.nvp; // Count number of polygons to remove. - int numRemovedVerts = 0; int numTouchedVerts = 0; int numRemainingEdges = 0; for (int i = 0; i < mesh.npolys; ++i) @@ -586,7 +584,6 @@ static bool canRemoveVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned sho } if (numRemoved) { - numRemovedVerts += numRemoved; numRemainingEdges += numVerts-(numRemoved+1); } } @@ -989,7 +986,7 @@ static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short /// limit must be retricted to <= #DT_VERTS_PER_POLYGON. /// /// @see rcAllocPolyMesh, rcContourSet, rcPolyMesh, rcConfig -bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMesh& mesh) +bool rcBuildPolyMesh(rcContext* ctx, const rcContourSet& cset, const int nvp, rcPolyMesh& mesh) { rcAssert(ctx); diff --git a/Source/ThirdParty/recastnavigation/RecastMeshDetail.cpp b/Source/ThirdParty/recastnavigation/RecastMeshDetail.cpp index 1999200c1..40f5b8c60 100644 --- a/Source/ThirdParty/recastnavigation/RecastMeshDetail.cpp +++ b/Source/ThirdParty/recastnavigation/RecastMeshDetail.cpp @@ -17,7 +17,6 @@ // #include -#define _USE_MATH_DEFINES #include #include #include @@ -284,7 +283,7 @@ static unsigned short getHeight(const float fx, const float fy, const float fz, enum EdgeValues { EV_UNDEF = -1, - EV_HULL = -2, + EV_HULL = -2 }; static int findEdge(const int* edges, int nedges, int s, int t) diff --git a/Source/ThirdParty/recastnavigation/RecastRasterization.cpp b/Source/ThirdParty/recastnavigation/RecastRasterization.cpp index a4cef7490..2a4f619fb 100644 --- a/Source/ThirdParty/recastnavigation/RecastRasterization.cpp +++ b/Source/ThirdParty/recastnavigation/RecastRasterization.cpp @@ -16,373 +16,485 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include "Recast.h" #include "RecastAlloc.h" #include "RecastAssert.h" -inline bool overlapBounds(const float* amin, const float* amax, const float* bmin, const float* bmax) +/// Check whether two bounding boxes overlap +/// +/// @param[in] aMin Min axis extents of bounding box A +/// @param[in] aMax Max axis extents of bounding box A +/// @param[in] bMin Min axis extents of bounding box B +/// @param[in] bMax Max axis extents of bounding box B +/// @returns true if the two bounding boxes overlap. False otherwise. +static bool overlapBounds(const float* aMin, const float* aMax, const float* bMin, const float* bMax) { - bool overlap = true; - overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap; - overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap; - overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap; - return overlap; + return + aMin[0] <= bMax[0] && aMax[0] >= bMin[0] && + aMin[1] <= bMax[1] && aMax[1] >= bMin[1] && + aMin[2] <= bMax[2] && aMax[2] >= bMin[2]; } -inline bool overlapInterval(unsigned short amin, unsigned short amax, - unsigned short bmin, unsigned short bmax) -{ - if (amax < bmin) return false; - if (amin > bmax) return false; - return true; -} - - +/// Allocates a new span in the heightfield. +/// Use a memory pool and free list to minimize actual allocations. +/// +/// @param[in] hf The heightfield +/// @returns A pointer to the allocated or re-used span memory. static rcSpan* allocSpan(rcHeightfield& hf) { - // If running out of memory, allocate new page and update the freelist. - if (!hf.freelist || !hf.freelist->next) + // If necessary, allocate new page and update the freelist. + if (hf.freelist == NULL || hf.freelist->next == NULL) { // Create new page. // Allocate memory for the new pool. - rcSpanPool* pool = (rcSpanPool*)rcAlloc(sizeof(rcSpanPool), RC_ALLOC_PERM); - if (!pool) return 0; + rcSpanPool* spanPool = (rcSpanPool*)rcAlloc(sizeof(rcSpanPool), RC_ALLOC_PERM); + if (spanPool == NULL) + { + return NULL; + } // Add the pool into the list of pools. - pool->next = hf.pools; - hf.pools = pool; - // Add new items to the free list. - rcSpan* freelist = hf.freelist; - rcSpan* head = &pool->items[0]; - rcSpan* it = &pool->items[RC_SPANS_PER_POOL]; + spanPool->next = hf.pools; + hf.pools = spanPool; + + // Add new spans to the free list. + rcSpan* freeList = hf.freelist; + rcSpan* head = &spanPool->items[0]; + rcSpan* it = &spanPool->items[RC_SPANS_PER_POOL]; do { --it; - it->next = freelist; - freelist = it; + it->next = freeList; + freeList = it; } while (it != head); hf.freelist = it; } - - // Pop item from in front of the free list. - rcSpan* it = hf.freelist; + + // Pop item from the front of the free list. + rcSpan* newSpan = hf.freelist; hf.freelist = hf.freelist->next; - return it; + return newSpan; } -static void freeSpan(rcHeightfield& hf, rcSpan* ptr) +/// Releases the memory used by the span back to the heightfield, so it can be re-used for new spans. +/// @param[in] hf The heightfield. +/// @param[in] span A pointer to the span to free +static void freeSpan(rcHeightfield& hf, rcSpan* span) { - if (!ptr) return; - // Add the node in front of the free list. - ptr->next = hf.freelist; - hf.freelist = ptr; -} - -static bool addSpan(rcHeightfield& hf, const int x, const int y, - const unsigned short smin, const unsigned short smax, - const unsigned char area, const int flagMergeThr) -{ - - int idx = x + y*hf.width; - - rcSpan* s = allocSpan(hf); - if (!s) - return false; - s->smin = smin; - s->smax = smax; - s->area = area; - s->next = 0; - - // Empty cell, add the first span. - if (!hf.spans[idx]) + if (span == NULL) { - hf.spans[idx] = s; - return true; + return; } - rcSpan* prev = 0; - rcSpan* cur = hf.spans[idx]; - - // Insert and merge spans. - while (cur) + // Add the span to the front of the free list. + span->next = hf.freelist; + hf.freelist = span; +} + +/// Adds a span to the heightfield. If the new span overlaps existing spans, +/// it will merge the new span with the existing ones. +/// +/// @param[in] hf Heightfield to add spans to +/// @param[in] x The new span's column cell x index +/// @param[in] z The new span's column cell z index +/// @param[in] min The new span's minimum cell index +/// @param[in] max The new span's maximum cell index +/// @param[in] areaID The new span's area type ID +/// @param[in] flagMergeThreshold How close two spans maximum extents need to be to merge area type IDs +static bool addSpan(rcHeightfield& hf, + const int x, const int z, + const unsigned short min, const unsigned short max, + const unsigned char areaID, const int flagMergeThreshold) +{ + // Create the new span. + rcSpan* newSpan = allocSpan(hf); + if (newSpan == NULL) { - if (cur->smin > s->smax) + return false; + } + newSpan->smin = min; + newSpan->smax = max; + newSpan->area = areaID; + newSpan->next = NULL; + + const int columnIndex = x + z * hf.width; + rcSpan* previousSpan = NULL; + rcSpan* currentSpan = hf.spans[columnIndex]; + + // Insert the new span, possibly merging it with existing spans. + while (currentSpan != NULL) + { + if (currentSpan->smin > newSpan->smax) { - // Current span is further than the new span, break. + // Current span is completely after the new span, break. break; } - else if (cur->smax < s->smin) + + if (currentSpan->smax < newSpan->smin) { - // Current span is before the new span advance. - prev = cur; - cur = cur->next; + // Current span is completely before the new span. Keep going. + previousSpan = currentSpan; + currentSpan = currentSpan->next; } else { - // Merge spans. - if (cur->smin < s->smin) - s->smin = cur->smin; - if (cur->smax > s->smax) - s->smax = cur->smax; + // The new span overlaps with an existing span. Merge them. + if (currentSpan->smin < newSpan->smin) + { + newSpan->smin = currentSpan->smin; + } + if (currentSpan->smax > newSpan->smax) + { + newSpan->smax = currentSpan->smax; + } // Merge flags. - if (rcAbs((int)s->smax - (int)cur->smax) <= flagMergeThr) - s->area = rcMax(s->area, cur->area); + if (rcAbs((int)newSpan->smax - (int)currentSpan->smax) <= flagMergeThreshold) + { + // Higher area ID numbers indicate higher resolution priority. + newSpan->area = rcMax(newSpan->area, currentSpan->area); + } - // Remove current span. - rcSpan* next = cur->next; - freeSpan(hf, cur); - if (prev) - prev->next = next; + // Remove the current span since it's now merged with newSpan. + // Keep going because there might be other overlapping spans that also need to be merged. + rcSpan* next = currentSpan->next; + freeSpan(hf, currentSpan); + if (previousSpan) + { + previousSpan->next = next; + } else - hf.spans[idx] = next; - cur = next; + { + hf.spans[columnIndex] = next; + } + currentSpan = next; } } - // Insert new span. - if (prev) + // Insert new span after prev + if (previousSpan != NULL) { - s->next = prev->next; - prev->next = s; + newSpan->next = previousSpan->next; + previousSpan->next = newSpan; } else { - s->next = hf.spans[idx]; - hf.spans[idx] = s; + // This span should go before the others in the list + newSpan->next = hf.spans[columnIndex]; + hf.spans[columnIndex] = newSpan; } return true; } -/// @par -/// -/// The span addition can be set to favor flags. If the span is merged to -/// another span and the new @p smax is within @p flagMergeThr units -/// from the existing span, the span flags are merged. -/// -/// @see rcHeightfield, rcSpan. -bool rcAddSpan(rcContext* ctx, rcHeightfield& hf, const int x, const int y, - const unsigned short smin, const unsigned short smax, - const unsigned char area, const int flagMergeThr) +bool rcAddSpan(rcContext* context, rcHeightfield& heightfield, + const int x, const int z, + const unsigned short spanMin, const unsigned short spanMax, + const unsigned char areaID, const int flagMergeThreshold) { - rcAssert(ctx); + rcAssert(context); - if (!addSpan(hf, x, y, smin, smax, area, flagMergeThr)) + if (!addSpan(heightfield, x, z, spanMin, spanMax, areaID, flagMergeThreshold)) { - ctx->log(RC_LOG_ERROR, "rcAddSpan: Out of memory."); + context->log(RC_LOG_ERROR, "rcAddSpan: Out of memory."); return false; } return true; } -// divides a convex polygons into two convex polygons on both sides of a line -static void dividePoly(const float* in, int nin, - float* out1, int* nout1, - float* out2, int* nout2, - float x, int axis) +enum rcAxis { - float d[12]; - for (int i = 0; i < nin; ++i) - d[i] = x - in[i*3+axis]; + RC_AXIS_X = 0, + RC_AXIS_Y = 1, + RC_AXIS_Z = 2 +}; - int m = 0, n = 0; - for (int i = 0, j = nin-1; i < nin; j=i, ++i) +/// Divides a convex polygon of max 12 vertices into two convex polygons +/// across a separating axis. +/// +/// @param[in] inVerts The input polygon vertices +/// @param[in] inVertsCount The number of input polygon vertices +/// @param[out] outVerts1 Resulting polygon 1's vertices +/// @param[out] outVerts1Count The number of resulting polygon 1 vertices +/// @param[out] outVerts2 Resulting polygon 2's vertices +/// @param[out] outVerts2Count The number of resulting polygon 2 vertices +/// @param[in] axisOffset THe offset along the specified axis +/// @param[in] axis The separating axis +static void dividePoly(const float* inVerts, int inVertsCount, + float* outVerts1, int* outVerts1Count, + float* outVerts2, int* outVerts2Count, + float axisOffset, rcAxis axis) +{ + rcAssert(inVertsCount <= 12); + + // How far positive or negative away from the separating axis is each vertex. + float inVertAxisDelta[12]; + for (int inVert = 0; inVert < inVertsCount; ++inVert) { - bool ina = d[j] >= 0; - bool inb = d[i] >= 0; - if (ina != inb) + inVertAxisDelta[inVert] = axisOffset - inVerts[inVert * 3 + axis]; + } + + int poly1Vert = 0; + int poly2Vert = 0; + for (int inVertA = 0, inVertB = inVertsCount - 1; inVertA < inVertsCount; inVertB = inVertA, ++inVertA) + { + // If the two vertices are on the same side of the separating axis + bool sameSide = (inVertAxisDelta[inVertA] >= 0) == (inVertAxisDelta[inVertB] >= 0); + + if (!sameSide) { - float s = d[j] / (d[j] - d[i]); - out1[m*3+0] = in[j*3+0] + (in[i*3+0] - in[j*3+0])*s; - out1[m*3+1] = in[j*3+1] + (in[i*3+1] - in[j*3+1])*s; - out1[m*3+2] = in[j*3+2] + (in[i*3+2] - in[j*3+2])*s; - rcVcopy(out2 + n*3, out1 + m*3); - m++; - n++; - // add the i'th point to the right polygon. Do NOT add points that are on the dividing line + float s = inVertAxisDelta[inVertB] / (inVertAxisDelta[inVertB] - inVertAxisDelta[inVertA]); + outVerts1[poly1Vert * 3 + 0] = inVerts[inVertB * 3 + 0] + (inVerts[inVertA * 3 + 0] - inVerts[inVertB * 3 + 0]) * s; + outVerts1[poly1Vert * 3 + 1] = inVerts[inVertB * 3 + 1] + (inVerts[inVertA * 3 + 1] - inVerts[inVertB * 3 + 1]) * s; + outVerts1[poly1Vert * 3 + 2] = inVerts[inVertB * 3 + 2] + (inVerts[inVertA * 3 + 2] - inVerts[inVertB * 3 + 2]) * s; + rcVcopy(&outVerts2[poly2Vert * 3], &outVerts1[poly1Vert * 3]); + poly1Vert++; + poly2Vert++; + + // add the inVertA point to the right polygon. Do NOT add points that are on the dividing line // since these were already added above - if (d[i] > 0) + if (inVertAxisDelta[inVertA] > 0) { - rcVcopy(out1 + m*3, in + i*3); - m++; + rcVcopy(&outVerts1[poly1Vert * 3], &inVerts[inVertA * 3]); + poly1Vert++; } - else if (d[i] < 0) + else if (inVertAxisDelta[inVertA] < 0) { - rcVcopy(out2 + n*3, in + i*3); - n++; + rcVcopy(&outVerts2[poly2Vert * 3], &inVerts[inVertA * 3]); + poly2Vert++; } } - else // same side + else { - // add the i'th point to the right polygon. Addition is done even for points on the dividing line - if (d[i] >= 0) + // add the inVertA point to the right polygon. Addition is done even for points on the dividing line + if (inVertAxisDelta[inVertA] >= 0) { - rcVcopy(out1 + m*3, in + i*3); - m++; - if (d[i] != 0) + rcVcopy(&outVerts1[poly1Vert * 3], &inVerts[inVertA * 3]); + poly1Vert++; + if (inVertAxisDelta[inVertA] != 0) + { continue; + } } - rcVcopy(out2 + n*3, in + i*3); - n++; + rcVcopy(&outVerts2[poly2Vert * 3], &inVerts[inVertA * 3]); + poly2Vert++; } } - *nout1 = m; - *nout2 = n; + *outVerts1Count = poly1Vert; + *outVerts2Count = poly2Vert; } - - +/// Rasterize a single triangle to the heightfield. +/// +/// This code is extremely hot, so much care should be given to maintaining maximum perf here. +/// +/// @param[in] v0 Triangle vertex 0 +/// @param[in] v1 Triangle vertex 1 +/// @param[in] v2 Triangle vertex 2 +/// @param[in] areaID The area ID to assign to the rasterized spans +/// @param[in] hf Heightfield to rasterize into +/// @param[in] hfBBMin The min extents of the heightfield bounding box +/// @param[in] hfBBMax The max extents of the heightfield bounding box +/// @param[in] cellSize The x and z axis size of a voxel in the heightfield +/// @param[in] inverseCellSize 1 / cellSize +/// @param[in] inverseCellHeight 1 / cellHeight +/// @param[in] flagMergeThreshold The threshold in which area flags will be merged +/// @returns true if the operation completes successfully. false if there was an error adding spans to the heightfield. static bool rasterizeTri(const float* v0, const float* v1, const float* v2, - const unsigned char area, rcHeightfield& hf, - const float* bmin, const float* bmax, - const float cs, const float ics, const float ich, - const int flagMergeThr) + const unsigned char areaID, rcHeightfield& hf, + const float* hfBBMin, const float* hfBBMax, + const float cellSize, const float inverseCellSize, const float inverseCellHeight, + const int flagMergeThreshold) { + // Calculate the bounding box of the triangle. + float triBBMin[3]; + rcVcopy(triBBMin, v0); + rcVmin(triBBMin, v1); + rcVmin(triBBMin, v2); + + float triBBMax[3]; + rcVcopy(triBBMax, v0); + rcVmax(triBBMax, v1); + rcVmax(triBBMax, v2); + + // If the triangle does not touch the bounding box of the heightfield, skip the triangle. + if (!overlapBounds(triBBMin, triBBMax, hfBBMin, hfBBMax)) + { + return true; + } + const int w = hf.width; const int h = hf.height; - float tmin[3], tmax[3]; - const float by = bmax[1] - bmin[1]; - - // Calculate the bounding box of the triangle. - rcVcopy(tmin, v0); - rcVcopy(tmax, v0); - rcVmin(tmin, v1); - rcVmin(tmin, v2); - rcVmax(tmax, v1); - rcVmax(tmax, v2); - - // If the triangle does not touch the bbox of the heightfield, skip the triagle. - if (!overlapBounds(bmin, bmax, tmin, tmax)) - return true; - - // Calculate the footprint of the triangle on the grid's y-axis - int y0 = (int)((tmin[2] - bmin[2])*ics); - int y1 = (int)((tmax[2] - bmin[2])*ics); - y0 = rcClamp(y0, 0, h-1); - y1 = rcClamp(y1, 0, h-1); - + const float by = hfBBMax[1] - hfBBMin[1]; + + // Calculate the footprint of the triangle on the grid's z-axis + int z0 = (int)((triBBMin[2] - hfBBMin[2]) * inverseCellSize); + int z1 = (int)((triBBMax[2] - hfBBMin[2]) * inverseCellSize); + + // use -1 rather than 0 to cut the polygon properly at the start of the tile + z0 = rcClamp(z0, -1, h - 1); + z1 = rcClamp(z1, 0, h - 1); + // Clip the triangle into all grid cells it touches. - float buf[7*3*4]; - float *in = buf, *inrow = buf+7*3, *p1 = inrow+7*3, *p2 = p1+7*3; + float buf[7 * 3 * 4]; + float* in = buf; + float* inRow = buf + 7 * 3; + float* p1 = inRow + 7 * 3; + float* p2 = p1 + 7 * 3; rcVcopy(&in[0], v0); - rcVcopy(&in[1*3], v1); - rcVcopy(&in[2*3], v2); - int nvrow, nvIn = 3; - - for (int y = y0; y <= y1; ++y) + rcVcopy(&in[1 * 3], v1); + rcVcopy(&in[2 * 3], v2); + int nvRow; + int nvIn = 3; + + for (int z = z0; z <= z1; ++z) { // Clip polygon to row. Store the remaining polygon as well - const float cz = bmin[2] + y*cs; - dividePoly(in, nvIn, inrow, &nvrow, p1, &nvIn, cz+cs, 2); + const float cellZ = hfBBMin[2] + (float)z * cellSize; + dividePoly(in, nvIn, inRow, &nvRow, p1, &nvIn, cellZ + cellSize, RC_AXIS_Z); rcSwap(in, p1); - if (nvrow < 3) continue; - // find the horizontal bounds in the row - float minX = inrow[0], maxX = inrow[0]; - for (int i=1; i inrow[i*3]) minX = inrow[i*3]; - if (maxX < inrow[i*3]) maxX = inrow[i*3]; + continue; } - int x0 = (int)((minX - bmin[0])*ics); - int x1 = (int)((maxX - bmin[0])*ics); - x0 = rcClamp(x0, 0, w-1); - x1 = rcClamp(x1, 0, w-1); + if (z < 0) + { + continue; + } + + // find X-axis bounds of the row + float minX = inRow[0]; + float maxX = inRow[0]; + for (int vert = 1; vert < nvRow; ++vert) + { + if (minX > inRow[vert * 3]) + { + minX = inRow[vert * 3]; + } + if (maxX < inRow[vert * 3]) + { + maxX = inRow[vert * 3]; + } + } + int x0 = (int)((minX - hfBBMin[0]) * inverseCellSize); + int x1 = (int)((maxX - hfBBMin[0]) * inverseCellSize); + if (x1 < 0 || x0 >= w) + { + continue; + } + x0 = rcClamp(x0, -1, w - 1); + x1 = rcClamp(x1, 0, w - 1); - int nv, nv2 = nvrow; + int nv; + int nv2 = nvRow; for (int x = x0; x <= x1; ++x) { // Clip polygon to column. store the remaining polygon as well - const float cx = bmin[0] + x*cs; - dividePoly(inrow, nv2, p1, &nv, p2, &nv2, cx+cs, 0); - rcSwap(inrow, p2); - if (nv < 3) continue; + const float cx = hfBBMin[0] + (float)x * cellSize; + dividePoly(inRow, nv2, p1, &nv, p2, &nv2, cx + cellSize, RC_AXIS_X); + rcSwap(inRow, p2); + + if (nv < 3) + { + continue; + } + if (x < 0) + { + continue; + } // Calculate min and max of the span. - float smin = p1[1], smax = p1[1]; - for (int i = 1; i < nv; ++i) + float spanMin = p1[1]; + float spanMax = p1[1]; + for (int vert = 1; vert < nv; ++vert) { - smin = rcMin(smin, p1[i*3+1]); - smax = rcMax(smax, p1[i*3+1]); + spanMin = rcMin(spanMin, p1[vert * 3 + 1]); + spanMax = rcMax(spanMax, p1[vert * 3 + 1]); } - smin -= bmin[1]; - smax -= bmin[1]; - // Skip the span if it is outside the heightfield bbox - if (smax < 0.0f) continue; - if (smin > by) continue; - // Clamp the span to the heightfield bbox. - if (smin < 0.0f) smin = 0; - if (smax > by) smax = by; + spanMin -= hfBBMin[1]; + spanMax -= hfBBMin[1]; + // Skip the span if it's completely outside the heightfield bounding box + if (spanMax < 0.0f) + { + continue; + } + if (spanMin > by) + { + continue; + } + + // Clamp the span to the heightfield bounding box. + if (spanMin < 0.0f) + { + spanMin = 0; + } + if (spanMax > by) + { + spanMax = by; + } + // Snap the span to the heightfield height grid. - unsigned short ismin = (unsigned short)rcClamp((int)floorf(smin * ich), 0, RC_SPAN_MAX_HEIGHT); - unsigned short ismax = (unsigned short)rcClamp((int)ceilf(smax * ich), (int)ismin+1, RC_SPAN_MAX_HEIGHT); - - if (!addSpan(hf, x, y, ismin, ismax, area, flagMergeThr)) + unsigned short spanMinCellIndex = (unsigned short)rcClamp((int)floorf(spanMin * inverseCellHeight), 0, RC_SPAN_MAX_HEIGHT); + unsigned short spanMaxCellIndex = (unsigned short)rcClamp((int)ceilf(spanMax * inverseCellHeight), (int)spanMinCellIndex + 1, RC_SPAN_MAX_HEIGHT); + + if (!addSpan(hf, x, z, spanMinCellIndex, spanMaxCellIndex, areaID, flagMergeThreshold)) + { return false; + } } } return true; } -/// @par -/// -/// No spans will be added if the triangle does not overlap the heightfield grid. -/// -/// @see rcHeightfield -bool rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const float* v2, - const unsigned char area, rcHeightfield& solid, - const int flagMergeThr) +bool rcRasterizeTriangle(rcContext* context, + const float* v0, const float* v1, const float* v2, + const unsigned char areaID, rcHeightfield& heightfield, const int flagMergeThreshold) { - rcAssert(ctx); + rcAssert(context != NULL); - rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES); + rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES); - const float ics = 1.0f/solid.cs; - const float ich = 1.0f/solid.ch; - if (!rasterizeTri(v0, v1, v2, area, solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) + // Rasterize the single triangle. + const float inverseCellSize = 1.0f / heightfield.cs; + const float inverseCellHeight = 1.0f / heightfield.ch; + if (!rasterizeTri(v0, v1, v2, areaID, heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold)) { - ctx->log(RC_LOG_ERROR, "rcRasterizeTriangle: Out of memory."); + context->log(RC_LOG_ERROR, "rcRasterizeTriangle: Out of memory."); return false; } return true; } -/// @par -/// -/// Spans will only be added for triangles that overlap the heightfield grid. -/// -/// @see rcHeightfield -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, - const int* tris, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr) +bool rcRasterizeTriangles(rcContext* context, + const float* verts, const int /*nv*/, + const int* tris, const unsigned char* triAreaIDs, const int numTris, + rcHeightfield& heightfield, const int flagMergeThreshold) { - rcAssert(ctx); + rcAssert(context != NULL); - rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES); + rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES); - const float ics = 1.0f/solid.cs; - const float ich = 1.0f/solid.ch; - // Rasterize triangles. - for (int i = 0; i < nt; ++i) + // Rasterize the triangles. + const float inverseCellSize = 1.0f / heightfield.cs; + const float inverseCellHeight = 1.0f / heightfield.ch; + for (int triIndex = 0; triIndex < numTris; ++triIndex) { - const float* v0 = &verts[tris[i*3+0]*3]; - const float* v1 = &verts[tris[i*3+1]*3]; - const float* v2 = &verts[tris[i*3+2]*3]; - // Rasterize. - if (!rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) + const float* v0 = &verts[tris[triIndex * 3 + 0] * 3]; + const float* v1 = &verts[tris[triIndex * 3 + 1] * 3]; + const float* v2 = &verts[tris[triIndex * 3 + 2] * 3]; + if (!rasterizeTri(v0, v1, v2, triAreaIDs[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold)) { - ctx->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); + context->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); return false; } } @@ -390,31 +502,26 @@ bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, return true; } -/// @par -/// -/// Spans will only be added for triangles that overlap the heightfield grid. -/// -/// @see rcHeightfield -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, - const unsigned short* tris, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr) +bool rcRasterizeTriangles(rcContext* context, + const float* verts, const int /*nv*/, + const unsigned short* tris, const unsigned char* triAreaIDs, const int numTris, + rcHeightfield& heightfield, const int flagMergeThreshold) { - rcAssert(ctx); + rcAssert(context != NULL); - rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES); - - const float ics = 1.0f/solid.cs; - const float ich = 1.0f/solid.ch; - // Rasterize triangles. - for (int i = 0; i < nt; ++i) + rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES); + + // Rasterize the triangles. + const float inverseCellSize = 1.0f / heightfield.cs; + const float inverseCellHeight = 1.0f / heightfield.ch; + for (int triIndex = 0; triIndex < numTris; ++triIndex) { - const float* v0 = &verts[tris[i*3+0]*3]; - const float* v1 = &verts[tris[i*3+1]*3]; - const float* v2 = &verts[tris[i*3+2]*3]; - // Rasterize. - if (!rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) + const float* v0 = &verts[tris[triIndex * 3 + 0] * 3]; + const float* v1 = &verts[tris[triIndex * 3 + 1] * 3]; + const float* v2 = &verts[tris[triIndex * 3 + 2] * 3]; + if (!rasterizeTri(v0, v1, v2, triAreaIDs[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold)) { - ctx->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); + context->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); return false; } } @@ -422,30 +529,25 @@ bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, return true; } -/// @par -/// -/// Spans will only be added for triangles that overlap the heightfield grid. -/// -/// @see rcHeightfield -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr) +bool rcRasterizeTriangles(rcContext* context, + const float* verts, const unsigned char* triAreaIDs, const int numTris, + rcHeightfield& heightfield, const int flagMergeThreshold) { - rcAssert(ctx); + rcAssert(context != NULL); + + rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES); - rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES); - - const float ics = 1.0f/solid.cs; - const float ich = 1.0f/solid.ch; - // Rasterize triangles. - for (int i = 0; i < nt; ++i) + // Rasterize the triangles. + const float inverseCellSize = 1.0f / heightfield.cs; + const float inverseCellHeight = 1.0f / heightfield.ch; + for (int triIndex = 0; triIndex < numTris; ++triIndex) { - const float* v0 = &verts[(i*3+0)*3]; - const float* v1 = &verts[(i*3+1)*3]; - const float* v2 = &verts[(i*3+2)*3]; - // Rasterize. - if (!rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) + const float* v0 = &verts[(triIndex * 3 + 0) * 3]; + const float* v1 = &verts[(triIndex * 3 + 1) * 3]; + const float* v2 = &verts[(triIndex * 3 + 2) * 3]; + if (!rasterizeTri(v0, v1, v2, triAreaIDs[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold)) { - ctx->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); + context->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); return false; } } diff --git a/Source/ThirdParty/recastnavigation/RecastRegion.cpp b/Source/ThirdParty/recastnavigation/RecastRegion.cpp index 48318688b..4a7e841a9 100644 --- a/Source/ThirdParty/recastnavigation/RecastRegion.cpp +++ b/Source/ThirdParty/recastnavigation/RecastRegion.cpp @@ -17,7 +17,6 @@ // #include -#define _USE_MATH_DEFINES #include #include #include From 2655dd12d63b89155e7dabe5fa022e0b69e3058a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 19 Sep 2023 13:40:01 +0200 Subject: [PATCH 042/188] Reapply e218cc417fa184cdea10c7024f174cfee505da35 but a little better --- .../ThirdParty/recastnavigation/DetourCommon.cpp | 7 +++++++ .../ThirdParty/recastnavigation/DetourCommon.h | 2 ++ .../recastnavigation/DetourNavMeshQuery.cpp | 16 +++++++++++----- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Source/ThirdParty/recastnavigation/DetourCommon.cpp b/Source/ThirdParty/recastnavigation/DetourCommon.cpp index b8457828e..8a9a4a42a 100644 --- a/Source/ThirdParty/recastnavigation/DetourCommon.cpp +++ b/Source/ThirdParty/recastnavigation/DetourCommon.cpp @@ -183,6 +183,13 @@ float dtDistancePtSegSqr2D(const float* pt, const float* p, const float* q, floa return dx*dx + dz*dz; } +float dtDistancePtPtSqr2D(const float* pt, const float* p) +{ + float dx = pt[0] - p[0]; + float dz = pt[2] - p[2]; + return dx*dx + dz*dz; +} + void dtCalcPolyCenter(float* tc, const unsigned short* idx, int nidx, const float* verts) { tc[0] = 0.0f; diff --git a/Source/ThirdParty/recastnavigation/DetourCommon.h b/Source/ThirdParty/recastnavigation/DetourCommon.h index d54bc7edc..656311e90 100644 --- a/Source/ThirdParty/recastnavigation/DetourCommon.h +++ b/Source/ThirdParty/recastnavigation/DetourCommon.h @@ -416,6 +416,8 @@ bool dtDistancePtPolyEdgesSqr(const float* pt, const float* verts, const int nve float dtDistancePtSegSqr2D(const float* pt, const float* p, const float* q, float& t); +float dtDistancePtPtSqr2D(const float* pt, const float* p); + /// Derives the centroid of a convex polygon. /// @param[out] tc The centroid of the polgyon. [(x, y, z)] /// @param[in] idx The polygon indices. [(vertIndex) * @p nidx] diff --git a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.cpp b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.cpp index cebe26b5d..7faefde81 100644 --- a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.cpp +++ b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.cpp @@ -482,12 +482,18 @@ dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const f v = &randomTile->verts[randomPoly->verts[j]*3]; dtVcopy(&verts[j*3],v); } - - const float s = frand(); - const float t = frand(); - + float pt[3]; - dtRandomPointInConvexPoly(verts, randomPoly->vertCount, areas, s, t, pt); + int checksLimit = 100; + do + { + const float s = frand(); + const float t = frand(); + dtRandomPointInConvexPoly(verts, randomPoly->vertCount, areas, s, t, pt); + } + while (dtDistancePtPtSqr2D(centerPos, pt) > radiusSqr && checksLimit-- > 0); + if (checksLimit <= 0) + return DT_FAILURE; closestPointOnPoly(randomPolyRef, pt, pt, NULL); From 255e47fa1e83fa74ad6c7b3b1ae733b25fa517c5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 19 Sep 2023 14:00:37 +0200 Subject: [PATCH 043/188] Fix crash due to invalid message in Assimp --- Source/Engine/Core/Types/String.cpp | 2 +- Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Core/Types/String.cpp b/Source/Engine/Core/Types/String.cpp index 98b2c08dd..dab000873 100644 --- a/Source/Engine/Core/Types/String.cpp +++ b/Source/Engine/Core/Types/String.cpp @@ -72,7 +72,7 @@ void String::Set(const char* chars, int32 length) } _length = length; } - if (chars) + if (chars && length) StringUtils::ConvertANSI2UTF16(chars, _data, length, _length); } diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index daecf8c45..d0fba8f12 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -42,7 +42,16 @@ public: void write(const char* message) override { String s(message); - s.Replace('\n', ' '); + if (s.Length() <= 0) + return; + for (int32 i = 0; i < s.Length(); i++) + { + Char& c = s[i]; + if (c == '\n') + c = ' '; + else if (c >= 255) + c = '?'; + } LOG(Info, "[Assimp]: {0}", s); } }; From 49a6b5734a033c05cda6a127265f2ba0686f5738 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 19 Sep 2023 14:14:57 +0200 Subject: [PATCH 044/188] Fix crash when setting material instance base material before it's loaded --- Source/Engine/Content/Assets/MaterialInstance.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Engine/Content/Assets/MaterialInstance.cpp b/Source/Engine/Content/Assets/MaterialInstance.cpp index 7581692e1..3bf0c54cd 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.cpp +++ b/Source/Engine/Content/Assets/MaterialInstance.cpp @@ -205,8 +205,6 @@ void MaterialInstance::Bind(BindParameters& params) Asset::LoadResult MaterialInstance::load() { - ASSERT(_baseMaterial == nullptr); - // Get main chunk auto chunk0 = GetChunk(0); if (chunk0 == nullptr || chunk0->IsMissing()) @@ -229,6 +227,7 @@ Asset::LoadResult MaterialInstance::load() else { // Clear parameters if has no material loaded + _baseMaterial = nullptr; Params.Dispose(); ParamsChanged(); } From 4e44002259bd10af946b60eab855127dd64526dc Mon Sep 17 00:00:00 2001 From: Andrej Stojkovikj <40241148+AndrejStojkovic@users.noreply.github.com> Date: Tue, 19 Sep 2023 15:47:38 +0200 Subject: [PATCH 045/188] Fix SmoothDamp missed type casting for Vector 3 --- Source/Engine/Core/Math/Vector3.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Core/Math/Vector3.cs b/Source/Engine/Core/Math/Vector3.cs index 53ec824fe..d009a692e 100644 --- a/Source/Engine/Core/Math/Vector3.cs +++ b/Source/Engine/Core/Math/Vector3.cs @@ -1092,7 +1092,7 @@ namespace FlaxEngine float maxChangeSpeed = maxSpeed * smoothTime; float changeSq = maxChangeSpeed * maxChangeSpeed; - float sqrMag = change_x * change_x + change_y * change_y + change_z * change_z; + Real sqrMag = change_x * change_x + change_y * change_y + change_z * change_z; if (sqrMag > changeSq) { var mag = (Real)Math.Sqrt(sqrMag); From e1f528ec9acb74689d95b169252e6dfdb1dcfcb3 Mon Sep 17 00:00:00 2001 From: Andrej Stojkovikj <40241148+AndrejStojkovic@users.noreply.github.com> Date: Tue, 19 Sep 2023 15:48:16 +0200 Subject: [PATCH 046/188] Fix SmoothDamp missed type casting for Vector2 --- Source/Engine/Core/Math/Vector2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Core/Math/Vector2.cs b/Source/Engine/Core/Math/Vector2.cs index cb0cd17d1..3eea3890f 100644 --- a/Source/Engine/Core/Math/Vector2.cs +++ b/Source/Engine/Core/Math/Vector2.cs @@ -1001,7 +1001,7 @@ namespace FlaxEngine float maxChangeSpeed = maxSpeed * smoothTime; float changeSq = maxChangeSpeed * maxChangeSpeed; - float sqrDist = change_x * change_x + change_y * change_y; + Real sqrDist = change_x * change_x + change_y * change_y; if (sqrDist > changeSq) { var dist = (Real)Math.Sqrt(sqrDist); From 3f299f4cf6aba92760b644b980cc977e4a9295e2 Mon Sep 17 00:00:00 2001 From: Andrej Stojkovikj Date: Tue, 19 Sep 2023 15:57:33 +0200 Subject: [PATCH 047/188] Just in case change type cast for other variables as well --- Source/Engine/Core/Math/Vector2.cs | 10 +++++----- Source/Engine/Core/Math/Vector3.cs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Source/Engine/Core/Math/Vector2.cs b/Source/Engine/Core/Math/Vector2.cs index 3eea3890f..568c51764 100644 --- a/Source/Engine/Core/Math/Vector2.cs +++ b/Source/Engine/Core/Math/Vector2.cs @@ -991,16 +991,16 @@ namespace FlaxEngine public static Vector2 SmoothDamp(Vector2 current, Vector2 target, ref Vector2 currentVelocity, float smoothTime, [DefaultValue("float.PositiveInfinity")] float maxSpeed, [DefaultValue("Time.DeltaTime")] float deltaTime) { smoothTime = Mathf.Max(0.0001f, smoothTime); - float a = 2f / smoothTime; - float b = a * deltaTime; - float e = 1f / (1f + b + 0.48f * b * b + 0.235f * b * b * b); + Real a = 2f / smoothTime; + Real b = a * deltaTime; + Real e = 1f / (1f + b + 0.48f * b * b + 0.235f * b * b * b); Real change_x = current.X - target.X; Real change_y = current.Y - target.Y; Vector2 originalTo = target; - float maxChangeSpeed = maxSpeed * smoothTime; - float changeSq = maxChangeSpeed * maxChangeSpeed; + Real maxChangeSpeed = maxSpeed * smoothTime; + Real changeSq = maxChangeSpeed * maxChangeSpeed; Real sqrDist = change_x * change_x + change_y * change_y; if (sqrDist > changeSq) { diff --git a/Source/Engine/Core/Math/Vector3.cs b/Source/Engine/Core/Math/Vector3.cs index d009a692e..0ad557a32 100644 --- a/Source/Engine/Core/Math/Vector3.cs +++ b/Source/Engine/Core/Math/Vector3.cs @@ -1080,9 +1080,9 @@ namespace FlaxEngine public static Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime, [DefaultValue("float.PositiveInfinity")] float maxSpeed, [DefaultValue("Time.DeltaTime")] float deltaTime) { smoothTime = Mathf.Max(0.0001f, smoothTime); - float a = 2f / smoothTime; - float b = a * deltaTime; - float e = 1f / (1f + b + 0.48f * b * b + 0.235f * b * b * b); + Real a = 2f / smoothTime; + Real b = a * deltaTime; + Real e = 1f / (1f + b + 0.48f * b * b + 0.235f * b * b * b); Real change_x = current.X - target.X; Real change_y = current.Y - target.Y; @@ -1090,8 +1090,8 @@ namespace FlaxEngine Vector3 originalTo = target; - float maxChangeSpeed = maxSpeed * smoothTime; - float changeSq = maxChangeSpeed * maxChangeSpeed; + Real maxChangeSpeed = maxSpeed * smoothTime; + Real changeSq = maxChangeSpeed * maxChangeSpeed; Real sqrMag = change_x * change_x + change_y * change_y + change_z * change_z; if (sqrMag > changeSq) { From 8930c7ba565def87f8c618d7359dfb036eb97ad4 Mon Sep 17 00:00:00 2001 From: Andrej Stojkovikj Date: Tue, 19 Sep 2023 16:09:18 +0200 Subject: [PATCH 048/188] Implemented static functions for length and length squared for Vectors --- Source/Engine/Core/Math/Vector2.cs | 18 ++++++++++++++++++ Source/Engine/Core/Math/Vector3.cs | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/Source/Engine/Core/Math/Vector2.cs b/Source/Engine/Core/Math/Vector2.cs index 568c51764..e953acfd6 100644 --- a/Source/Engine/Core/Math/Vector2.cs +++ b/Source/Engine/Core/Math/Vector2.cs @@ -821,6 +821,24 @@ namespace FlaxEngine return value; } + /// + /// Returns the length of a vector. + /// + /// The vector to get the length from. + public static Real Length(Vector2 vector) + { + return (Real)Mathf.Sqrt(vector.X * vector.X + vector.Y * vector.Y); + } + + /// + /// Returns the length squared of a vector. + /// + /// The vector to get the length squared from. + public static Real LengthSquared(Vector2 vector) + { + return vector.X * vector.X + vector.Y * vector.Y; + } + /// /// Makes sure that Length of the output vector is always below max and above 0. /// diff --git a/Source/Engine/Core/Math/Vector3.cs b/Source/Engine/Core/Math/Vector3.cs index 0ad557a32..c4bcf6979 100644 --- a/Source/Engine/Core/Math/Vector3.cs +++ b/Source/Engine/Core/Math/Vector3.cs @@ -963,6 +963,24 @@ namespace FlaxEngine return value; } + /// + /// Returns the length of a vector. + /// + /// The vector to get the length from. + public static Real Length(Vector3 vector) + { + return (Real)Mathf.Sqrt(vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z); + } + + /// + /// Returns the length squared of a vector. + /// + /// The vector to get the length squared from. + public static Real LengthSquared(Vector3 vector) + { + return vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z; + } + /// /// Makes sure that Length of the output vector is always below max and above 0. /// From 4c5168a976f7f4b599c7ec513e3e99b2b892c087 Mon Sep 17 00:00:00 2001 From: Denys Date: Tue, 19 Sep 2023 16:50:17 +0200 Subject: [PATCH 049/188] Use `xdg-open` to open file manager in Linux `nautilus` is GNOME file manager. Other distros might use another FM. As a common ground we can use `xdg-open` which is a part of `freedesktop.org` and should be available almost everywhere. --- Source/Engine/Platform/Linux/LinuxFileSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp index f0d5ff499..279813470 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp +++ b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp @@ -136,7 +136,7 @@ bool LinuxFileSystem::ShowFileExplorer(const StringView& path) { const StringAsANSI<> pathAnsi(*path, path.Length()); char cmd[2048]; - sprintf(cmd, "nautilus %s &", pathAnsi.Get()); + sprintf(cmd, "xdg-open %s &", pathAnsi.Get()); system(cmd); return false; } From cb460af2646090718b7bb30c6df652ba91f3d139 Mon Sep 17 00:00:00 2001 From: Andrej Stojkovikj Date: Tue, 19 Sep 2023 19:29:59 +0200 Subject: [PATCH 050/188] Revert last feature since feature already exists --- Source/Engine/Core/Math/Vector2.cs | 18 ------------------ Source/Engine/Core/Math/Vector3.cs | 18 ------------------ 2 files changed, 36 deletions(-) diff --git a/Source/Engine/Core/Math/Vector2.cs b/Source/Engine/Core/Math/Vector2.cs index e953acfd6..568c51764 100644 --- a/Source/Engine/Core/Math/Vector2.cs +++ b/Source/Engine/Core/Math/Vector2.cs @@ -821,24 +821,6 @@ namespace FlaxEngine return value; } - /// - /// Returns the length of a vector. - /// - /// The vector to get the length from. - public static Real Length(Vector2 vector) - { - return (Real)Mathf.Sqrt(vector.X * vector.X + vector.Y * vector.Y); - } - - /// - /// Returns the length squared of a vector. - /// - /// The vector to get the length squared from. - public static Real LengthSquared(Vector2 vector) - { - return vector.X * vector.X + vector.Y * vector.Y; - } - /// /// Makes sure that Length of the output vector is always below max and above 0. /// diff --git a/Source/Engine/Core/Math/Vector3.cs b/Source/Engine/Core/Math/Vector3.cs index c4bcf6979..0ad557a32 100644 --- a/Source/Engine/Core/Math/Vector3.cs +++ b/Source/Engine/Core/Math/Vector3.cs @@ -963,24 +963,6 @@ namespace FlaxEngine return value; } - /// - /// Returns the length of a vector. - /// - /// The vector to get the length from. - public static Real Length(Vector3 vector) - { - return (Real)Mathf.Sqrt(vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z); - } - - /// - /// Returns the length squared of a vector. - /// - /// The vector to get the length squared from. - public static Real LengthSquared(Vector3 vector) - { - return vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z; - } - /// /// Makes sure that Length of the output vector is always below max and above 0. /// From 35ee890f9fcc725ec2188ce4ea554d853eaf758e Mon Sep 17 00:00:00 2001 From: Andrej Stojkovikj Date: Tue, 19 Sep 2023 19:33:25 +0200 Subject: [PATCH 051/188] Fix in typo, should be length instead of magnitude --- Source/Engine/Core/Math/Vector3.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Core/Math/Vector3.cs b/Source/Engine/Core/Math/Vector3.cs index 0ad557a32..59d1f63db 100644 --- a/Source/Engine/Core/Math/Vector3.cs +++ b/Source/Engine/Core/Math/Vector3.cs @@ -1092,13 +1092,13 @@ namespace FlaxEngine Real maxChangeSpeed = maxSpeed * smoothTime; Real changeSq = maxChangeSpeed * maxChangeSpeed; - Real sqrMag = change_x * change_x + change_y * change_y + change_z * change_z; - if (sqrMag > changeSq) + Real sqrLen = change_x * change_x + change_y * change_y + change_z * change_z; + if (sqrLen > changeSq) { - var mag = (Real)Math.Sqrt(sqrMag); - change_x = change_x / mag * maxChangeSpeed; - change_y = change_y / mag * maxChangeSpeed; - change_z = change_z / mag * maxChangeSpeed; + var len = (Real)Math.Sqrt(sqrLen); + change_x = change_x / len * maxChangeSpeed; + change_y = change_y / len * maxChangeSpeed; + change_z = change_z / len * maxChangeSpeed; } target.X = current.X - change_x; From a56ce92867fc410dfe66cde64bea920ecc861d3c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 19 Sep 2023 23:07:10 +0200 Subject: [PATCH 052/188] Fix crash when importing model with materials and `Split Objects` enabled --- Source/Editor/GUI/ComboBox.cs | 2 +- .../Engine/Tools/ModelTool/ModelTool.Assimp.cpp | 8 ++++---- .../Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp | 8 ++++---- Source/Engine/Tools/ModelTool/ModelTool.cpp | 16 ++++++++++++++++ 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Source/Editor/GUI/ComboBox.cs b/Source/Editor/GUI/ComboBox.cs index da2106450..0417cc7e3 100644 --- a/Source/Editor/GUI/ComboBox.cs +++ b/Source/Editor/GUI/ComboBox.cs @@ -545,7 +545,7 @@ namespace FlaxEditor.GUI Render2D.DrawRectangle(clientRect.MakeExpanded(-2.0f), borderColor); // Check if has selected item - if (_selectedIndices.Count > 0) + if (_selectedIndices != null && _selectedIndices.Count > 0) { string text = _selectedIndices.Count == 1 ? _items[_selectedIndices[0]] : "Multiple Values"; diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index d0fba8f12..ab9b6b606 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -716,13 +716,13 @@ bool ModelTool::ImportDataAssimp(const char* path, ImportedModelData& data, Opti if (EnumHasAnyFlags(data.Types, ImportDataTypes::Geometry) && context->Scene->HasMeshes()) { const int meshCount = context->Scene->mNumMeshes; - if (options.SplitObjects && options.ObjectIndex == -1) + if (options.SplitObjects && options.ObjectIndex == -1 && meshCount > 1) { // Import the first object within this call options.SplitObjects = false; options.ObjectIndex = 0; - if (meshCount > 1 && options.OnSplitImport.IsBinded()) + if (options.OnSplitImport.IsBinded()) { // Split all animations into separate assets LOG(Info, "Splitting imported {0} meshes", meshCount); @@ -789,13 +789,13 @@ bool ModelTool::ImportDataAssimp(const char* path, ImportedModelData& data, Opti if (EnumHasAnyFlags(data.Types, ImportDataTypes::Animations) && context->Scene->HasAnimations()) { const int32 animCount = (int32)context->Scene->mNumAnimations; - if (options.SplitObjects && options.ObjectIndex == -1) + if (options.SplitObjects && options.ObjectIndex == -1 && animCount > 1) { // Import the first object within this call options.SplitObjects = false; options.ObjectIndex = 0; - if (animCount > 1 && options.OnSplitImport.IsBinded()) + if (options.OnSplitImport.IsBinded()) { // Split all animations into separate assets LOG(Info, "Splitting imported {0} animations", animCount); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index 1eb9e1f27..ab55df7f6 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -1245,13 +1245,13 @@ bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, Opt if (EnumHasAnyFlags(data.Types, ImportDataTypes::Geometry) && context->Scene->getMeshCount() > 0) { const int meshCount = context->Scene->getMeshCount(); - if (options.SplitObjects && options.ObjectIndex == -1) + if (options.SplitObjects && options.ObjectIndex == -1 && meshCount > 1) { // Import the first object within this call options.SplitObjects = false; options.ObjectIndex = 0; - if (meshCount > 1 && options.OnSplitImport.IsBinded()) + if (options.OnSplitImport.IsBinded()) { // Split all animations into separate assets LOG(Info, "Splitting imported {0} meshes", meshCount); @@ -1328,13 +1328,13 @@ bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, Opt if (EnumHasAnyFlags(data.Types, ImportDataTypes::Animations)) { const int animCount = context->Scene->getAnimationStackCount(); - if (options.SplitObjects && options.ObjectIndex == -1) + if (options.SplitObjects && options.ObjectIndex == -1 && animCount > 1) { // Import the first object within this call options.SplitObjects = false; options.ObjectIndex = 0; - if (animCount > 1 && options.OnSplitImport.IsBinded()) + if (options.OnSplitImport.IsBinded()) { // Split all animations into separate assets LOG(Info, "Splitting imported {0} animations", animCount); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 19f203667..99873c016 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -16,6 +16,7 @@ #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/Models/ModelData.h" #include "Engine/Content/Assets/Model.h" +#include "Engine/Content/Content.h" #include "Engine/Serialization/MemoryWriteStream.h" #if USE_EDITOR #include "Engine/Core/Types/DateTime.h" @@ -909,6 +910,10 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op { auto& texture = data.Textures[i]; + // When splitting imported meshes allow only the first mesh to import assets (mesh[0] is imported after all following ones so import assets during mesh[1]) + if (!options.SplitObjects && options.ObjectIndex != 1 && options.ObjectIndex != -1) + continue; + // Auto-import textures if (autoImportOutput.IsEmpty() || (data.Types & ImportDataTypes::Textures) == ImportDataTypes::None || texture.FilePath.IsEmpty()) continue; @@ -994,6 +999,17 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op materialOptions.Info.CullMode = CullMode::TwoSided; if (!Math::IsOne(material.Opacity.Value) || material.Opacity.TextureIndex != -1) materialOptions.Info.BlendMode = MaterialBlendMode::Transparent; + + // When splitting imported meshes allow only the first mesh to import assets (mesh[0] is imported after all following ones so import assets during mesh[1]) + if (!options.SplitObjects && options.ObjectIndex != 1 && options.ObjectIndex != -1) + { + // Find that asset create previously + AssetInfo info; + if (Content::GetAssetInfo(assetPath, info)) + material.AssetID = info.ID; + continue; + } + AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialTag, assetPath, material.AssetID, &materialOptions); #endif } From 4a1787dfbe310c776a6ccd6ae6554222b3797c68 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Wed, 20 Sep 2023 00:06:22 +0300 Subject: [PATCH 053/188] Generate project files at startup when project Cache was cleared --- Source/Editor/States/LoadingState.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/Editor/States/LoadingState.cs b/Source/Editor/States/LoadingState.cs index 746c8d92b..698dc192f 100644 --- a/Source/Editor/States/LoadingState.cs +++ b/Source/Editor/States/LoadingState.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.IO; using FlaxEngine; using FlaxEngine.Utilities; @@ -54,6 +55,13 @@ namespace FlaxEditor.States } else if (Editor.Options.Options.General.ForceScriptCompilationOnStartup && !skipCompile) { + // Generate project files when Cache is missing or was cleared previously + if (!Directory.Exists(Path.Combine(Editor.GameProject?.ProjectFolderPath, "Cache", "Intermediate")) || + !Directory.Exists(Path.Combine(Editor.GameProject?.ProjectFolderPath, "Cache", "Projects"))) + { + var customArgs = Editor.Instance.CodeEditing.SelectedEditor.GenerateProjectCustomArgs; + ScriptsBuilder.GenerateProject(customArgs); + } // Compile scripts before loading any scenes, then we load them and can open scenes ScriptsBuilder.Compile(); } From 7e81fdbd758fab7c089b1dcc47f3b7c81cd384cd Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 19 Sep 2023 23:45:30 +0200 Subject: [PATCH 054/188] Fix crash when D3D11 backend fails to create shader due to driver failure --- Source/Engine/Graphics/Shaders/GPUShader.cpp | 2 +- .../DirectX/DX11/GPUBufferDX11.cpp | 6 +-- .../DirectX/DX11/GPUDeviceDX11.cpp | 28 +++++++------- .../DirectX/DX11/GPUSamplerDX11.cpp | 2 +- .../DirectX/DX11/GPUShaderDX11.cpp | 21 ++++++---- .../DirectX/DX11/GPUSwapChainDX11.cpp | 24 ++++++------ .../DirectX/DX11/GPUTextureDX11.cpp | 38 +++++++++---------- .../DirectX/DX12/CommandAllocatorPoolDX12.cpp | 4 +- .../DirectX/DX12/CommandQueueDX12.cpp | 4 +- .../DirectX/DX12/DescriptorHeapDX12.cpp | 4 +- .../DirectX/DX12/GPUBufferDX12.cpp | 2 +- .../DirectX/DX12/GPUContextDX12.cpp | 2 +- .../DirectX/DX12/GPUDeviceDX12.cpp | 28 +++++++------- .../DirectX/DX12/GPUSwapChainDX12.cpp | 18 ++++----- .../DirectX/DX12/GPUTextureDX12.cpp | 4 +- .../DirectX/DX12/GPUTimerQueryDX12.cpp | 2 +- .../DirectX/DX12/QueryHeapDX12.cpp | 6 +-- .../DirectX/DX12/UploadBufferDX12.cpp | 4 +- .../GraphicsDevice/DirectX/RenderToolsDX.h | 8 ++-- Source/Engine/Renderer/HistogramPass.cpp | 2 +- 20 files changed, 108 insertions(+), 101 deletions(-) diff --git a/Source/Engine/Graphics/Shaders/GPUShader.cpp b/Source/Engine/Graphics/Shaders/GPUShader.cpp index 3639e01b1..e76ee9996 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.cpp +++ b/Source/Engine/Graphics/Shaders/GPUShader.cpp @@ -120,7 +120,7 @@ bool GPUShader::Create(MemoryReadStream& stream) GPUShaderProgram* shader = CreateGPUShaderProgram(type, initializer, cache, cacheSize, stream); if (shader == nullptr) { - LOG(Warning, "Failed to create shader program."); + LOG(Error, "Failed to create {} Shader program '{}'.", ::ToString(type), String(initializer.Name)); return true; } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUBufferDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUBufferDX11.cpp index b83ab6a08..352fd921a 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUBufferDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUBufferDX11.cpp @@ -105,7 +105,7 @@ bool GPUBufferDX11::OnInit() data.SysMemPitch = bufferDesc.ByteWidth; data.SysMemSlicePitch = 0; } - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateBuffer(&bufferDesc, _desc.InitData ? &data : nullptr, &_resource)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateBuffer(&bufferDesc, _desc.InitData ? &data : nullptr, &_resource)); // Set state DX_SET_DEBUG_NAME(_resource, GetName()); @@ -135,7 +135,7 @@ bool GPUBufferDX11::OnInit() srvDesc.Buffer.NumElements = numElements; } ID3D11ShaderResourceView* srv; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateShaderResourceView(_resource, &srvDesc, &srv)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateShaderResourceView(_resource, &srvDesc, &srv)); _view.SetSRV(srv); } if (useUAV) @@ -156,7 +156,7 @@ bool GPUBufferDX11::OnInit() else uavDesc.Format = RenderToolsDX::ToDxgiFormat(PixelFormatExtensions::FindUnorderedAccessFormat(_desc.Format)); ID3D11UnorderedAccessView* uav; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateUnorderedAccessView(_resource, &uavDesc, &uav)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateUnorderedAccessView(_resource, &uavDesc, &uav)); _view.SetUAV(uav); } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index 10e60aebc..c29c6254d 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -143,7 +143,7 @@ GPUDevice* GPUDeviceDX11::Create() if (tempAdapter && TryCreateDevice(tempAdapter, maxAllowedFeatureLevel, &adapter.MaxFeatureLevel)) { adapter.Index = index; - VALIDATE_DIRECTX_RESULT(tempAdapter->GetDesc(&adapter.Description)); + VALIDATE_DIRECTX_CALL(tempAdapter->GetDesc(&adapter.Description)); uint32 outputs = RenderToolsDX::CountAdapterOutputs(tempAdapter); LOG(Info, "Adapter {1}: '{0}', DirectX {2}", adapter.Description.Description, index, RenderToolsDX::GetFeatureLevelString(adapter.MaxFeatureLevel)); @@ -163,7 +163,7 @@ GPUDevice* GPUDeviceDX11::Create() if (tempAdapter && TryCreateDevice(tempAdapter, maxAllowedFeatureLevel, &adapter.MaxFeatureLevel)) { DXGI_ADAPTER_DESC desc; - VALIDATE_DIRECTX_RESULT(tempAdapter->GetDesc(&desc)); + VALIDATE_DIRECTX_CALL(tempAdapter->GetDesc(&desc)); for (int i = 0; i < adapters.Count(); i++) { if (adapters[i].Description.AdapterLuid.LowPart == desc.AdapterLuid.LowPart && @@ -274,7 +274,7 @@ ID3D11BlendState* GPUDeviceDX11::GetBlendState(const BlendingMode& blending) #endif // Create object - VALIDATE_DIRECTX_RESULT(_device->CreateBlendState(&desc, &blendState)); + VALIDATE_DIRECTX_CALL(_device->CreateBlendState(&desc, &blendState)); // Cache blend state BlendStates.Add(blending, blendState); @@ -333,7 +333,7 @@ bool GPUDeviceDX11::Init() // Create DirectX device D3D_FEATURE_LEVEL createdFeatureLevel = static_cast(0); auto targetFeatureLevel = GetD3DFeatureLevel(); - VALIDATE_DIRECTX_RESULT(D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, flags, &targetFeatureLevel, 1, D3D11_SDK_VERSION, &_device, &createdFeatureLevel, &_imContext)); + VALIDATE_DIRECTX_CALL(D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, flags, &targetFeatureLevel, 1, D3D11_SDK_VERSION, &_device, &createdFeatureLevel, &_imContext)); // Validate result ASSERT(_device); @@ -409,7 +409,7 @@ bool GPUDeviceDX11::Init() // Init debug layer #if GPU_ENABLE_DIAGNOSTICS ComPtr infoQueue; - VALIDATE_DIRECTX_RESULT(_device->QueryInterface(IID_PPV_ARGS(&infoQueue))); + VALIDATE_DIRECTX_CALL(_device->QueryInterface(IID_PPV_ARGS(&infoQueue))); if (infoQueue) { D3D11_INFO_QUEUE_FILTER filter; @@ -457,7 +457,7 @@ bool GPUDeviceDX11::Init() samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; result = _device->CreateSamplerState(&samplerDesc, &_samplerLinearClamp); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); // Point Clamp samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; @@ -467,7 +467,7 @@ bool GPUDeviceDX11::Init() samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; result = _device->CreateSamplerState(&samplerDesc, &_samplerPointClamp); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); // Linear Wrap samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; @@ -477,7 +477,7 @@ bool GPUDeviceDX11::Init() samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; result = _device->CreateSamplerState(&samplerDesc, &_samplerLinearWrap); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); // Point Wrap samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; @@ -487,7 +487,7 @@ bool GPUDeviceDX11::Init() samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; result = _device->CreateSamplerState(&samplerDesc, &_samplerPointWrap); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); // Shadow samplerDesc.Filter = D3D11_FILTER_COMPARISON_MIN_MAG_MIP_POINT; @@ -500,7 +500,7 @@ bool GPUDeviceDX11::Init() samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; result = _device->CreateSamplerState(&samplerDesc, &_samplerShadow); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); // Shadow PCF samplerDesc.Filter = D3D11_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR; @@ -514,7 +514,7 @@ bool GPUDeviceDX11::Init() samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; result = _device->CreateSamplerState(&samplerDesc, &_samplerShadowPCF); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); } // Rasterizer States @@ -534,7 +534,7 @@ bool GPUDeviceDX11::Init() rDesc.AntialiasedLineEnable = !!wireframe; \ rDesc.DepthClipEnable = !!depthClip; \ result = _device->CreateRasterizerState(&rDesc, &RasterizerStates[index]); \ - LOG_DIRECTX_RESULT_WITH_RETURN(result) + LOG_DIRECTX_RESULT_WITH_RETURN(result, true) CREATE_RASTERIZER_STATE(CullMode::Normal, D3D11_CULL_BACK, false, false); CREATE_RASTERIZER_STATE(CullMode::Inverted, D3D11_CULL_FRONT, false, false); CREATE_RASTERIZER_STATE(CullMode::TwoSided, D3D11_CULL_NONE, false, false); @@ -568,7 +568,7 @@ bool GPUDeviceDX11::Init() dsDesc.DepthFunc = (D3D11_COMPARISON_FUNC)depthFunc; \ index = (int32)depthFunc + (depthEnable ? 0 : 9) + (depthWrite ? 0 : 18); \ HRESULT result = _device->CreateDepthStencilState(&dsDesc, &DepthStencilStates[index]); \ - LOG_DIRECTX_RESULT_WITH_RETURN(result); } + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); } CREATE_DEPTH_STENCIL_STATE(false, false); CREATE_DEPTH_STENCIL_STATE(false, true); CREATE_DEPTH_STENCIL_STATE(true, true); @@ -666,7 +666,7 @@ void GPUDeviceDX11::DrawEnd() #if GPU_ENABLE_DIAGNOSTICS // Flush debug messages queue ComPtr infoQueue; - VALIDATE_DIRECTX_RESULT(_device->QueryInterface(IID_PPV_ARGS(&infoQueue))); + VALIDATE_DIRECTX_CALL(_device->QueryInterface(IID_PPV_ARGS(&infoQueue))); if (infoQueue) { Array data; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSamplerDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSamplerDX11.cpp index 428390637..fadb20ead 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSamplerDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSamplerDX11.cpp @@ -106,7 +106,7 @@ bool GPUSamplerDX11::OnInit() samplerDesc.MinLOD = _desc.MinMipLevel; samplerDesc.MaxLOD = _desc.MaxMipLevel; HRESULT result = _device->GetDevice()->CreateSamplerState(&samplerDesc, &SamplerState); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); ASSERT(SamplerState != nullptr); _memoryUsage = sizeof(D3D11_SAMPLER_DESC); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp index 0e4b7ac0c..fd14f0aeb 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp @@ -10,6 +10,7 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize, MemoryReadStream& stream) { GPUShaderProgram* shader = nullptr; + HRESULT result; switch (type) { case ShaderStage::Vertex: @@ -90,12 +91,13 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const if (inputLayoutSize > 0) { // Create input layout - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateInputLayout(inputLayoutDesc, inputLayoutSize, cacheBytes, cacheSize, &inputLayout)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateInputLayout(inputLayoutDesc, inputLayoutSize, cacheBytes, cacheSize, &inputLayout)); } // Create shader ID3D11VertexShader* buffer = nullptr; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateVertexShader(cacheBytes, cacheSize, nullptr, &buffer)); + result = _device->GetDevice()->CreateVertexShader(cacheBytes, cacheSize, nullptr, &buffer); + LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object shader = New(initializer, buffer, inputLayout, inputLayoutSize); @@ -109,7 +111,8 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const // Create shader ID3D11HullShader* buffer = nullptr; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateHullShader(cacheBytes, cacheSize, nullptr, &buffer)); + result = _device->GetDevice()->CreateHullShader(cacheBytes, cacheSize, nullptr, &buffer); + LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object shader = New(initializer, buffer, controlPointsCount); @@ -119,7 +122,8 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const { // Create shader ID3D11DomainShader* buffer = nullptr; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateDomainShader(cacheBytes, cacheSize, nullptr, &buffer)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateDomainShader(cacheBytes, cacheSize, nullptr, &buffer)); + LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object shader = New(initializer, buffer); @@ -129,7 +133,8 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const { // Create shader ID3D11GeometryShader* buffer = nullptr; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateGeometryShader(cacheBytes, cacheSize, nullptr, &buffer)); + result = _device->GetDevice()->CreateGeometryShader(cacheBytes, cacheSize, nullptr, &buffer); + LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object shader = New(initializer, buffer); @@ -139,7 +144,8 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const { // Create shader ID3D11PixelShader* buffer = nullptr; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreatePixelShader(cacheBytes, cacheSize, nullptr, &buffer)); + result = _device->GetDevice()->CreatePixelShader(cacheBytes, cacheSize, nullptr, &buffer); + LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object shader = New(initializer, buffer); @@ -149,7 +155,8 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const { // Create shader ID3D11ComputeShader* buffer = nullptr; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateComputeShader(cacheBytes, cacheSize, nullptr, &buffer)); + result = _device->GetDevice()->CreateComputeShader(cacheBytes, cacheSize, nullptr, &buffer); + LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object shader = New(initializer, buffer); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp index 7b9564f54..d5c0e5c55 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp @@ -28,13 +28,13 @@ GPUSwapChainDX11::GPUSwapChainDX11(GPUDeviceDX11* device, Window* window) void GPUSwapChainDX11::getBackBuffer() { - VALIDATE_DIRECTX_RESULT(_swapChain->GetBuffer(0, __uuidof(_backBuffer), reinterpret_cast(&_backBuffer))); + VALIDATE_DIRECTX_CALL(_swapChain->GetBuffer(0, __uuidof(_backBuffer), reinterpret_cast(&_backBuffer))); ID3D11RenderTargetView* rtv; ID3D11ShaderResourceView* srv; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateRenderTargetView(_backBuffer, nullptr, &rtv)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateRenderTargetView(_backBuffer, nullptr, &rtv)); #if GPU_USE_WINDOW_SRV - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateShaderResourceView(_backBuffer, nullptr, &srv)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateShaderResourceView(_backBuffer, nullptr, &srv)); #else srv = nullptr; #endif @@ -55,7 +55,7 @@ void GPUSwapChainDX11::OnReleaseGPU() // Disable fullscreen mode if (_swapChain) { - VALIDATE_DIRECTX_RESULT(_swapChain->SetFullscreenState(false, nullptr)); + VALIDATE_DIRECTX_CALL(_swapChain->SetFullscreenState(false, nullptr)); } #endif @@ -78,7 +78,7 @@ bool GPUSwapChainDX11::IsFullscreen() // Get state BOOL state; - VALIDATE_DIRECTX_RESULT(_swapChain->GetFullscreenState(&state, nullptr)); + VALIDATE_DIRECTX_CALL(_swapChain->GetFullscreenState(&state, nullptr)); return state == TRUE; } @@ -229,21 +229,21 @@ bool GPUSwapChainDX11::Resize(int32 width, int32 height) // Create swap chain #if PLATFORM_WINDOWS auto dxgi = _device->GetDXGIFactory(); - VALIDATE_DIRECTX_RESULT(dxgi->CreateSwapChain(_device->GetDevice(), &swapChainDesc, &_swapChain)); + VALIDATE_DIRECTX_CALL(dxgi->CreateSwapChain(_device->GetDevice(), &swapChainDesc, &_swapChain)); ASSERT(_swapChain); // Disable DXGI changes to the window - VALIDATE_DIRECTX_RESULT(dxgi->MakeWindowAssociation(_windowHandle, DXGI_MWA_NO_ALT_ENTER)); + VALIDATE_DIRECTX_CALL(dxgi->MakeWindowAssociation(_windowHandle, DXGI_MWA_NO_ALT_ENTER)); #else auto dxgiFactory = (IDXGIFactory2*)_device->GetDXGIFactory(); - VALIDATE_DIRECTX_RESULT(dxgiFactory->CreateSwapChainForCoreWindow(_device->GetDevice(), static_cast(_windowHandle), &swapChainDesc, nullptr, &_swapChain)); + VALIDATE_DIRECTX_CALL(dxgiFactory->CreateSwapChainForCoreWindow(_device->GetDevice(), static_cast(_windowHandle), &swapChainDesc, nullptr, &_swapChain)); ASSERT(_swapChain); // Ensure that DXGI does not queue more than one frame at a time. This both reduces latency and // ensures that the application will only render after each VSync, minimizing power consumption. ComPtr dxgiDevice; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->QueryInterface(IID_PPV_ARGS(&dxgiDevice))); - VALIDATE_DIRECTX_RESULT(dxgiDevice->SetMaximumFrameLatency(1)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->QueryInterface(IID_PPV_ARGS(&dxgiDevice))); + VALIDATE_DIRECTX_CALL(dxgiDevice->SetMaximumFrameLatency(1)); #endif } else @@ -252,10 +252,10 @@ bool GPUSwapChainDX11::Resize(int32 width, int32 height) #if PLATFORM_WINDOWS _swapChain->GetDesc(&swapChainDesc); - VALIDATE_DIRECTX_RESULT(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, width, height, swapChainDesc.BufferDesc.Format, swapChainDesc.Flags)); + VALIDATE_DIRECTX_CALL(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, width, height, swapChainDesc.BufferDesc.Format, swapChainDesc.Flags)); #else _swapChain->GetDesc1(&swapChainDesc); - VALIDATE_DIRECTX_RESULT(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, width, height, swapChainDesc.Format, swapChainDesc.Flags)); + VALIDATE_DIRECTX_CALL(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, width, height, swapChainDesc.Format, swapChainDesc.Flags)); #endif } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.cpp index 73be91108..3d1979954 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.cpp @@ -87,7 +87,7 @@ bool GPUTextureDX11::OnInit() result = device->CreateTexture2D(&textureDesc, nullptr, &texture); _resource = texture; } - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); ASSERT(_resource != nullptr); DX_SET_DEBUG_NAME(_resource, GetName()); @@ -135,7 +135,7 @@ void GPUTextureDX11::OnResidentMipsChanged() } ID3D11ShaderResourceView* srView = nullptr; if (mipLevels != 0) - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateShaderResourceView(_resource, &srDesc, &srView)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateShaderResourceView(_resource, &srDesc, &srView)); GPUTextureViewDX11& view = IsVolume() ? _handleVolume : _handlesPerSlice[0]; if (view.GetParent() == nullptr) view.Init(this, nullptr, srView, nullptr, nullptr, Format(), MultiSampleLevel()); @@ -201,7 +201,7 @@ void GPUTextureDX11::initHandles() srDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D; srDesc.Texture3D.MostDetailedMip = 0; srDesc.Texture3D.MipLevels = mipLevels; - VALIDATE_DIRECTX_RESULT(device->CreateShaderResourceView(_resource, &srDesc, &srView)); + VALIDATE_DIRECTX_CALL(device->CreateShaderResourceView(_resource, &srDesc, &srView)); } if (useRTV) { @@ -209,7 +209,7 @@ void GPUTextureDX11::initHandles() rtDesc.Texture3D.MipSlice = 0; rtDesc.Texture3D.FirstWSlice = 0; rtDesc.Texture3D.WSize = Depth(); - VALIDATE_DIRECTX_RESULT(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); + VALIDATE_DIRECTX_CALL(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); } if (useUAV) { @@ -217,7 +217,7 @@ void GPUTextureDX11::initHandles() uaDesc.Texture3D.MipSlice = 0; uaDesc.Texture3D.WSize = Depth(); uaDesc.Texture3D.FirstWSlice = 0; - VALIDATE_DIRECTX_RESULT(device->CreateUnorderedAccessView(_resource, &uaDesc, &uaView)); + VALIDATE_DIRECTX_CALL(device->CreateUnorderedAccessView(_resource, &uaDesc, &uaView)); } _handleVolume.Init(this, rtView, srView, nullptr, uaView, format, msaa); @@ -232,7 +232,7 @@ void GPUTextureDX11::initHandles() for (int32 sliceIndex = 0; sliceIndex < Depth(); sliceIndex++) { rtDesc.Texture3D.FirstWSlice = sliceIndex; - VALIDATE_DIRECTX_RESULT(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); + VALIDATE_DIRECTX_CALL(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); _handlesPerSlice[sliceIndex].Init(this, rtView, nullptr, nullptr, nullptr, format, msaa); } } @@ -263,7 +263,7 @@ void GPUTextureDX11::initHandles() dsDesc.Texture2DArray.FirstArraySlice = arrayIndex; dsDesc.Texture2DArray.MipSlice = 0; } - VALIDATE_DIRECTX_RESULT(device->CreateDepthStencilView(_resource, &dsDesc, &dsView)); + VALIDATE_DIRECTX_CALL(device->CreateDepthStencilView(_resource, &dsDesc, &dsView)); } if (useRTV) { @@ -281,7 +281,7 @@ void GPUTextureDX11::initHandles() rtDesc.Texture2DArray.FirstArraySlice = arrayIndex; rtDesc.Texture2DArray.MipSlice = 0; } - VALIDATE_DIRECTX_RESULT(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); + VALIDATE_DIRECTX_CALL(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); } if (useSRV) { @@ -305,7 +305,7 @@ void GPUTextureDX11::initHandles() srDesc.Texture2DArray.MipLevels = mipLevels; srDesc.Texture2DArray.MostDetailedMip = 0; } - VALIDATE_DIRECTX_RESULT(device->CreateShaderResourceView(_resource, &srDesc, &srView)); + VALIDATE_DIRECTX_CALL(device->CreateShaderResourceView(_resource, &srDesc, &srView)); } } @@ -322,7 +322,7 @@ void GPUTextureDX11::initHandles() dsDesc.Texture2DArray.ArraySize = arraySize; dsDesc.Texture2DArray.FirstArraySlice = 0; dsDesc.Texture2DArray.MipSlice = 0; - VALIDATE_DIRECTX_RESULT(device->CreateDepthStencilView(_resource, &dsDesc, &dsView)); + VALIDATE_DIRECTX_CALL(device->CreateDepthStencilView(_resource, &dsDesc, &dsView)); } if (useRTV) { @@ -330,7 +330,7 @@ void GPUTextureDX11::initHandles() rtDesc.Texture2DArray.ArraySize = arraySize; rtDesc.Texture2DArray.FirstArraySlice = 0; rtDesc.Texture2DArray.MipSlice = 0; - VALIDATE_DIRECTX_RESULT(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); + VALIDATE_DIRECTX_CALL(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); } if (useSRV) { @@ -348,7 +348,7 @@ void GPUTextureDX11::initHandles() srDesc.Texture2DArray.MipLevels = mipLevels; srDesc.Texture2DArray.MostDetailedMip = 0; } - VALIDATE_DIRECTX_RESULT(device->CreateShaderResourceView(_resource, &srDesc, &srView)); + VALIDATE_DIRECTX_CALL(device->CreateShaderResourceView(_resource, &srDesc, &srView)); } if (useUAV) { @@ -356,7 +356,7 @@ void GPUTextureDX11::initHandles() uaDesc.Texture2DArray.MipSlice = 0; uaDesc.Texture2DArray.ArraySize = arraySize; uaDesc.Texture2DArray.FirstArraySlice = 0; - VALIDATE_DIRECTX_RESULT(device->CreateUnorderedAccessView(_resource, &uaDesc, &uaView)); + VALIDATE_DIRECTX_CALL(device->CreateUnorderedAccessView(_resource, &uaDesc, &uaView)); } _handleArray.Init(this, rtView, srView, dsView, uaView, format, msaa); } @@ -386,7 +386,7 @@ void GPUTextureDX11::initHandles() dsDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; dsDesc.Texture2D.MipSlice = 0; } - VALIDATE_DIRECTX_RESULT(device->CreateDepthStencilView(_resource, &dsDesc, &dsView)); + VALIDATE_DIRECTX_CALL(device->CreateDepthStencilView(_resource, &dsDesc, &dsView)); } if (useRTV) { @@ -406,7 +406,7 @@ void GPUTextureDX11::initHandles() rtDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; rtDesc.Texture2D.MipSlice = 0; } - VALIDATE_DIRECTX_RESULT(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); + VALIDATE_DIRECTX_CALL(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); } if (useSRV) { @@ -426,13 +426,13 @@ void GPUTextureDX11::initHandles() srDesc.Texture2D.MostDetailedMip = 0; srDesc.Texture2D.MipLevels = mipLevels; } - VALIDATE_DIRECTX_RESULT(device->CreateShaderResourceView(_resource, &srDesc, &srView)); + VALIDATE_DIRECTX_CALL(device->CreateShaderResourceView(_resource, &srDesc, &srView)); } if (useUAV) { uaDesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D; uaDesc.Texture2D.MipSlice = 0; - VALIDATE_DIRECTX_RESULT(device->CreateUnorderedAccessView(_resource, &uaDesc, &uaView)); + VALIDATE_DIRECTX_CALL(device->CreateUnorderedAccessView(_resource, &uaDesc, &uaView)); } _handlesPerSlice[0].Init(this, rtView, srView, dsView, uaView, format, msaa); } @@ -521,7 +521,7 @@ void GPUTextureDX11::initHandles() dsDesc.Flags = D3D11_DSV_READ_ONLY_DEPTH; if (PixelFormatExtensions::HasStencil(format)) dsDesc.Flags |= D3D11_DSV_READ_ONLY_STENCIL; - VALIDATE_DIRECTX_RESULT(device->CreateDepthStencilView(_resource, &dsDesc, &dsView)); + VALIDATE_DIRECTX_CALL(device->CreateDepthStencilView(_resource, &dsDesc, &dsView)); } ASSERT(!useRTV); rtView = nullptr; @@ -543,7 +543,7 @@ void GPUTextureDX11::initHandles() srDesc.Texture2D.MostDetailedMip = 0; srDesc.Texture2D.MipLevels = mipLevels; } - VALIDATE_DIRECTX_RESULT(device->CreateShaderResourceView(_resource, &srDesc, &srView)); + VALIDATE_DIRECTX_CALL(device->CreateShaderResourceView(_resource, &srDesc, &srView)); } _handleReadOnlyDepth.Init(this, rtView, srView, dsView, nullptr, format, msaa); } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/CommandAllocatorPoolDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/CommandAllocatorPoolDX12.cpp index f0ad790af..9bb185030 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/CommandAllocatorPoolDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/CommandAllocatorPoolDX12.cpp @@ -31,7 +31,7 @@ ID3D12CommandAllocator* CommandAllocatorPoolDX12::RequestAllocator(uint64 comple if (firstPair.First <= completedFenceValue) { allocator = firstPair.Second; - VALIDATE_DIRECTX_RESULT(allocator->Reset()); + VALIDATE_DIRECTX_CALL(allocator->Reset()); _ready.RemoveAtKeepOrder(0); } } @@ -39,7 +39,7 @@ ID3D12CommandAllocator* CommandAllocatorPoolDX12::RequestAllocator(uint64 comple // If no allocators were ready to be reused, create a new one if (allocator == nullptr) { - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateCommandAllocator(_type, IID_PPV_ARGS(&allocator))); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateCommandAllocator(_type, IID_PPV_ARGS(&allocator))); #if GPU_ENABLE_RESOURCE_NAMING Char name[32]; swprintf(name, 32, L"CommandAllocator %u", _pool.Count()); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp index 8f78b749e..3235f4015 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp @@ -111,7 +111,7 @@ bool CommandQueueDX12::Init() desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; desc.NodeMask = 0; HRESULT result = _device->GetDevice()->CreateCommandQueue(&desc, IID_PPV_ARGS(&_commandQueue)); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); #if GPU_ENABLE_RESOURCE_NAMING _commandQueue->SetName(TEXT("CommandQueueDX12::CommandQueue")); #endif @@ -148,7 +148,7 @@ void CommandQueueDX12::WaitForGPU() uint64 CommandQueueDX12::ExecuteCommandList(ID3D12CommandList* list) { - VALIDATE_DIRECTX_RESULT((static_cast(list))->Close()); + VALIDATE_DIRECTX_CALL((static_cast(list))->Close()); _commandQueue->ExecuteCommandLists(1, &list); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.cpp index 238318b2c..2ee2ea023 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.cpp @@ -73,7 +73,7 @@ bool DescriptorHeapWithSlotsDX12::Create(D3D12_DESCRIPTOR_HEAP_TYPE type, uint32 // Create heap const HRESULT result = _device->GetDevice()->CreateDescriptorHeap(&desc, __uuidof(ID3D12DescriptorHeap), reinterpret_cast(&_heap)); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); // Setup _type = type; @@ -196,7 +196,7 @@ bool DescriptorHeapRingBufferDX12::Init() desc.Flags = _shaderVisible ? D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE : D3D12_DESCRIPTOR_HEAP_FLAG_NONE; desc.NodeMask = 0; const HRESULT result = _device->GetDevice()->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&_heap)); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); // Setup _firstFree = 0; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp index 5d2d97f0b..8f948d246 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp @@ -136,7 +136,7 @@ bool GPUBufferDX12::OnInit() // Create resource ID3D12Resource* resource; D3D12_RESOURCE_STATES initialState = D3D12_RESOURCE_STATE_COPY_DEST; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, initialState, nullptr, IID_PPV_ARGS(&resource))); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, initialState, nullptr, IID_PPV_ARGS(&resource))); // Set state initResource(resource, initialState, 1); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp index 54f1a4290..e186054c2 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp @@ -83,7 +83,7 @@ GPUContextDX12::GPUContextDX12(GPUDeviceDX12* device, D3D12_COMMAND_LIST_TYPE ty FrameFenceValues[0] = 0; FrameFenceValues[1] = 0; _currentAllocator = _device->GetCommandQueue()->RequestAllocator(); - VALIDATE_DIRECTX_RESULT(device->GetDevice()->CreateCommandList(0, type, _currentAllocator, nullptr, IID_PPV_ARGS(&_commandList))); + VALIDATE_DIRECTX_CALL(device->GetDevice()->CreateCommandList(0, type, _currentAllocator, nullptr, IID_PPV_ARGS(&_commandList))); #if GPU_ENABLE_RESOURCE_NAMING _commandList->SetName(TEXT("GPUContextDX12::CommandList")); #endif diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp index 047b69bb5..e2266f551 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp @@ -77,7 +77,7 @@ GPUDevice* GPUDeviceDX12::Create() #endif #ifdef __ID3D12DeviceRemovedExtendedDataSettings_FWD_DEFINED__ ComPtr dredSettings; - VALIDATE_DIRECTX_RESULT(D3D12GetDebugInterface(IID_PPV_ARGS(&dredSettings))); + VALIDATE_DIRECTX_CALL(D3D12GetDebugInterface(IID_PPV_ARGS(&dredSettings))); if (dredSettings) { // Turn on AutoBreadcrumbs and Page Fault reporting @@ -116,7 +116,7 @@ GPUDevice* GPUDeviceDX12::Create() { adapter.Index = index; adapter.MaxFeatureLevel = D3D_FEATURE_LEVEL_12_0; - VALIDATE_DIRECTX_RESULT(tempAdapter->GetDesc(&adapter.Description)); + VALIDATE_DIRECTX_CALL(tempAdapter->GetDesc(&adapter.Description)); uint32 outputs = RenderToolsDX::CountAdapterOutputs(tempAdapter); // Send that info to the log @@ -137,7 +137,7 @@ GPUDevice* GPUDeviceDX12::Create() if (tempAdapter && CheckDX12Support(tempAdapter)) { DXGI_ADAPTER_DESC desc; - VALIDATE_DIRECTX_RESULT(tempAdapter->GetDesc(&desc)); + VALIDATE_DIRECTX_CALL(tempAdapter->GetDesc(&desc)); for (int i = 0; i < adapters.Count(); i++) { if (adapters[i].Description.AdapterLuid.LowPart == desc.AdapterLuid.LowPart && @@ -254,7 +254,7 @@ bool GPUDeviceDX12::Init() #if PLATFORM_XBOX_SCARLETT params.DisableDXR = TRUE; #endif - VALIDATE_DIRECTX_RESULT(D3D12XboxCreateDevice(nullptr, ¶ms, IID_GRAPHICS_PPV_ARGS(&_device))); + VALIDATE_DIRECTX_CALL(D3D12XboxCreateDevice(nullptr, ¶ms, IID_GRAPHICS_PPV_ARGS(&_device))); // Setup adapter D3D12XBOX_GPU_HARDWARE_CONFIGURATION hwConfig = {}; @@ -319,12 +319,12 @@ bool GPUDeviceDX12::Init() } // Create DirectX device - VALIDATE_DIRECTX_RESULT(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&_device))); + VALIDATE_DIRECTX_CALL(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&_device))); // Debug Layer #if GPU_ENABLE_DIAGNOSTICS ComPtr infoQueue; - VALIDATE_DIRECTX_RESULT(_device->QueryInterface(IID_PPV_ARGS(&infoQueue))); + VALIDATE_DIRECTX_CALL(_device->QueryInterface(IID_PPV_ARGS(&infoQueue))); if (infoQueue) { D3D12_INFO_QUEUE_FILTER filter; @@ -363,7 +363,7 @@ bool GPUDeviceDX12::Init() // Spawn some info about the hardware D3D12_FEATURE_DATA_D3D12_OPTIONS options; - VALIDATE_DIRECTX_RESULT(_device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &options, sizeof(options))); + VALIDATE_DIRECTX_CALL(_device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &options, sizeof(options))); LOG(Info, "Tiled Resources Tier: {0}", (int32)options.TiledResourcesTier); LOG(Info, "Resource Binding Tier: {0}", (int32)options.ResourceBindingTier); LOG(Info, "Conservative Rasterization Tier: {0}", (int32)options.ConservativeRasterizationTier); @@ -662,10 +662,10 @@ bool GPUDeviceDX12::Init() // Serialize ComPtr signature; ComPtr error; - VALIDATE_DIRECTX_RESULT(D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error)); + VALIDATE_DIRECTX_CALL(D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error)); // Create - VALIDATE_DIRECTX_RESULT(_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&_rootSignature))); + VALIDATE_DIRECTX_CALL(_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&_rootSignature))); } // Upload buffer @@ -896,14 +896,14 @@ void GPUDeviceDX12::OnResumed() void GPUDeviceDX12::updateFrameEvents() { ComPtr dxgiDevice; - VALIDATE_DIRECTX_RESULT(_device->QueryInterface(IID_GRAPHICS_PPV_ARGS(&dxgiDevice))); + VALIDATE_DIRECTX_CALL(_device->QueryInterface(IID_GRAPHICS_PPV_ARGS(&dxgiDevice))); ComPtr dxgiAdapter; - VALIDATE_DIRECTX_RESULT(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf())); + VALIDATE_DIRECTX_CALL(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf())); dxgiAdapter->GetDesc(&_adapter->Description); ComPtr dxgiOutput; - VALIDATE_DIRECTX_RESULT(dxgiAdapter->EnumOutputs(0, dxgiOutput.GetAddressOf())); - VALIDATE_DIRECTX_RESULT(_device->SetFrameIntervalX(dxgiOutput.Get(), D3D12XBOX_FRAME_INTERVAL_60_HZ, DX12_BACK_BUFFER_COUNT - 1u, D3D12XBOX_FRAME_INTERVAL_FLAG_NONE)); - VALIDATE_DIRECTX_RESULT(_device->ScheduleFrameEventX(D3D12XBOX_FRAME_EVENT_ORIGIN, 0U, nullptr, D3D12XBOX_SCHEDULE_FRAME_EVENT_FLAG_NONE)); + VALIDATE_DIRECTX_CALL(dxgiAdapter->EnumOutputs(0, dxgiOutput.GetAddressOf())); + VALIDATE_DIRECTX_CALL(_device->SetFrameIntervalX(dxgiOutput.Get(), D3D12XBOX_FRAME_INTERVAL_60_HZ, DX12_BACK_BUFFER_COUNT - 1u, D3D12XBOX_FRAME_INTERVAL_FLAG_NONE)); + VALIDATE_DIRECTX_CALL(_device->ScheduleFrameEventX(D3D12XBOX_FRAME_EVENT_ORIGIN, 0U, nullptr, D3D12XBOX_SCHEDULE_FRAME_EVENT_FLAG_NONE)); } #endif diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp index 85d79582f..8a9ca1c64 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp @@ -66,7 +66,7 @@ void GPUSwapChainDX12::OnReleaseGPU() // Disable fullscreen mode if (_swapChain) { - VALIDATE_DIRECTX_RESULT(_swapChain->SetFullscreenState(false, nullptr)); + VALIDATE_DIRECTX_CALL(_swapChain->SetFullscreenState(false, nullptr)); } #endif @@ -100,7 +100,7 @@ bool GPUSwapChainDX12::IsFullscreen() // Get state BOOL state; - VALIDATE_DIRECTX_RESULT(_swapChain->GetFullscreenState(&state, nullptr)); + VALIDATE_DIRECTX_CALL(_swapChain->GetFullscreenState(&state, nullptr)); return state == TRUE; #endif } @@ -221,7 +221,7 @@ bool GPUSwapChainDX12::Resize(int32 width, int32 height) // Create swap chain (it needs the queue so that it can force a flush on it) IDXGISwapChain1* swapChain; auto dxgiFactory = _device->GetDXGIFactory(); - VALIDATE_DIRECTX_RESULT(dxgiFactory->CreateSwapChainForHwnd(_device->GetCommandQueueDX12(), _windowHandle, &swapChainDesc, &fullscreenDesc, nullptr, &swapChain)); + VALIDATE_DIRECTX_CALL(dxgiFactory->CreateSwapChainForHwnd(_device->GetCommandQueueDX12(), _windowHandle, &swapChainDesc, &fullscreenDesc, nullptr, &swapChain)); _swapChain = static_cast(swapChain); ASSERT(_swapChain); DX_SET_DEBUG_NAME_EX(_swapChain, TEXT("RenderOutput"), TEXT("SwapChain"), TEXT("")); @@ -229,7 +229,7 @@ bool GPUSwapChainDX12::Resize(int32 width, int32 height) _backBuffers.Resize(swapChainDesc.BufferCount); // Disable DXGI changes to the window - VALIDATE_DIRECTX_RESULT(dxgiFactory->MakeWindowAssociation(_windowHandle, DXGI_MWA_NO_ALT_ENTER)); + VALIDATE_DIRECTX_CALL(dxgiFactory->MakeWindowAssociation(_windowHandle, DXGI_MWA_NO_ALT_ENTER)); } else { @@ -237,7 +237,7 @@ bool GPUSwapChainDX12::Resize(int32 width, int32 height) _swapChain->GetDesc1(&swapChainDesc); - VALIDATE_DIRECTX_RESULT(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, width, height, swapChainDesc.Format, swapChainDesc.Flags)); + VALIDATE_DIRECTX_CALL(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, width, height, swapChainDesc.Format, swapChainDesc.Flags)); } _currentFrameIndex = _swapChain->GetCurrentBackBufferIndex(); @@ -316,7 +316,7 @@ void GPUSwapChainDX12::getBackBuffer() swapChainBufferDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; D3D12_CLEAR_VALUE swapChainOptimizedClearValue = {}; swapChainOptimizedClearValue.Format = swapChainBufferDesc.Format; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateCommittedResource( + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateCommittedResource( &swapChainHeapProperties, D3D12_HEAP_FLAG_ALLOW_DISPLAY, &swapChainBufferDesc, @@ -324,7 +324,7 @@ void GPUSwapChainDX12::getBackBuffer() &swapChainOptimizedClearValue, IID_GRAPHICS_PPV_ARGS(&backbuffer))); #else - VALIDATE_DIRECTX_RESULT(_swapChain->GetBuffer(i, IID_PPV_ARGS(&backbuffer))); + VALIDATE_DIRECTX_CALL(_swapChain->GetBuffer(i, IID_PPV_ARGS(&backbuffer))); #endif DX_SET_DEBUG_NAME_EX(backbuffer, TEXT("RenderOutput"), TEXT("BackBuffer"), i); _backBuffers[i].Setup(this, backbuffer); @@ -337,7 +337,7 @@ void GPUSwapChainDX12::Begin(RenderTask* task) { // Wait until frame start is signaled _framePipelineToken = D3D12XBOX_FRAME_PIPELINE_TOKEN_NULL; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->WaitFrameEventX(D3D12XBOX_FRAME_EVENT_ORIGIN, INFINITE, nullptr, D3D12XBOX_WAIT_FRAME_EVENT_FLAG_NONE, &_framePipelineToken)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->WaitFrameEventX(D3D12XBOX_FRAME_EVENT_ORIGIN, INFINITE, nullptr, D3D12XBOX_WAIT_FRAME_EVENT_FLAG_NONE, &_framePipelineToken)); GPUSwapChain::Begin(task); } @@ -366,7 +366,7 @@ void GPUSwapChainDX12::Present(bool vsync) planeParameters.Token = _framePipelineToken; planeParameters.ResourceCount = 1; planeParameters.ppResources = &backBuffer; - VALIDATE_DIRECTX_RESULT(_device->GetCommandQueueDX12()->PresentX(1, &planeParameters, nullptr)); + VALIDATE_DIRECTX_CALL(_device->GetCommandQueueDX12()->PresentX(1, &planeParameters, nullptr)); // Base GPUSwapChain::Present(vsync); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp index dd7402097..b89d65275 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp @@ -113,7 +113,7 @@ bool GPUTextureDX12::OnInit() resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; auto result = device->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&resource)); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); initResource(resource, D3D12_RESOURCE_STATE_COPY_DEST, 1); DX_SET_DEBUG_NAME(_resource, GetName()); _memoryUsage = totalSize; @@ -184,7 +184,7 @@ bool GPUTextureDX12::OnInit() // Create texture auto result = device->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, initialState, clearValuePtr, IID_PPV_ARGS(&resource)); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); // Set state bool isRead = useSRV || useUAV; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTimerQueryDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTimerQueryDX12.cpp index 6df638d23..7c814baed 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTimerQueryDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTimerQueryDX12.cpp @@ -37,7 +37,7 @@ void GPUTimerQueryDX12::End() heap.EndQuery(context, _end); const auto queue = _device->GetCommandQueue()->GetCommandQueue(); - VALIDATE_DIRECTX_RESULT(queue->GetTimestampFrequency(&_gpuFrequency)); + VALIDATE_DIRECTX_CALL(queue->GetTimestampFrequency(&_gpuFrequency)); _endCalled = true; } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/QueryHeapDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/QueryHeapDX12.cpp index dc6939cff..ea6af0a2d 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/QueryHeapDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/QueryHeapDX12.cpp @@ -41,7 +41,7 @@ bool QueryHeapDX12::Init() heapDesc.Count = _queryHeapCount; heapDesc.NodeMask = 0; HRESULT result = _device->GetDevice()->CreateQueryHeap(&heapDesc, IID_PPV_ARGS(&_queryHeap)); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); DX_SET_DEBUG_NAME(_queryHeap, "Query Heap"); // Create the result buffer @@ -64,7 +64,7 @@ bool QueryHeapDX12::Init() resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; result = _device->GetDevice()->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&_resultBuffer)); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); DX_SET_DEBUG_NAME(_resultBuffer, "Query Heap Result Buffer"); // Start out with an open query batch @@ -181,7 +181,7 @@ void* QueryHeapDX12::ResolveQuery(ElementHandle& handle) range.Begin = batch.Start * _resultSize; range.End = range.Begin + batch.Count * _resultSize; void* mapped = nullptr; - VALIDATE_DIRECTX_RESULT(_resultBuffer->Map(0, &range, &mapped)); + VALIDATE_DIRECTX_CALL(_resultBuffer->Map(0, &range, &mapped)); // Copy the results data Platform::MemoryCopy(_resultData.Get() + range.Begin, (byte*)mapped + range.Begin, batch.Count * _resultSize); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp index 27aa4c697..11f1b7ffe 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp @@ -229,7 +229,7 @@ UploadBufferPageDX12::UploadBufferPageDX12(GPUDeviceDX12* device, uint64 size) resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; ID3D12Resource* resource; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&resource))); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&resource))); // Set state initResource(resource, D3D12_RESOURCE_STATE_GENERIC_READ, 1); @@ -238,7 +238,7 @@ UploadBufferPageDX12::UploadBufferPageDX12(GPUDeviceDX12* device, uint64 size) GPUAddress = _resource->GetGPUVirtualAddress(); // Map buffer - VALIDATE_DIRECTX_RESULT(_resource->Map(0, nullptr, &CPUAddress)); + VALIDATE_DIRECTX_CALL(_resource->Map(0, nullptr, &CPUAddress)); } void UploadBufferPageDX12::OnReleaseGPU() diff --git a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h index 61d32cff0..ef783b218 100644 --- a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h +++ b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h @@ -272,15 +272,15 @@ namespace RenderToolsDX #if GPU_ENABLE_ASSERTION // DirectX results validation -#define VALIDATE_DIRECTX_RESULT(x) { HRESULT result = x; if (FAILED(result)) RenderToolsDX::ValidateD3DResult(result, __FILE__, __LINE__); } +#define VALIDATE_DIRECTX_CALL(x) { HRESULT result = x; if (FAILED(result)) RenderToolsDX::ValidateD3DResult(result, __FILE__, __LINE__); } #define LOG_DIRECTX_RESULT(result) if (FAILED(result)) RenderToolsDX::LogD3DResult(result, __FILE__, __LINE__) -#define LOG_DIRECTX_RESULT_WITH_RETURN(result) if (FAILED(result)) { RenderToolsDX::LogD3DResult(result, __FILE__, __LINE__); return true; } +#define LOG_DIRECTX_RESULT_WITH_RETURN(result, returnValue) if (FAILED(result)) { RenderToolsDX::LogD3DResult(result, __FILE__, __LINE__); return returnValue; } #else -#define VALIDATE_DIRECTX_RESULT(x) x +#define VALIDATE_DIRECTX_CALL(x) x #define LOG_DIRECTX_RESULT(result) if(FAILED(result)) RenderToolsDX::LogD3DResult(result) -#define LOG_DIRECTX_RESULT_WITH_RETURN(result) if(FAILED(result)) { RenderToolsDX::LogD3DResult(result); return true; } +#define LOG_DIRECTX_RESULT_WITH_RETURN(result, returnValue) if(FAILED(result)) { RenderToolsDX::LogD3DResult(result); return returnValue; } #endif diff --git a/Source/Engine/Renderer/HistogramPass.cpp b/Source/Engine/Renderer/HistogramPass.cpp index ed28d27b3..77d228430 100644 --- a/Source/Engine/Renderer/HistogramPass.cpp +++ b/Source/Engine/Renderer/HistogramPass.cpp @@ -26,7 +26,7 @@ PACK_STRUCT(struct HistogramData { GPUBuffer* HistogramPass::Render(RenderContext& renderContext, GPUTexture* colorBuffer) { auto device = GPUDevice::Instance; - auto context = device->GetMainContext();; + auto context = device->GetMainContext(); if (checkIfSkipPass() || !_isSupported) return nullptr; From f40657ea043793670caa3305f9c7cc9bd5309be2 Mon Sep 17 00:00:00 2001 From: Andrew Spiering Date: Tue, 19 Sep 2023 20:58:12 -0700 Subject: [PATCH 055/188] macOS support fixes * Adding macOS FileSystemWatcher, this should allow files to be monitored and update like the other OSs * Reworked how macOS launches processes to use NSTask which just deals with escaped and unescaped paths better * Made a change to the ScriptsBuilder::RunBuildTool, this was adding the escaped values to the path, in reality it should be up to the underlying OS to make sure things are properly escaped, so removed those as they just end up causing issues. Also instead of appending the args to the fileName we just properly use the Arguments variable on the CreateProcessSettings * No longer use open in order to show files in the finder, we use the proper method selectFile * made a slight cleanup change to the MacPlatform Tick function * Added ToNSString functions just to make that easier * Added a ParseArguments function that will take a string and turn it into an array for NSTask --- Source/Editor/Scripting/ScriptsBuilder.cpp | 9 +- .../Engine/Platform/Apple/ApplePlatform.cpp | 40 ++++++ Source/Engine/Platform/Apple/AppleUtils.h | 3 + Source/Engine/Platform/FileSystemWatcher.h | 2 + Source/Engine/Platform/Mac/MacFileSystem.cpp | 2 +- .../Platform/Mac/MacFileSystemWatcher.cpp | 100 +++++++++++++++ .../Platform/Mac/MacFileSystemWatcher.h | 39 ++++++ Source/Engine/Platform/Mac/MacPlatform.cpp | 115 ++++++++++++------ Source/Engine/Platform/Types.h | 4 +- 9 files changed, 268 insertions(+), 46 deletions(-) create mode 100644 Source/Engine/Platform/Mac/MacFileSystemWatcher.cpp create mode 100644 Source/Engine/Platform/Mac/MacFileSystemWatcher.h diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp index 3082a531e..8807379cf 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.cpp +++ b/Source/Editor/Scripting/ScriptsBuilder.cpp @@ -246,20 +246,17 @@ bool ScriptsBuilder::RunBuildTool(const StringView& args, const StringView& work Log::FileNotFoundException(monoPath).SetLevel(LogType::Fatal); return true; } - //const String monoPath = TEXT("mono"); - cmdLine.Append(TEXT("\"")); + const String monoPath = TEXT("mono"); cmdLine.Append(monoPath); - cmdLine.Append(TEXT("\" ")); + cmdLine.Append(TEXT(" ")); // TODO: Set env var for the mono MONO_GC_PARAMS=nursery-size64m to boost build performance -> profile it #endif - cmdLine.Append(TEXT("\"")); cmdLine.Append(buildToolPath); - cmdLine.Append(TEXT("\" ")); - cmdLine.Append(args.Get(), args.Length()); // Call build tool CreateProcessSettings procSettings; procSettings.FileName = StringView(*cmdLine, cmdLine.Length()); + procSettings.Arguments = args.Get(); procSettings.WorkingDirectory = workingDir; const int32 result = Platform::CreateProcess(procSettings); if (result != 0) diff --git a/Source/Engine/Platform/Apple/ApplePlatform.cpp b/Source/Engine/Platform/Apple/ApplePlatform.cpp index 8ae5a484f..eeef5b91f 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.cpp +++ b/Source/Engine/Platform/Apple/ApplePlatform.cpp @@ -70,6 +70,46 @@ CFStringRef AppleUtils::ToString(const StringView& str) return CFStringCreateWithBytes(nullptr, (const UInt8*)str.GetText(), str.Length() * sizeof(Char), kCFStringEncodingUTF16LE, false); } +NSString* AppleUtils::ToNSString(const StringView& str) +{ + NSString* ret = !str.IsEmpty() ? [[NSString alloc] initWithBytes: (const UInt8*)str.Get() length: str.Length() * sizeof(Char) encoding: NSUTF16LittleEndianStringEncoding] : nil; + return ret ? ret : @""; +} + +NSString* AppleUtils::ToNSString(const char* string) +{ + NSString* ret = string ? [NSString stringWithUTF8String: string] : nil; + return ret ? ret : @""; +} + + +NSArray* AppleUtils::ParseArguments(NSString* argsString) { + NSMutableArray *argsArray = [NSMutableArray array]; + NSScanner *scanner = [NSScanner scannerWithString:argsString]; + NSString *currentArg = nil; + BOOL insideQuotes = NO; + + while (![scanner isAtEnd]) { + if (insideQuotes) { + [scanner scanUpToString:@"\"" intoString:¤tArg]; + [scanner scanString:@"\"" intoString:NULL]; + insideQuotes = NO; + } else { + [scanner scanUpToString:@" " intoString:¤tArg]; + [scanner scanString:@" " intoString:NULL]; + } + + if ([currentArg isEqualToString:@"\""]) { + insideQuotes = YES; + } else if (currentArg) { + [argsArray addObject:currentArg]; + } + } + + return [argsArray copy]; +} + + typedef uint16_t offset_t; #define align_mem_up(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) diff --git a/Source/Engine/Platform/Apple/AppleUtils.h b/Source/Engine/Platform/Apple/AppleUtils.h index d6f639042..7144ba14b 100644 --- a/Source/Engine/Platform/Apple/AppleUtils.h +++ b/Source/Engine/Platform/Apple/AppleUtils.h @@ -13,6 +13,9 @@ class AppleUtils public: static String ToString(CFStringRef str); static CFStringRef ToString(const StringView& str); + static NSString* ToNSString(const StringView& str); + static NSString* ToNSString(const char* string); + static NSArray* ParseArguments(NSString* argsString); #if PLATFORM_MAC static Float2 PosToCoca(const Float2& pos); static Float2 CocaToPos(const Float2& pos); diff --git a/Source/Engine/Platform/FileSystemWatcher.h b/Source/Engine/Platform/FileSystemWatcher.h index c6b44ca80..1f02f5725 100644 --- a/Source/Engine/Platform/FileSystemWatcher.h +++ b/Source/Engine/Platform/FileSystemWatcher.h @@ -6,6 +6,8 @@ #include "Windows/WindowsFileSystemWatcher.h" #elif PLATFORM_LINUX #include "Linux/LinuxFileSystemWatcher.h" +#elif PLATFORM_MAC +#include "Mac/MacFileSystemWatcher.h" #else #include "Base/FileSystemWatcherBase.h" #endif diff --git a/Source/Engine/Platform/Mac/MacFileSystem.cpp b/Source/Engine/Platform/Mac/MacFileSystem.cpp index 5efa1905b..c765feef4 100644 --- a/Source/Engine/Platform/Mac/MacFileSystem.cpp +++ b/Source/Engine/Platform/Mac/MacFileSystem.cpp @@ -132,7 +132,7 @@ bool MacFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const StringVie bool MacFileSystem::ShowFileExplorer(const StringView& path) { - return Platform::StartProcess(TEXT("open"), String::Format(TEXT("\"{0}\""), path), StringView::Empty) != 0; + return [[NSWorkspace sharedWorkspace] selectFile: AppleUtils::ToNSString(FileSystem::ConvertRelativePathToAbsolute(path)) inFileViewerRootedAtPath: @""]; } #endif diff --git a/Source/Engine/Platform/Mac/MacFileSystemWatcher.cpp b/Source/Engine/Platform/Mac/MacFileSystemWatcher.cpp new file mode 100644 index 000000000..be13e2737 --- /dev/null +++ b/Source/Engine/Platform/Mac/MacFileSystemWatcher.cpp @@ -0,0 +1,100 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#if PLATFORM_MAC +#include "MacFileSystemWatcher.h" +#include "Engine/Platform/Apple/AppleUtils.h" +#include "Engine/Platform/CriticalSection.h" +#include "Engine/Platform/Thread.h" +#include "Engine/Threading/ThreadSpawner.h" +#include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Types/StringView.h" + +void DirectoryWatchCallback( ConstFSEventStreamRef StreamRef, void* FileWatcherPtr, size_t EventCount, void* EventPaths, const FSEventStreamEventFlags EventFlags[], const FSEventStreamEventId EventIDs[] ) +{ + MacFileSystemWatcher* macFileSystemWatcher = (MacFileSystemWatcher*)FileWatcherPtr; + if (macFileSystemWatcher) + { + CFArrayRef EventPathArray = (CFArrayRef)EventPaths; + for( size_t EventIndex = 0; EventIndex < EventCount; ++EventIndex ) + { + const FSEventStreamEventFlags Flags = EventFlags[EventIndex]; + if( !(Flags & kFSEventStreamEventFlagItemIsFile) && !(Flags & kFSEventStreamEventFlagItemIsDir) ) + { + // events about symlinks don't concern us + continue; + } + + auto action = FileSystemAction::Unknown; + + const bool added = ( Flags & kFSEventStreamEventFlagItemCreated ); + const bool renamed = ( Flags & kFSEventStreamEventFlagItemRenamed ); + const bool modified = ( Flags & kFSEventStreamEventFlagItemModified ); + const bool removed = ( Flags & kFSEventStreamEventFlagItemRemoved ); + + if (added) + { + action = FileSystemAction::Create; + } + + if (renamed || modified) + { + action = FileSystemAction::Delete; + } + + if (removed) + { + action = FileSystemAction::Modify; + } + + const String resolvedPath = AppleUtils::ToString((CFStringRef)CFArrayGetValueAtIndex(EventPathArray,EventIndex)); + + macFileSystemWatcher->OnEvent(resolvedPath, action); + } + } +} + +MacFileSystemWatcher::MacFileSystemWatcher(const String& directory, bool withSubDirs) + : FileSystemWatcherBase(directory, withSubDirs) +{ + + CFStringRef FullPathMac = AppleUtils::ToString(StringView(directory)); + CFArrayRef PathsToWatch = CFArrayCreate(NULL, (const void**)&FullPathMac, 1, NULL); + + CFAbsoluteTime Latency = 0.2; + + FSEventStreamContext Context; + Context.version = 0; + Context.info = this; + Context.retain = NULL; + Context.release = NULL; + Context.copyDescription = NULL; + + EventStream = FSEventStreamCreate( NULL, + &DirectoryWatchCallback, + &Context, + PathsToWatch, + kFSEventStreamEventIdSinceNow, + Latency, + kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents + ); + + CFRelease(PathsToWatch); + CFRelease(FullPathMac); + + FSEventStreamScheduleWithRunLoop( EventStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode ); + FSEventStreamStart( EventStream ); + + IsRunning = true; +} + +MacFileSystemWatcher::~MacFileSystemWatcher() +{ + if (IsRunning) + { + FSEventStreamStop(EventStream); + FSEventStreamUnscheduleFromRunLoop(EventStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + FSEventStreamInvalidate(EventStream); + FSEventStreamRelease(EventStream); + } +} +#endif diff --git a/Source/Engine/Platform/Mac/MacFileSystemWatcher.h b/Source/Engine/Platform/Mac/MacFileSystemWatcher.h new file mode 100644 index 000000000..97ca20bda --- /dev/null +++ b/Source/Engine/Platform/Mac/MacFileSystemWatcher.h @@ -0,0 +1,39 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_MAC +#include "Engine/Platform/Base/FileSystemWatcherBase.h" + +#include + + +/// +/// Mac platform implementation of the file system watching object. +/// +class FLAXENGINE_API MacFileSystemWatcher : public FileSystemWatcherBase +{ +public: + + /// + /// Initializes a new instance of the class. + /// + /// The directory to watch. + /// True if monitor the directory tree rooted at the specified directory or just a given directory. + MacFileSystemWatcher(const String& directory, bool withSubDirs); + + /// + /// Finalizes an instance of the class. + /// + ~MacFileSystemWatcher(); + +public: + + + +private: + + FSEventStreamRef EventStream; + bool IsRunning; +}; +#endif diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp index 8cba5e1dc..4c965f549 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.cpp +++ b/Source/Engine/Platform/Mac/MacPlatform.cpp @@ -324,13 +324,16 @@ void MacPlatform::BeforeRun() void MacPlatform::Tick() { // Process system events - while (true) + NSEvent* event = nil; + do { - NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES]; - if (event == nil) - break; - [NSApp sendEvent:event]; - } + NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES]; + if (event) + { + [NSApp sendEvent:event]; + } + + } while(event); ApplePlatform::Tick(); } @@ -461,50 +464,88 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings) } } } - - // Sanatize the string if the exePath has spaces with properly espcaped spaces for popen - exePath.Replace(TEXT(" "), TEXT("\\ ")); - const String cmdLine = exePath + TEXT(" ") + settings.Arguments; - const StringAsANSI<> cmdLineAnsi(*cmdLine, cmdLine.Length()); - FILE* pipe = popen(cmdLineAnsi.Get(), "r"); + NSTask *task = [[NSTask alloc] init]; + task.launchPath = AppleUtils::ToNSString(exePath); + task.arguments = AppleUtils::ParseArguments(AppleUtils::ToNSString(settings.Arguments)); + if (cwd.Length() != 0) - { - Platform::SetWorkingDirectory(cwd); - } - if (!pipe) - { - LOG(Warning, "Failed to start process, errno={}", errno); - return -1; - } - - // TODO: environment - + task.currentDirectoryPath = AppleUtils::ToNSString(cwd); + + int32 returnCode = 0; + if (settings.WaitForEnd) { + id outputObserver = nil; + if (captureStdOut) { - char lineBuffer[1024]; - while (fgets(lineBuffer, sizeof(lineBuffer), pipe) != NULL) + NSPipe *stdoutPipe = [NSPipe pipe]; + [task setStandardOutput:stdoutPipe]; + + outputObserver = [[NSNotificationCenter defaultCenter] + addObserverForName: NSFileHandleDataAvailableNotification + object: [stdoutPipe fileHandleForReading] + queue: nil + usingBlock:^(NSNotification* notification) { - char* p = lineBuffer + strlen(lineBuffer) - 1; - if (*p == '\n') *p = 0; - String line(lineBuffer); - if (settings.SaveOutput) - settings.Output.Add(line.Get(), line.Length()); - if (settings.LogOutput) - Log::Logger::Write(LogType::Info, line); + NSData* data = [stdoutPipe fileHandleForReading].availableData; + if (data.length) + { + String line((char*)data.bytes); + if (settings.SaveOutput) + settings.Output.Add(line.Get(), line.Length()); + if (settings.LogOutput) + Log::Logger::Write(LogType::Info, line); + [[stdoutPipe fileHandleForReading] waitForDataInBackgroundAndNotify]; + } } + ]; + + [[stdoutPipe fileHandleForReading] waitForDataInBackgroundAndNotify]; } - else + + String exception; + + @try { - while (!feof(pipe)) - { - sleep(1); - } + [task launch]; + [task waitUntilExit]; + } + @catch (NSException* e) + { + exception = e.reason.UTF8String; + } + + if (!exception.IsEmpty()) + { + LOG(Error, "Failed to run command {0} {1} with error {2}", settings.FileName, settings.Arguments, exception); + return -1; + } + + returnCode = [task terminationStatus]; + } + else { + + String exception; + + @try + { + [task launch]; + } + @catch (NSException* e) + { + exception = e.reason.UTF8String; + } + + if (!exception.IsEmpty()) + { + LOG(Error, "Failed to run command {0} {1} with error {2}", settings.FileName, settings.Arguments, exception); + return -1; } } + return returnCode; } diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h index 76135dcaf..863743121 100644 --- a/Source/Engine/Platform/Types.h +++ b/Source/Engine/Platform/Types.h @@ -237,8 +237,8 @@ class UnixConditionVariable; typedef UnixConditionVariable ConditionVariable; class MacFileSystem; typedef MacFileSystem FileSystem; -class FileSystemWatcherBase; -typedef FileSystemWatcherBase FileSystemWatcher; +class MacFileSystemWatcher; +typedef MacFileSystemWatcher FileSystemWatcher; class UnixFile; typedef UnixFile File; class MacPlatform; From e3cf9c05e435edd043d18193d19c5b1f73ce2124 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 20 Sep 2023 09:06:54 +0200 Subject: [PATCH 056/188] Fix logging macOS process to remove redundant newlines --- Source/Engine/Core/Log.cpp | 4 ++-- Source/Engine/Platform/Mac/MacPlatform.cpp | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp index fea72bfe8..1147c4ff1 100644 --- a/Source/Engine/Core/Log.cpp +++ b/Source/Engine/Core/Log.cpp @@ -223,10 +223,10 @@ void Log::Logger::ProcessLogMessage(LogType type, const StringView& msg, fmt_fla else { //w.append(msg.Get(), msg.Get() + msg.Length()); - fmt_flax::format(w, TEXT("{}"), (const Char*)msg.Get()); + fmt_flax::format(w, TEXT("{}"), msg); } #else - fmt_flax::format(w, TEXT("{}"), (const Char*)msg.Get()); + fmt_flax::format(w, TEXT("{}"), msg); #endif } diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp index 4c965f549..8f566073f 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.cpp +++ b/Source/Engine/Platform/Mac/MacPlatform.cpp @@ -327,7 +327,7 @@ void MacPlatform::Tick() NSEvent* event = nil; do { - NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES]; + event = [NSApp nextEventMatchingMask: NSEventMaskAny untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES]; if (event) { [NSApp sendEvent:event]; @@ -464,17 +464,15 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings) } } } - + NSTask *task = [[NSTask alloc] init]; task.launchPath = AppleUtils::ToNSString(exePath); task.arguments = AppleUtils::ParseArguments(AppleUtils::ToNSString(settings.Arguments)); - + if (cwd.Length() != 0) task.currentDirectoryPath = AppleUtils::ToNSString(cwd); - - + int32 returnCode = 0; - if (settings.WaitForEnd) { id outputObserver = nil; @@ -493,11 +491,16 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings) NSData* data = [stdoutPipe fileHandleForReading].availableData; if (data.length) { - String line((char*)data.bytes); + String line((const char*)data.bytes, data.length); if (settings.SaveOutput) settings.Output.Add(line.Get(), line.Length()); if (settings.LogOutput) - Log::Logger::Write(LogType::Info, line); + { + StringView lineView(line); + if (line[line.Length() - 1] == '\n') + lineView = StringView(line.Get(), line.Length() - 1); + Log::Logger::Write(LogType::Info, lineView); + } [[stdoutPipe fileHandleForReading] waitForDataInBackgroundAndNotify]; } } From 1740cbf2eb90dc4080b3351c4a83c4c18e99c998 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 20 Sep 2023 09:21:56 +0200 Subject: [PATCH 057/188] Improve process starting on mac --- Source/Engine/Platform/Mac/MacPlatform.cpp | 30 ++++++---------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp index 8f566073f..5750cd260 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.cpp +++ b/Source/Engine/Platform/Mac/MacPlatform.cpp @@ -432,13 +432,6 @@ Window* MacPlatform::CreateWindow(const CreateWindowSettings& settings) int32 MacPlatform::CreateProcess(CreateProcessSettings& settings) { LOG(Info, "Command: {0} {1}", settings.FileName, settings.Arguments); - String cwd; - if (settings.WorkingDirectory.HasChars()) - { - LOG(Info, "Working directory: {0}", settings.WorkingDirectory); - cwd = Platform::GetWorkingDirectory(); - Platform::SetWorkingDirectory(settings.WorkingDirectory); - } const bool captureStdOut = settings.LogOutput || settings.SaveOutput; // Special case if filename points to the app package (use actual executable) @@ -468,9 +461,8 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings) NSTask *task = [[NSTask alloc] init]; task.launchPath = AppleUtils::ToNSString(exePath); task.arguments = AppleUtils::ParseArguments(AppleUtils::ToNSString(settings.Arguments)); - - if (cwd.Length() != 0) - task.currentDirectoryPath = AppleUtils::ToNSString(cwd); + if (settings.WorkingDirectory.HasChars()) + task.currentDirectoryPath = AppleUtils::ToNSString(settings.WorkingDirectory); int32 returnCode = 0; if (settings.WaitForEnd) @@ -510,29 +502,25 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings) } String exception; - @try { [task launch]; [task waitUntilExit]; + returnCode = [task terminationStatus]; } @catch (NSException* e) { exception = e.reason.UTF8String; } - if (!exception.IsEmpty()) { LOG(Error, "Failed to run command {0} {1} with error {2}", settings.FileName, settings.Arguments, exception); - return -1; + returnCode = -1; } - - returnCode = [task terminationStatus]; } - else { - - String exception; - + else + { + String exception; @try { [task launch]; @@ -541,14 +529,12 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings) { exception = e.reason.UTF8String; } - if (!exception.IsEmpty()) { LOG(Error, "Failed to run command {0} {1} with error {2}", settings.FileName, settings.Arguments, exception); - return -1; + returnCode = -1; } } - return returnCode; } From 34a36d822a2dde52e179a68ba49c4da3831d7949 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 20 Sep 2023 10:06:16 +0200 Subject: [PATCH 058/188] Improve parsing command line in build tools when using lots of quotes --- .../Tools/Flax.Build.Tests/TestCommandLine.cs | 6 ++++ Source/Tools/Flax.Build/CommandLine.cs | 34 ++++++++++++++----- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Source/Tools/Flax.Build.Tests/TestCommandLine.cs b/Source/Tools/Flax.Build.Tests/TestCommandLine.cs index db530b70d..905dc99af 100644 --- a/Source/Tools/Flax.Build.Tests/TestCommandLine.cs +++ b/Source/Tools/Flax.Build.Tests/TestCommandLine.cs @@ -12,6 +12,7 @@ namespace Flax.Build.Tests [Test, Sequential] public void TestParseOptionOnly([Values( "-something", + "something", " \t \t-\t \tsomething\t ", "-something=")] string commandLine) @@ -25,6 +26,11 @@ namespace Flax.Build.Tests [Test, Sequential] public void TestParseOneValue([Values( "-something=value", + "something=value", + "-something=\"value\"", + "-something=\\\"value\\\"", + "\"-something=\"value\"\"", + "\"-something=\\\"value\\\"\"", " \t \t-\t \tsomething\t =value ", "-something=value ")] string commandLine) diff --git a/Source/Tools/Flax.Build/CommandLine.cs b/Source/Tools/Flax.Build/CommandLine.cs index 91b6febce..f0a631145 100644 --- a/Source/Tools/Flax.Build/CommandLine.cs +++ b/Source/Tools/Flax.Build/CommandLine.cs @@ -249,6 +249,8 @@ namespace Flax.Build var wholeQuote = commandLine[i] == '\"'; if (wholeQuote) i++; + if (i == length) + break; if (commandLine[i] == '-') i++; else if (commandLine[i] == '/') @@ -279,7 +281,7 @@ namespace Flax.Build }); if (wholeQuote) i++; - if (i != length && commandLine[i] != '\"') + if (i < length && commandLine[i] != '\"') i++; continue; } @@ -287,23 +289,36 @@ namespace Flax.Build // Read value i++; int valueStart, valueEnd; - if (commandLine[i] == '\"') + if (commandLine.Length > i + 1 && commandLine[i] == '\\' && commandLine[i + 1] == '\"') { - valueStart = i + 1; + valueStart = i + 2; i++; - while (i < length && commandLine[i] != '\"') + while (i + 1 < length && commandLine[i] != '\\' && commandLine[i + 1] != '\"') i++; valueEnd = i; - i++; + i += 2; + if (wholeQuote) + { + while (i < length && commandLine[i] != '\"') + i++; + i++; + } } - else if (commandLine[i] == '\'') + else if (commandLine[i] == '\"' || commandLine[i] == '\'') { + var quoteChar = commandLine[i]; valueStart = i + 1; i++; - while (i < length && commandLine[i] != '\'') + while (i < length && commandLine[i] != quoteChar) i++; valueEnd = i; i++; + if (wholeQuote) + { + while (i < length && commandLine[i] != '\"') + i++; + i++; + } } else if (wholeQuote) { @@ -321,10 +336,13 @@ namespace Flax.Build valueEnd = i; } string value = commandLine.Substring(valueStart, valueEnd - valueStart); + value = value.Trim(); + if (value.StartsWith("\\\"") && value.EndsWith("\\\"")) + value = value.Substring(2, value.Length - 4); options.Add(new Option { Name = name, - Value = value.Trim() + Value = value }); } From 5c5c64cf761ebc79c4f53fb6eec6c88d0536a93d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 20 Sep 2023 11:52:26 +0200 Subject: [PATCH 059/188] Update macOS app cooking to properly execute tools --- .../Cooker/Platform/Mac/MacPlatformTools.cpp | 63 +++++++++++++------ 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp index c059acba9..b66a96a9b 100644 --- a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp @@ -5,6 +5,7 @@ #include "MacPlatformTools.h" #include "Engine/Platform/File.h" #include "Engine/Platform/FileSystem.h" +#include "Engine/Platform/CreateProcessSettings.h" #include "Engine/Platform/Mac/MacPlatformSettings.h" #include "Engine/Core/Config/GameSettings.h" #include "Engine/Core/Config/BuildSettings.h" @@ -124,17 +125,35 @@ bool MacPlatformTools::OnPostProcess(CookingData& data) LOG(Error, "Failed to export application icon."); return true; } - bool failed = Platform::RunProcess(TEXT("sips -z 16 16 icon_1024x1024.png --out icon_16x16.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 32 32 icon_1024x1024.png --out icon_16x16@2x.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 32 32 icon_1024x1024.png --out icon_32x32.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 64 64 icon_1024x1024.png --out icon_32x32@2x.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 128 128 icon_1024x1024.png --out icon_128x128.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 256 256 icon_1024x1024.png --out icon_128x128@2x.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 256 256 icon_1024x1024.png --out icon_256x256.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 512 512 icon_1024x1024.png --out icon_256x256@2x.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 512 512 icon_1024x1024.png --out icon_512x512.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 1024 1024 icon_1024x1024.png --out icon_512x512@2x.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("iconutil -c icns icon.iconset"), iconFolderPath); + CreateProcessSettings procSettings; + procSettings.HiddenWindow = true; + procSettings.FileName = TEXT("/usr/bin/sips"); + procSettings.WorkingDirectory = tmpFolderPath; + procSettings.Arguments = TEXT("-z 16 16 icon_1024x1024.png --out icon_16x16.png"); + bool failed = false; + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 32 32 icon_1024x1024.png --out icon_16x16@2x.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 32 32 icon_1024x1024.png --out icon_32x32.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 64 64 icon_1024x1024.png --out icon_32x32@2x.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 128 128 icon_1024x1024.png --out icon_128x128.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 256 256 icon_1024x1024.png --out icon_128x128@2x.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 256 256 icon_1024x1024.png --out icon_256x256.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 512 512 icon_1024x1024.png --out icon_256x256@2x.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 512 512 icon_1024x1024.png --out icon_512x512.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 1024 1024 icon_1024x1024.png --out icon_512x512@2x.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.FileName = TEXT("/usr/bin/iconutil"); + procSettings.Arguments = TEXT("-c icns icon.iconset"); + procSettings.WorkingDirectory = iconFolderPath; + failed |= Platform::CreateProcess(procSettings); if (failed) { LOG(Error, "Failed to export application icon."); @@ -210,16 +229,22 @@ bool MacPlatformTools::OnPostProcess(CookingData& data) return false; GameCooker::PackageFiles(); LOG(Info, "Building app package..."); - const String dmgPath = data.OriginalOutputPath / appName + TEXT(".dmg"); - const String dmgCommand = String::Format(TEXT("hdiutil create {0}.dmg -volname {0} -fs HFS+ -srcfolder {0}.app"), appName); - const int32 result = Platform::RunProcess(dmgCommand, data.OriginalOutputPath); - if (result != 0) { - data.Error(String::Format(TEXT("Failed to package app (result code: {0}). See log for more info."), result)); - return true; + const String dmgPath = data.OriginalOutputPath / appName + TEXT(".dmg"); + CreateProcessSettings procSettings; + procSettings.HiddenWindow = true; + procSettings.WorkingDirectory = data.OriginalOutputPath; + procSettings.FileName = TEXT("/usr/bin/hdiutil"); + procSettings.Arguments = String::Format(TEXT("create {0}.dmg -volname {0} -fs HFS+ -srcfolder {0}.app"), appName); + const int32 result = Platform::CreateProcess(procSettings); + if (result != 0) + { + data.Error(String::Format(TEXT("Failed to package app (result code: {0}). See log for more info."), result)); + return true; + } + // TODO: sign dmg + LOG(Info, "Output application package: {0} (size: {1} MB)", dmgPath, FileSystem::GetFileSize(dmgPath) / 1024 / 1024); } - // TODO: sign dmg - LOG(Info, "Output application package: {0} (size: {1} MB)", dmgPath, FileSystem::GetFileSize(dmgPath) / 1024 / 1024); return false; } From 8bd66495101e40bd6626a30d0c36227dd8b2d33f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 20 Sep 2023 14:17:11 +0200 Subject: [PATCH 060/188] Refactor managed array unboxing to handle case of C# array passes as object #1415 --- Source/Engine/Engine/NativeInterop.Unmanaged.cs | 13 +++++++++++++ Source/Engine/Scripting/ManagedCLR/MCore.h | 1 + Source/Engine/Scripting/ManagedCLR/MUtils.h | 5 +++-- Source/Engine/Scripting/Runtime/DotNet.cpp | 6 ++++++ Source/Engine/Scripting/Runtime/Mono.cpp | 5 +++++ Source/Engine/Scripting/Runtime/None.cpp | 5 +++++ 6 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 670b7fff5..423aae6a5 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -559,6 +559,19 @@ namespace FlaxEngine.Interop return managedArray.Pointer; } + [UnmanagedCallersOnly] + internal static IntPtr GetArray(ManagedHandle handle) + { + if (!handle.IsAllocated) + return IntPtr.Zero; + object value = handle.Target; + if (value is ManagedArray) + return (IntPtr)handle; + if (value is Array) + return Invoker.MarshalReturnValueGeneric(value.GetType(), value); + return IntPtr.Zero; + } + [UnmanagedCallersOnly] internal static int GetArrayLength(ManagedHandle arrayHandle) { diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.h b/Source/Engine/Scripting/ManagedCLR/MCore.h index e1de3c207..7c61031c7 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.h +++ b/Source/Engine/Scripting/ManagedCLR/MCore.h @@ -85,6 +85,7 @@ public: static MClass* GetClass(MClass* elementKlass); static int32 GetLength(const MArray* obj); static void* GetAddress(const MArray* obj); + static MArray* Unbox(MObject* obj); template FORCE_INLINE static T* GetAddress(const MArray* obj) diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.h b/Source/Engine/Scripting/ManagedCLR/MUtils.h index 7b340e5c6..a9b2d8414 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.h +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.h @@ -363,11 +363,12 @@ struct MConverter> void Unbox(Array& result, MObject* data) { - const int32 length = data ? MCore::Array::GetLength((MArray*)data) : 0; + MArray* array = MCore::Array::Unbox(data); + const int32 length = array ? MCore::Array::GetLength(array) : 0; result.Resize(length); MConverter converter; Span resultSpan(result.Get(), length); - converter.ToNativeArray(resultSpan, (MArray*)data); + converter.ToNativeArray(resultSpan, array); } }; diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 2157e8dff..c859fa961 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -408,6 +408,12 @@ void* MCore::Array::GetAddress(const MArray* obj) return CallStaticMethod(GetArrayPointerPtr, (void*)obj); } +MArray* MCore::Array::Unbox(MObject* obj) +{ + static void* GetArrayPtr = GetStaticMethodPointer(TEXT("GetArray")); + return (MArray*)CallStaticMethod(GetArrayPtr, (void*)obj); +} + MGCHandle MCore::GCHandle::New(MObject* obj, bool pinned) { ASSERT(obj); diff --git a/Source/Engine/Scripting/Runtime/Mono.cpp b/Source/Engine/Scripting/Runtime/Mono.cpp index 0a60db42f..da21e90d0 100644 --- a/Source/Engine/Scripting/Runtime/Mono.cpp +++ b/Source/Engine/Scripting/Runtime/Mono.cpp @@ -804,6 +804,11 @@ void* MCore::Array::GetAddress(const MArray* obj) return mono_array_addr_with_size((MonoArray*)obj, 0, 0); } +MArray* MCore::Array::Unbox(MObject* obj) +{ + return (MArray*)obj; +} + MGCHandle MCore::GCHandle::New(MObject* obj, bool pinned) { return mono_gchandle_new(obj, pinned); diff --git a/Source/Engine/Scripting/Runtime/None.cpp b/Source/Engine/Scripting/Runtime/None.cpp index ef9c118cf..39d85ad84 100644 --- a/Source/Engine/Scripting/Runtime/None.cpp +++ b/Source/Engine/Scripting/Runtime/None.cpp @@ -141,6 +141,11 @@ void* MCore::Array::GetAddress(const MArray* obj) return nullptr; } +MArray* MCore::Array::Unbox(MObject* obj) +{ + return nullptr; +} + MGCHandle MCore::GCHandle::New(MObject* obj, bool pinned) { return (MGCHandle)(uintptr)obj; From 8cd8ae93c3281e525970931eb380fc05e6ea8afb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 20 Sep 2023 15:01:02 +0200 Subject: [PATCH 061/188] Add `ObjectRadius` to `DrawCall` to properly cull local lights and env probes in Forward Shading --- .../Graphics/Materials/MaterialShaderFeatures.cpp | 10 ++++++---- Source/Engine/Graphics/Models/Mesh.cpp | 3 +++ Source/Engine/Graphics/Models/SkinnedMesh.cpp | 2 ++ Source/Engine/Level/Actors/Skybox.cpp | 1 + Source/Engine/Level/Actors/SplineModel.cpp | 1 + Source/Engine/Particles/Particles.cpp | 3 ++- Source/Engine/Renderer/DrawCall.h | 5 +++++ Source/Engine/Renderer/GBufferPass.cpp | 1 + Source/Engine/Renderer/RenderList.cpp | 1 + Source/Engine/Terrain/TerrainChunk.cpp | 6 ++++-- Source/Engine/Terrain/TerrainChunk.h | 3 ++- Source/Engine/UI/TextRender.cpp | 1 + 12 files changed, 29 insertions(+), 8 deletions(-) diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp index b3069b4db..d8d69dc3e 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -76,11 +76,11 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanEnvironmentProbes.Count(); i++) { const auto p = cache->EnvironmentProbes[i]; - if (p->GetSphere().Contains(drawCallOrigin) != ContainmentType::Disjoint) + if (CollisionsHelper::SphereIntersectsSphere(objectBoundsWorld, p->GetSphere())) { probe = p; break; @@ -99,10 +99,12 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanPointLights.Count() && data.LocalLightsCount < MaxLocalLights; i++) { const auto& light = cache->PointLights[i]; - if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.ObjectPosition) != ContainmentType::Disjoint) + if (CollisionsHelper::SphereIntersectsSphere(objectBounds, BoundingSphere(light.Position, light.Radius))) { light.SetupLightData(&data.LocalLights[data.LocalLightsCount], false); data.LocalLightsCount++; @@ -111,7 +113,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanSpotLights.Count() && data.LocalLightsCount < MaxLocalLights; i++) { const auto& light = cache->SpotLights[i]; - if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.ObjectPosition) != ContainmentType::Disjoint) + if (CollisionsHelper::SphereIntersectsSphere(objectBounds, BoundingSphere(light.Position, light.Radius))) { light.SetupLightData(&data.LocalLights[data.LocalLightsCount], false); data.LocalLightsCount++; diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index e5ba5488d..558b53b18 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -429,6 +429,7 @@ void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, cons drawCall.Material = material; drawCall.World = world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = _sphere.Radius * drawCall.World.GetScaleVector().GetAbsolute().MaxValue(); drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = world; drawCall.Surface.Lightmap = nullptr; @@ -495,6 +496,7 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float drawCall.Material = material; drawCall.World = *info.World; drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; drawCall.Surface.Lightmap = (info.Flags & StaticFlags::Lightmap) != StaticFlags::None ? info.Lightmap : nullptr; @@ -555,6 +557,7 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in drawCall.Material = material; drawCall.World = *info.World; drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; drawCall.Surface.Lightmap = (info.Flags & StaticFlags::Lightmap) != StaticFlags::None ? info.Lightmap : nullptr; diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index b6c966508..f6d24c335 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -198,6 +198,7 @@ void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, drawCall.Material = material; drawCall.World = *info.World; drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; drawCall.Surface.Lightmap = nullptr; @@ -258,6 +259,7 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI drawCall.Material = material; drawCall.World = *info.World; drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; drawCall.Surface.Lightmap = nullptr; diff --git a/Source/Engine/Level/Actors/Skybox.cpp b/Source/Engine/Level/Actors/Skybox.cpp index 7d35a2776..455c229c0 100644 --- a/Source/Engine/Level/Actors/Skybox.cpp +++ b/Source/Engine/Level/Actors/Skybox.cpp @@ -100,6 +100,7 @@ void Skybox::ApplySky(GPUContext* context, RenderContext& renderContext, const M Platform::MemoryClear(&drawCall, sizeof(DrawCall)); drawCall.World = world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = _sphere.Radius; drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.WorldDeterminantSign = Math::FloatSelect(world.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = GetPerInstanceRandom(); diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 6ca841b41..6b7916e4d 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -405,6 +405,7 @@ void SplineModel::Draw(RenderContext& renderContext) const Transform splineTransform = GetTransform(); renderContext.View.GetWorldMatrix(splineTransform, drawCall.World); drawCall.ObjectPosition = drawCall.World.GetTranslation() + drawCall.Deformable.LocalMatrix.GetTranslation(); + drawCall.ObjectRadius = _sphere.Radius; // TODO: use radius for the spline chunk rather than whole spline const float worldDeterminantSign = drawCall.World.RotDeterminant() * drawCall.Deformable.LocalMatrix.RotDeterminant(); for (int32 segment = 0; segment < _instances.Count(); segment++) { diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index 873c4bd52..52234fd7a 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -938,7 +938,8 @@ void Particles::DrawParticles(RenderContext& renderContext, ParticleEffect* effe // Setup a draw call common data DrawCall drawCall; drawCall.PerInstanceRandom = effect->GetPerInstanceRandom(); - drawCall.ObjectPosition = effect->GetPosition(); + drawCall.ObjectPosition = effect->GetSphere().Center - view.Origin; + drawCall.ObjectRadius = effect->GetSphere().Radius; // Draw all emitters for (int32 emitterIndex = 0; emitterIndex < effect->Instance.Emitters.Count(); emitterIndex++) diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h index 9fd6c13b5..d51102166 100644 --- a/Source/Engine/Renderer/DrawCall.h +++ b/Source/Engine/Renderer/DrawCall.h @@ -258,6 +258,11 @@ struct DrawCall /// Float3 ObjectPosition; + /// + /// Object bounding sphere radius that contains it whole (sphere at ObjectPosition). + /// + float ObjectRadius; + /// /// The world matrix determinant sign (used for geometry that is two sided or has inverse scale - needs to flip normal vectors and change triangles culling). /// diff --git a/Source/Engine/Renderer/GBufferPass.cpp b/Source/Engine/Renderer/GBufferPass.cpp index 0e5e8508a..f824d2161 100644 --- a/Source/Engine/Renderer/GBufferPass.cpp +++ b/Source/Engine/Renderer/GBufferPass.cpp @@ -464,6 +464,7 @@ void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* light transform.Scale *= decal->GetSize(); renderContext.View.GetWorldMatrix(transform, drawCall.World); drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = decal->GetSphere().Radius; context->ResetRenderTarget(); diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index 1a4cc0205..f24f25128 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -855,6 +855,7 @@ DRAW: { auto& batch = BatchedDrawCalls.Get()[list.PreBatchedDrawCalls.Get()[i]]; auto drawCall = batch.DrawCall; + drawCall.ObjectRadius = 0.0f; bindParams.FirstDrawCall = &drawCall; const auto* instancesData = batch.Instances.Get(); diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp index 7db786ec2..737ce08f8 100644 --- a/Source/Engine/Terrain/TerrainChunk.cpp +++ b/Source/Engine/Terrain/TerrainChunk.cpp @@ -45,7 +45,7 @@ bool TerrainChunk::PrepareDraw(const RenderContext& renderContext) // Calculate chunk distance to view const auto lodView = (renderContext.LodProxyView ? renderContext.LodProxyView : &renderContext.View); - const float distance = Float3::Distance(_boundsCenter - lodView->Origin, lodView->Position); + const float distance = Float3::Distance(_sphere.Center - lodView->Origin, lodView->Position); lod = (int32)Math::Pow(distance / chunkEdgeSize, lodDistribution); lod += lodBias; @@ -88,6 +88,7 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const drawCall.Material = _cachedDrawMaterial; renderContext.View.GetWorldMatrix(_transform, drawCall.World); drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = _sphere.Radius; drawCall.Terrain.Patch = _patch; drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias; drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); @@ -145,6 +146,7 @@ void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* materi drawCall.Material = material; renderContext.View.GetWorldMatrix(_transform, drawCall.World); drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = _sphere.Radius; drawCall.Terrain.Patch = _patch; drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias; drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); @@ -202,7 +204,7 @@ void TerrainChunk::UpdateBounds() OrientedBoundingBox obb(Vector3::Zero, Vector3::One); obb.Transform(localTransform); obb.GetBoundingBox(_bounds); - _boundsCenter = _bounds.GetCenter(); + BoundingSphere::FromBox(_bounds, _sphere); _bounds.Minimum -= boundsExtent; _bounds.Maximum += boundsExtent; diff --git a/Source/Engine/Terrain/TerrainChunk.h b/Source/Engine/Terrain/TerrainChunk.h index dd74a18a1..884160b45 100644 --- a/Source/Engine/Terrain/TerrainChunk.h +++ b/Source/Engine/Terrain/TerrainChunk.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Core/Math/BoundingBox.h" +#include "Engine/Core/Math/BoundingSphere.h" #include "Engine/Core/Math/Matrix.h" #include "Engine/Core/Math/Transform.h" #include "Engine/Core/ISerializable.h" @@ -29,7 +30,7 @@ private: Float4 _heightmapUVScaleBias; Transform _transform; BoundingBox _bounds; - Vector3 _boundsCenter; + BoundingSphere _sphere; float _perInstanceRandom; float _yOffset, _yHeight; diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index cd60abddf..d161db0ee 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -366,6 +366,7 @@ void TextRender::Draw(RenderContext& renderContext) DrawCall drawCall; drawCall.World = world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = _sphere.Radius; drawCall.Surface.GeometrySize = _localBox.GetSize(); drawCall.Surface.PrevWorld = _drawState.PrevWorld; drawCall.Surface.Lightmap = nullptr; From 4611de5ed61453a290ec8d92f20a6de675928ec1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 20 Sep 2023 16:02:15 +0200 Subject: [PATCH 062/188] Bump up build number --- Flax.flaxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flax.flaxproj b/Flax.flaxproj index 50fcaeda3..fbf48cc2b 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -3,7 +3,7 @@ "Version": { "Major": 1, "Minor": 6, - "Build": 6344 + "Build": 6345 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.", From 3ede4c21923e85e2868ec82800b10fd3029cbe59 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 20 Sep 2023 16:02:24 +0200 Subject: [PATCH 063/188] Fix typo --- Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp index fd14f0aeb..b58684a4e 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp @@ -122,7 +122,7 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const { // Create shader ID3D11DomainShader* buffer = nullptr; - VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateDomainShader(cacheBytes, cacheSize, nullptr, &buffer)); + result = _device->GetDevice()->CreateDomainShader(cacheBytes, cacheSize, nullptr, &buffer); LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object From 821c373ae2fe403d558079a17ff9c413465e7014 Mon Sep 17 00:00:00 2001 From: Andrew Spiering Date: Wed, 20 Sep 2023 09:12:48 -0700 Subject: [PATCH 064/188] Allow for better support for running on m1/2 machines * So we need to account for 2 possible situations where you are running under and emulated process and a native process with a different target host in this case x64 --- .../Tools/Flax.Build/Build/DotNet/DotNetSdk.cs | 8 ++++++-- .../Flax.Build/Platforms/Mac/MacPlatform.cs | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs index 161a0cc15..20f64b92e 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs @@ -215,8 +215,12 @@ namespace Flax.Build } } - // Use x64 when cross-compiling from ARM64 - if (architecture == TargetArchitecture.ARM64 && (Configuration.BuildArchitectures != null && Configuration.BuildArchitectures[0] == TargetArchitecture.x64)) + bool isRunningOnArm64Targetx64 = architecture == TargetArchitecture.ARM64 && (Configuration.BuildArchitectures != null && Configuration.BuildArchitectures[0] == TargetArchitecture.x64); + + // We need to support two paths here: + // 1. We are running an x64 binary and we are running on an arm64 host machine + // 2. We are running an Arm64 binary and we are targeting an x64 host machine + if (Flax.Build.Platforms.MacPlatform.GetProcessIsTranslated() || isRunningOnArm64Targetx64) { rid = "osx-x64"; dotnetPath = Path.Combine(dotnetPath, "x64"); diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs index 53d299515..a680fbb1a 100644 --- a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs @@ -1,4 +1,5 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System.Runtime.InteropServices; namespace Flax.Build.Platforms { @@ -41,5 +42,20 @@ namespace Flax.Build.Platforms default: return false; } } + + [DllImport ("c")] + public static unsafe extern int sysctlbyname ( + string name, void* oldp, ulong *oldlenp, void* newp, ulong newlen); + + public unsafe static bool GetProcessIsTranslated() + { + int ret = 0; + ulong size = sizeof (int); + if (sysctlbyname ("sysctl.proc_translated", &ret, &size, null, 0) == -1) { + return false; + } + + return ret != 0; + } } } From dfb9f1985ff11f824b7e5c3ffa741f502f078cb9 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 20 Sep 2023 13:19:03 -0500 Subject: [PATCH 065/188] Improve input editors to use combo box. --- .../CustomEditors/Editors/InputEditor.cs | 82 ++++++++++++------- 1 file changed, 53 insertions(+), 29 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/InputEditor.cs b/Source/Editor/CustomEditors/Editors/InputEditor.cs index 416626e81..7a356c6cd 100644 --- a/Source/Editor/CustomEditors/Editors/InputEditor.cs +++ b/Source/Editor/CustomEditors/Editors/InputEditor.cs @@ -1,8 +1,10 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using FlaxEditor.CustomEditors.GUI; +using FlaxEditor.GUI; +using FlaxEditor.GUI.ContextMenu; using FlaxEngine; -using FlaxEngine.GUI; namespace FlaxEditor.CustomEditors.Editors { @@ -12,7 +14,7 @@ namespace FlaxEditor.CustomEditors.Editors [CustomEditor(typeof(InputEvent)), DefaultEditor] public class InputEventEditor : CustomEditor { - private Dropdown _dropdown; + private ComboBox _comboBox; /// public override DisplayStyle Style => DisplayStyle.Inline; @@ -20,23 +22,30 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var dropdownElement = layout.Custom(); - _dropdown = dropdownElement.CustomControl; - var names = new List(); + LinkedLabel.SetupContextMenu += OnSetupContextMenu; + var comboBoxElement = layout.ComboBox(); + _comboBox = comboBoxElement.ComboBox; + var names = new List(); foreach (var mapping in Input.ActionMappings) { if (!names.Contains(mapping.Name)) names.Add(mapping.Name); } - _dropdown.Items = names; + _comboBox.Items = names; if (Values[0] is InputEvent inputEvent && names.Contains(inputEvent.Name)) - _dropdown.SelectedItem = inputEvent.Name; - _dropdown.SelectedIndexChanged += OnSelectedIndexChanged; + _comboBox.SelectedItem = inputEvent.Name; + _comboBox.SelectedIndexChanged += OnSelectedIndexChanged; } - private void OnSelectedIndexChanged(Dropdown dropdown) + private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor) { - SetValue(new InputEvent(dropdown.SelectedItem)); + var button = menu.AddButton("Reset to null"); + button.Clicked += () => _comboBox.SelectedItem = null; + } + + private void OnSelectedIndexChanged(ComboBox comboBox) + { + SetValue(comboBox.SelectedItem == null ? null : new InputEvent(comboBox.SelectedItem)); } /// @@ -49,17 +58,21 @@ namespace FlaxEditor.CustomEditors.Editors } else { - if (Values[0] is InputEvent inputEvent && _dropdown.Items.Contains(inputEvent.Name)) - _dropdown.SelectedItem = inputEvent.Name; + if (Values[0] is InputEvent inputEvent && _comboBox.Items.Contains(inputEvent.Name)) + _comboBox.SelectedItem = inputEvent.Name; + else + _comboBox.SelectedItem = null; } } /// protected override void Deinitialize() { - if (_dropdown != null) - _dropdown.SelectedIndexChanged -= OnSelectedIndexChanged; - _dropdown = null; + if (LinkedLabel != null) + LinkedLabel.SetupContextMenu -= OnSetupContextMenu; + if (_comboBox != null) + _comboBox.SelectedIndexChanged -= OnSelectedIndexChanged; + _comboBox = null; } } @@ -69,7 +82,7 @@ namespace FlaxEditor.CustomEditors.Editors [CustomEditor(typeof(InputAxis)), DefaultEditor] public class InputAxisEditor : CustomEditor { - private Dropdown _dropdown; + private ComboBox _comboBox; /// public override DisplayStyle Style => DisplayStyle.Inline; @@ -77,23 +90,30 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var dropdownElement = layout.Custom(); - _dropdown = dropdownElement.CustomControl; - var names = new List(); + LinkedLabel.SetupContextMenu += OnSetupContextMenu; + var comboBoxElement = layout.ComboBox(); + _comboBox = comboBoxElement.ComboBox; + var names = new List(); foreach (var mapping in Input.AxisMappings) { if (!names.Contains(mapping.Name)) names.Add(mapping.Name); } - _dropdown.Items = names; + _comboBox.Items = names; if (Values[0] is InputAxis inputAxis && names.Contains(inputAxis.Name)) - _dropdown.SelectedItem = inputAxis.Name; - _dropdown.SelectedIndexChanged += OnSelectedIndexChanged; + _comboBox.SelectedItem = inputAxis.Name; + _comboBox.SelectedIndexChanged += OnSelectedIndexChanged; } - private void OnSelectedIndexChanged(Dropdown dropdown) + private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor) { - SetValue(new InputAxis(dropdown.SelectedItem)); + var button = menu.AddButton("Reset to null"); + button.Clicked += () => _comboBox.SelectedItem = null; + } + + private void OnSelectedIndexChanged(ComboBox comboBox) + { + SetValue(comboBox.SelectedItem == null ? null : new InputAxis(comboBox.SelectedItem)); } /// @@ -106,17 +126,21 @@ namespace FlaxEditor.CustomEditors.Editors } else { - if (Values[0] is InputAxis inputAxis && _dropdown.Items.Contains(inputAxis.Name)) - _dropdown.SelectedItem = inputAxis.Name; + if (Values[0] is InputAxis inputAxis && _comboBox.Items.Contains(inputAxis.Name)) + _comboBox.SelectedItem = inputAxis.Name; + else + _comboBox.SelectedItem = null; } } /// protected override void Deinitialize() { - if (_dropdown != null) - _dropdown.SelectedIndexChanged -= OnSelectedIndexChanged; - _dropdown = null; + if (LinkedLabel != null) + LinkedLabel.SetupContextMenu -= OnSetupContextMenu; + if (_comboBox != null) + _comboBox.SelectedIndexChanged -= OnSelectedIndexChanged; + _comboBox = null; } } } From fea296bcbb677f51853655d223cef4f74373ef17 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Wed, 20 Sep 2023 21:59:37 +0300 Subject: [PATCH 066/188] Fix white window flickering in context menus --- Source/Engine/Platform/CreateWindowSettings.cs | 1 + Source/Engine/Platform/CreateWindowSettings.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Platform/CreateWindowSettings.cs b/Source/Engine/Platform/CreateWindowSettings.cs index 1e5d17484..d4d9ce727 100644 --- a/Source/Engine/Platform/CreateWindowSettings.cs +++ b/Source/Engine/Platform/CreateWindowSettings.cs @@ -23,6 +23,7 @@ namespace FlaxEngine AllowDragAndDrop = true, IsRegularWindow = true, HasSizingFrame = true, + ShowAfterFirstPaint = true, }; } } diff --git a/Source/Engine/Platform/CreateWindowSettings.h b/Source/Engine/Platform/CreateWindowSettings.h index 71bbf58c3..1ff596df9 100644 --- a/Source/Engine/Platform/CreateWindowSettings.h +++ b/Source/Engine/Platform/CreateWindowSettings.h @@ -131,7 +131,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(CreateWindowSettings); /// /// Enable/disable window auto-show after the first paint. /// - API_FIELD() bool ShowAfterFirstPaint = false; + API_FIELD() bool ShowAfterFirstPaint = true; /// /// The custom data (platform dependant). From 812813251dff853d1e3969e3e7c520709f84403f Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 20 Sep 2023 14:42:18 -0500 Subject: [PATCH 067/188] Fix being able to create folder from project folder. --- Source/Editor/Windows/ContentWindow.ContextMenu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index 283133688..f460034d0 100644 --- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs +++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs @@ -147,7 +147,7 @@ namespace FlaxEditor.Windows cm.AddSeparator(); - if (!isRootFolder) + if (!isRootFolder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectTreeNode)) { cm.AddButton("New folder", NewFolder); } From d328f0adc4b69a0485b59451f783edefb0c63f1e Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Wed, 20 Sep 2023 23:13:07 +0300 Subject: [PATCH 068/188] Update build scripts to detect only supported versions of MSBuild --- Development/Scripts/Windows/CallBuildTool.bat | 12 +--- .../Scripts/Windows/GetMSBuildPath.bat | 66 ++++--------------- 2 files changed, 14 insertions(+), 64 deletions(-) diff --git a/Development/Scripts/Windows/CallBuildTool.bat b/Development/Scripts/Windows/CallBuildTool.bat index 9f60368c5..22c2d1dd7 100644 --- a/Development/Scripts/Windows/CallBuildTool.bat +++ b/Development/Scripts/Windows/CallBuildTool.bat @@ -11,16 +11,6 @@ for %%I in (Source\Logo.png) do if %%~zI LSS 2000 ( call "Development\Scripts\Windows\GetMSBuildPath.bat" if errorlevel 1 goto Error_NoVisualStudioEnvironment -if not exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" goto Compile -for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -products * -requires Microsoft.Component.MSBuild -property installationPath') do ( - for %%j in (15.0, Current) do ( - if exist "%%i\MSBuild\%%j\Bin\MSBuild.exe" ( - set MSBUILD_PATH="%%i\MSBuild\%%j\Bin\MSBuild.exe" - goto Compile - ) - ) -) - :Compile md Cache\Intermediate >nul 2>nul dir /s /b Source\Tools\Flax.Build\*.cs >Cache\Intermediate\Flax.Build.Files.txt @@ -44,7 +34,7 @@ goto Exit echo CallBuildTool ERROR: The script is in invalid directory. goto Exit :Error_NoVisualStudioEnvironment -echo CallBuildTool ERROR: Missing Visual Studio 2015 or newer. +echo CallBuildTool ERROR: Missing Visual Studio 2022 or newer. goto Exit :Error_CompilationFailed echo CallBuildTool ERROR: Failed to compile Flax.Build project. diff --git a/Development/Scripts/Windows/GetMSBuildPath.bat b/Development/Scripts/Windows/GetMSBuildPath.bat index b20230118..f44155272 100644 --- a/Development/Scripts/Windows/GetMSBuildPath.bat +++ b/Development/Scripts/Windows/GetMSBuildPath.bat @@ -4,66 +4,26 @@ rem Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. set MSBUILD_PATH= +rem Look for MSBuild version 17.0 or later if not exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" goto VsWhereNotFound -for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -products * -requires Microsoft.Component.MSBuild -property installationPath') do ( - if exist "%%i\MSBuild\15.0\Bin\MSBuild.exe" ( - set MSBUILD_PATH="%%i\MSBuild\15.0\Bin\MSBuild.exe" - goto End - ) -) -for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -prerelease -products * -requires Microsoft.Component.MSBuild -property installationPath') do ( - if exist "%%i\MSBuild\15.0\Bin\MSBuild.exe" ( - set MSBUILD_PATH="%%i\MSBuild\15.0\Bin\MSBuild.exe" - goto End - ) +for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -version 17.0 -latest -products * -requires Microsoft.Component.MSBuild -property installationPath') do ( if exist "%%i\MSBuild\Current\Bin\MSBuild.exe" ( set MSBUILD_PATH="%%i\MSBuild\Current\Bin\MSBuild.exe" goto End ) ) -:VsWhereNotFound -if exist "%ProgramFiles(x86)%\MSBuild\14.0\bin\MSBuild.exe" ( - set MSBUILD_PATH="%ProgramFiles(x86)%\MSBuild\14.0\bin\MSBuild.exe" - goto End +rem Look for MSBuild version 17.0 or later in pre-release versions +for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -version 17.0 -latest -prerelease -products * -requires Microsoft.Component.MSBuild -property installationPath') do ( + if exist "%%i\MSBuild\Current\Bin\MSBuild.exe" ( + set MSBUILD_PATH="%%i\MSBuild\Current\Bin\MSBuild.exe" + goto End + ) ) - -call :GetInstallPath Microsoft\VisualStudio\SxS\VS7 15.0 MSBuild\15.0\bin\MSBuild.exe -if not errorlevel 1 goto End -call :GetInstallPath Microsoft\MSBuild\ToolsVersions\14.0 MSBuildToolsPath MSBuild.exe -if not errorlevel 1 goto End -call :GetInstallPath Microsoft\MSBuild\ToolsVersions\12.0 MSBuildToolsPath MSBuild.exe -if not errorlevel 1 goto End -call :GetInstallPath Microsoft\MSBuild\ToolsVersions\4.0 MSBuildToolsPath MSBuild.exe -if not errorlevel 1 goto End - +echo GetMSBuildPath ERROR: Could not find MSBuild version 17.0 or later. +exit /B 1 +:VsWhereNotFound +echo GetMSBuildPath ERROR: vswhere.exe was not found. exit /B 1 :End -exit /B 0 - -:GetInstallPath -for /f "tokens=2,*" %%A in ('REG.exe query HKCU\SOFTWARE\%1 /v %2 2^>Nul') do ( - if exist "%%B%%3" ( - set MSBUILD_PATH="%%B%3" - exit /B 0 - ) -) -for /f "tokens=2,*" %%A in ('REG.exe query HKLM\SOFTWARE\%1 /v %2 2^>Nul') do ( - if exist "%%B%3" ( - set MSBUILD_PATH="%%B%3" - exit /B 0 - ) -) -for /f "tokens=2,*" %%A in ('REG.exe query HKCU\SOFTWARE\Wow6432Node\%1 /v %2 2^>Nul') do ( - if exist "%%B%%3" ( - set MSBUILD_PATH="%%B%3" - exit /B 0 - ) -) -for /f "tokens=2,*" %%A in ('REG.exe query HKLM\SOFTWARE\Wow6432Node\%1 /v %2 2^>Nul') do ( - if exist "%%B%3" ( - set MSBUILD_PATH="%%B%3" - exit /B 0 - ) -) -exit /B 1 +exit /B 0 \ No newline at end of file From 07da23338b94c5126af1367a6f2aa3c9a126a1ce Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Wed, 20 Sep 2023 23:13:47 +0300 Subject: [PATCH 069/188] Update README --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dc5abb84f..fac631a6a 100644 --- a/README.md +++ b/README.md @@ -31,19 +31,20 @@ Follow the instructions below to compile and run the engine from source. * Install Visual Studio 2022 or newer * Install Windows 8.1 SDK or newer (via Visual Studio Installer) * Install Microsoft Visual C++ 2015 v140 toolset or newer (via Visual Studio Installer) -* Install .Net 7 SDK (via Visual Studio Installer or [from web](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) +* Install .NET 7 SDK for **Windows x64** (via Visual Studio Installer or [from web](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) * Install Git with LFS * Clone repo (with LFS) * Run **GenerateProjectFiles.bat** * Open `Flax.sln` and set solution configuration to **Editor.Development** and solution platform to **Win64** * Set Flax (C++) or FlaxEngine (C#) as startup project * Compile Flax project (hit F7 or CTRL+Shift+B) +* Optionally set Debug Type to **Managed Only (.NET Core)** to debug C#-only, or **Mixed (.NET Core)** to debug both C++ and C# * Run Flax (hit F5 key) ## Linux * Install Visual Studio Code -* Install .Net 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) +* Install .NET 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) * Ubuntu: `sudo apt install dotnet-sdk-7.0` * Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/)) * Ubuntu: `sudo apt install vulkan-sdk` @@ -66,7 +67,7 @@ Follow the instructions below to compile and run the engine from source. ## Mac * Install XCode -* Install .Net 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) +* Install .NET 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) * Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/)) * Clone repo (with LFS) * Run `GenerateProjectFiles.command` From bcce08e81c4d9a5c2255f3c916f66245acc40d71 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 20 Sep 2023 22:14:54 +0200 Subject: [PATCH 070/188] Codestyle fix and doc --- .../Flax.Build/Platforms/Mac/MacPlatform.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs index a680fbb1a..ef89c8b4e 100644 --- a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs @@ -1,4 +1,5 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + using System.Runtime.InteropServices; namespace Flax.Build.Platforms @@ -43,19 +44,19 @@ namespace Flax.Build.Platforms } } - [DllImport ("c")] - public static unsafe extern int sysctlbyname ( - string name, void* oldp, ulong *oldlenp, void* newp, ulong newlen); - + /// + /// Returns true if running an x64 binary an arm64 host machine. + /// public unsafe static bool GetProcessIsTranslated() { int ret = 0; - ulong size = sizeof (int); - if (sysctlbyname ("sysctl.proc_translated", &ret, &size, null, 0) == -1) { + ulong size = sizeof(int); + if (sysctlbyname("sysctl.proc_translated", &ret, &size, null, 0) == -1) return false; - } - return ret != 0; } + + [DllImport("c")] + private static unsafe extern int sysctlbyname(string name, void* oldp, ulong* oldlenp, void* newp, ulong newlen); } } From cadd0d2356d21bd09bf1a598d802154d4b775555 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 20 Sep 2023 15:18:33 -0500 Subject: [PATCH 071/188] Fix visuals --- Source/Editor/Windows/ContentWindow.ContextMenu.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index f460034d0..6923d634d 100644 --- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs +++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs @@ -145,10 +145,9 @@ namespace FlaxEditor.Windows cm.AddButton("Refresh all thumbnails", RefreshViewItemsThumbnails); } - cm.AddSeparator(); - if (!isRootFolder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectTreeNode)) { + cm.AddSeparator(); cm.AddButton("New folder", NewFolder); } From ffbd14ad495fad820ad14b89fffb6ad18be3908a Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 20 Sep 2023 15:35:36 -0500 Subject: [PATCH 072/188] Fix right clicking on source folder to sometimes select non-visible child. --- Source/Engine/UI/GUI/ContainerControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/UI/GUI/ContainerControl.cs b/Source/Engine/UI/GUI/ContainerControl.cs index 2de96d0b3..8f1c31e89 100644 --- a/Source/Engine/UI/GUI/ContainerControl.cs +++ b/Source/Engine/UI/GUI/ContainerControl.cs @@ -360,7 +360,7 @@ namespace FlaxEngine.GUI { var containerControl = child as ContainerControl; var childAtRecursive = containerControl?.GetChildAtRecursive(childLocation); - if (childAtRecursive != null) + if (childAtRecursive != null && childAtRecursive.Visible) { child = childAtRecursive; } From da7ba0ecbaa33313558cb16da793db7017bfaa6c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 20 Sep 2023 22:41:47 +0200 Subject: [PATCH 073/188] Fix regression on cooking ios game --- .../Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp index 8b0cf06a8..85a7f99fa 100644 --- a/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp @@ -260,15 +260,20 @@ bool iOSPlatformTools::OnPostProcess(CookingData& data) { LOG(Info, "Building app package..."); const Char* configuration = data.Configuration == BuildConfiguration::Release ? TEXT("Release") : TEXT("Debug"); - String command = String::Format(TEXT("xcodebuild -project FlaxGame.xcodeproj -configuration {} -scheme FlaxGame -archivePath FlaxGame.xcarchive archive"), configuration); - int32 result = Platform::RunProcess(command, data.OriginalOutputPath); + CreateProcessSettings procSettings; + procSettings.HiddenWindow = true; + procSettings.WorkingDirectory = data.OriginalOutputPath; + procSettings.FileName = TEXT("/usr/bin/xcodebuild"); + procSettings.Arguments = String::Format(TEXT("-project FlaxGame.xcodeproj -configuration {} -scheme FlaxGame -archivePath FlaxGame.xcarchive archive"), configuration); + int32 result = Platform::CreateProcess(procSettings); if (result != 0) { data.Error(String::Format(TEXT("Failed to package app (result code: {0}). See log for more info."), result)); return true; } - command = TEXT("xcodebuild -exportArchive -archivePath FlaxGame.xcarchive -allowProvisioningUpdates -exportPath . -exportOptionsPlist ExportOptions.plist"); - result = Platform::RunProcess(command, data.OriginalOutputPath); + procSettings.FileName = TEXT("/usr/bin/xcodebuild"); + procSettings.Arguments = TEXT("-exportArchive -archivePath FlaxGame.xcarchive -allowProvisioningUpdates -exportPath . -exportOptionsPlist ExportOptions.plist"); + result = Platform::CreateProcess(procSettings); if (result != 0) { data.Error(String::Format(TEXT("Failed to package app (result code: {0}). See log for more info."), result)); From ed51791b581d5382229503df28432be0730585ad Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Thu, 21 Sep 2023 00:59:09 +0300 Subject: [PATCH 074/188] Prevent generating VS configurations for Win32 and engine projects --- Source/Tools/Flax.Build/Build/Builder.Projects.cs | 4 ++++ .../Projects/VisualStudio/VisualStudioProjectGenerator.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Source/Tools/Flax.Build/Build/Builder.Projects.cs b/Source/Tools/Flax.Build/Build/Builder.Projects.cs index 4fbabe986..790d45515 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Projects.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Projects.cs @@ -126,6 +126,10 @@ namespace Flax.Build if (!platform.HasRequiredSDKsInstalled && (!projectInfo.IsCSharpOnlyProject || platform != Platform.BuildPlatform)) continue; + // Prevent generating configuration data for Windows x86 + if (architecture == TargetArchitecture.x86 && targetPlatform == TargetPlatform.Windows) + continue; + string configurationText = targetName + '.' + platformName + '.' + configurationName; string architectureName = architecture.ToString(); if (platform is IProjectCustomizer customizer) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index 1955a57ba..035c588d6 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -402,6 +402,10 @@ namespace Flax.Build.Projects.VisualStudio if (project.Configurations == null || project.Configurations.Count == 0) throw new Exception("Missing configurations for project " + project.Name); + // Prevent generating default Debug|AnyCPU and Release|AnyCPU configurations from Flax projects + if (project.Name == "BuildScripts" || project.Name == "Flax.Build" || project.Name == "Flax.Build.Tests") + continue; + foreach (var configuration in project.Configurations) { configurations.Add(new SolutionConfiguration(configuration)); From eb5a1f11032fdecc3662a3cc2c3153ea7d86fc98 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 20 Sep 2023 17:49:17 -0500 Subject: [PATCH 075/188] Fix max and min on double click main menu buttons for custom menu bar --- Source/Editor/GUI/MainMenu.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Editor/GUI/MainMenu.cs b/Source/Editor/GUI/MainMenu.cs index 370ad056b..b313fed9f 100644 --- a/Source/Editor/GUI/MainMenu.cs +++ b/Source/Editor/GUI/MainMenu.cs @@ -292,7 +292,8 @@ namespace FlaxEditor.GUI return true; #if PLATFORM_WINDOWS - if (_useCustomWindowSystem) + var child = GetChildAtRecursive(location); + if (_useCustomWindowSystem && child is not Button && child is not MainMenuButton) { if (_window.IsMaximized) _window.Restore(); From ccb3f40de00dde8030982eb08428872a6b98b29b Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 20 Sep 2023 17:58:08 -0500 Subject: [PATCH 076/188] Prevent inital window from being covering the task bar on restore of windows window --- Source/Editor/Modules/WindowsModule.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index 61220f7d5..44c9f18c1 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -721,7 +721,7 @@ namespace FlaxEditor.Modules // Create main window var settings = CreateWindowSettings.Default; settings.Title = "Flax Editor"; - settings.Size = Platform.DesktopSize; + //settings.Size = Platform.DesktopSize; settings.StartPosition = WindowStartPosition.CenterScreen; settings.ShowAfterFirstPaint = true; @@ -746,6 +746,7 @@ namespace FlaxEditor.Modules return; } UpdateWindowTitle(); + MainWindow.Maximize(); // Link for main window events MainWindow.Closing += MainWindow_OnClosing; From 12c0ce9ea57cef28eadbc697ad5e8f4e6ba86974 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 20 Sep 2023 18:09:16 -0500 Subject: [PATCH 077/188] Revert maximize. --- Source/Editor/Modules/WindowsModule.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index 44c9f18c1..086772ca2 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -746,7 +746,6 @@ namespace FlaxEditor.Modules return; } UpdateWindowTitle(); - MainWindow.Maximize(); // Link for main window events MainWindow.Closing += MainWindow_OnClosing; From f315286e2602ae873dfb79607901feb39dfe6ed7 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 20 Sep 2023 19:08:36 -0500 Subject: [PATCH 078/188] Change wording to "Set To Null" --- Source/Editor/CustomEditors/Editors/InputEditor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/InputEditor.cs b/Source/Editor/CustomEditors/Editors/InputEditor.cs index 7a356c6cd..7b2682a84 100644 --- a/Source/Editor/CustomEditors/Editors/InputEditor.cs +++ b/Source/Editor/CustomEditors/Editors/InputEditor.cs @@ -39,7 +39,7 @@ namespace FlaxEditor.CustomEditors.Editors private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor) { - var button = menu.AddButton("Reset to null"); + var button = menu.AddButton("Set to null"); button.Clicked += () => _comboBox.SelectedItem = null; } @@ -107,7 +107,7 @@ namespace FlaxEditor.CustomEditors.Editors private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor) { - var button = menu.AddButton("Reset to null"); + var button = menu.AddButton("Set to null"); button.Clicked += () => _comboBox.SelectedItem = null; } From d4c71b5fd911cc04a2930022de99cdc43a5d49f5 Mon Sep 17 00:00:00 2001 From: Andrew Spiering Date: Wed, 20 Sep 2023 23:16:38 -0700 Subject: [PATCH 079/188] Launching Rider on macOS * Fixing issues where when the editor would try and launch rider on an M1/2 machine it would not properly open because it was the wrong architecture which is now resolved because open will do the right thing * Making the ParseArgs function a little more resilient against escaped arguments --- .../Scripting/CodeEditors/RiderCodeEditor.cpp | 14 +++++++ .../Engine/Platform/Apple/ApplePlatform.cpp | 37 +++++++++++-------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp index 05608d71e..7f5ca6f17 100644 --- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp @@ -268,8 +268,16 @@ void RiderCodeEditor::OpenFile(const String& path, int32 line) // Open file line = line > 0 ? line : 1; CreateProcessSettings procSettings; + +#if !PLATFORM_MAC procSettings.FileName = _execPath; procSettings.Arguments = String::Format(TEXT("\"{0}\" --line {2} \"{1}\""), _solutionPath, path, line); +#else + // This follows pretty much how all the other engines open rider which deals with cross architecture issues + procSettings.FileName = "/usr/bin/open"; + procSettings.Arguments = String::Format(TEXT("-n -a \"{0}\" --args \"{1}\" --line {3} \"{2}\""), _execPath, _solutionPath, path, line); +#endif + procSettings.HiddenWindow = false; procSettings.WaitForEnd = false; procSettings.LogOutput = false; @@ -287,8 +295,14 @@ void RiderCodeEditor::OpenSolution() // Open solution CreateProcessSettings procSettings; +#if !PLATFORM_MAC procSettings.FileName = _execPath; procSettings.Arguments = String::Format(TEXT("\"{0}\""), _solutionPath); +#else + // This follows pretty much how all the other engines open rider which deals with cross architecture issues + procSettings.FileName = "/usr/bin/open"; + procSettings.Arguments = String::Format(TEXT("-n -a \"{0}\" \"{1}\""), _execPath, _solutionPath); +#endif procSettings.HiddenWindow = false; procSettings.WaitForEnd = false; procSettings.LogOutput = false; diff --git a/Source/Engine/Platform/Apple/ApplePlatform.cpp b/Source/Engine/Platform/Apple/ApplePlatform.cpp index eeef5b91f..473fc0987 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.cpp +++ b/Source/Engine/Platform/Apple/ApplePlatform.cpp @@ -85,27 +85,34 @@ NSString* AppleUtils::ToNSString(const char* string) NSArray* AppleUtils::ParseArguments(NSString* argsString) { NSMutableArray *argsArray = [NSMutableArray array]; - NSScanner *scanner = [NSScanner scannerWithString:argsString]; - NSString *currentArg = nil; + NSMutableString *currentArg = [NSMutableString string]; BOOL insideQuotes = NO; - while (![scanner isAtEnd]) { - if (insideQuotes) { - [scanner scanUpToString:@"\"" intoString:¤tArg]; - [scanner scanString:@"\"" intoString:NULL]; - insideQuotes = NO; - } else { - [scanner scanUpToString:@" " intoString:¤tArg]; - [scanner scanString:@" " intoString:NULL]; - } + for (NSInteger i = 0; i < argsString.length; ++i) { + unichar c = [argsString characterAtIndex:i]; - if ([currentArg isEqualToString:@"\""]) { - insideQuotes = YES; - } else if (currentArg) { - [argsArray addObject:currentArg]; + if (c == '\"') { + if (insideQuotes) { + [argsArray addObject:[currentArg copy]]; + [currentArg setString:@""]; + insideQuotes = NO; + } else { + insideQuotes = YES; + } + } else if (c == ' ' && !insideQuotes) { + if (currentArg.length > 0) { + [argsArray addObject:[currentArg copy]]; + [currentArg setString:@""]; + } + } else { + [currentArg appendFormat:@"%C", c]; } } + if (currentArg.length > 0) { + [argsArray addObject:[currentArg copy]]; + } + return [argsArray copy]; } From a420ca72d670def12cd4fd8c2675291f159d8ecc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 21 Sep 2023 11:23:57 +0200 Subject: [PATCH 080/188] Fix Visual Studio solution project to ensure that main project is the first one --- .../VisualStudio/VisualStudioProjectGenerator.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index 1955a57ba..29b75e4c5 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -236,6 +236,20 @@ namespace Flax.Build.Projects.VisualStudio /// public override void GenerateSolution(Solution solution) { + // Ensure that the main project is the first one (initially selected by Visual Studio) + if (solution.MainProject != null && solution.Projects.Length != 0 && solution.Projects[0] != solution.MainProject) + { + for (int i = 1; i < solution.Projects.Length; i++) + { + if (solution.Projects[i] == solution.MainProject) + { + solution.Projects[i] = solution.Projects[0]; + solution.Projects[0] = solution.MainProject; + break; + } + } + } + // Try to extract info from the existing solution file to make random IDs stable var solutionId = Guid.NewGuid(); var folderIds = new Dictionary(); From d94c0d3323d50a8110200c5ed47dbc691e049c60 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 21 Sep 2023 11:26:04 +0200 Subject: [PATCH 081/188] Fix C#-only game projects to reference `precompiled DLLs` instead of `FlaxEngine.csproj` --- .../Flax.Build/Build/Builder.Projects.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Source/Tools/Flax.Build/Build/Builder.Projects.cs b/Source/Tools/Flax.Build/Build/Builder.Projects.cs index 4fbabe986..149829154 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Projects.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Projects.cs @@ -501,6 +501,35 @@ namespace Flax.Build } } + // When generating C#-only projects for Game that uses source-engine distribution replace dependencies on FlaxEngine with fixed dll file refs to fix Intellisense issues + if (rootProject.IsCSharpOnlyProject) + { + Project flaxDependencyToRemove = null; + foreach (var project in projects) + { + if (project.BaseName != "FlaxEngine") + { + var flaxDependency = project.Dependencies.FirstOrDefault(x => x.BaseName == "FlaxEngine"); + if (flaxDependency != null) + { + project.Dependencies.Remove(flaxDependency); + + // TODO: instead of this hack find a way to reference the prebuilt target bindings binary (example: game C# project references FlaxEngine C# prebuilt dll) + project.CSharp.FileReferences.Add(Path.Combine(Globals.EngineRoot, "Binaries/Editor/Win64/Development/FlaxEngine.CSharp.dll")); + + // Remove FlaxEngine from projects to prevent duplicated types errors in Intellisense (eg. Actor type defined in both FlaxEngine.CSharp.dll and FlaxEngine.csproj) + flaxDependencyToRemove = flaxDependency; + } + } + } + if (flaxDependencyToRemove != null) + { + projects.Remove(flaxDependencyToRemove); + foreach (var project in projects) + project.Dependencies.Remove(flaxDependencyToRemove); + } + } + // Setup custom projects GenerateCustomProjects?.Invoke(projects); nativeProjectGenerator.GenerateCustomProjects(projects); From 8f668c85065163ef7d8de308afce7084fdb7111e Mon Sep 17 00:00:00 2001 From: MineBill Date: Thu, 21 Sep 2023 12:39:19 +0300 Subject: [PATCH 082/188] Ensure floating numbers are formatted with enough precision --- Source/Engine/Visject/ShaderGraphValue.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Visject/ShaderGraphValue.cpp b/Source/Engine/Visject/ShaderGraphValue.cpp index b55da8cdb..56d7c9d06 100644 --- a/Source/Engine/Visject/ShaderGraphValue.cpp +++ b/Source/Engine/Visject/ShaderGraphValue.cpp @@ -40,11 +40,11 @@ ShaderGraphValue::ShaderGraphValue(const Variant& v) break; case VariantType::Float: Type = VariantType::Types::Float; - Value = String::Format(TEXT("{}"), v.AsFloat); + Value = String::Format(TEXT("{:.8f}"), v.AsFloat); break; case VariantType::Double: Type = VariantType::Types::Float; - Value = String::Format(TEXT("{}"), (float)v.AsDouble); + Value = String::Format(TEXT("{:.8f}"), (float)v.AsDouble); break; case VariantType::Float2: { From 21c742bd8a2d2b5f55d916649d9fab1d75af8586 Mon Sep 17 00:00:00 2001 From: davevanegdom Date: Thu, 21 Sep 2023 14:54:57 +0200 Subject: [PATCH 083/188] Customizable statusbar --- Source/Editor/Editor.cs | 6 ++- Source/Editor/GUI/StatusBar.cs | 2 +- .../Editor/Modules/ProgressReportingModule.cs | 1 + Source/Editor/Modules/UIModule.cs | 16 ++++++-- Source/Editor/Options/OptionsModule.cs | 9 +++++ Source/Engine/Scripting/Scripting.cs | 8 ++++ Source/Engine/UI/GUI/Style.cs | 38 +++++++++++++++++++ 7 files changed, 74 insertions(+), 6 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 7f0359331..308e40bc1 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -568,7 +568,8 @@ namespace FlaxEditor BackgroundColorSelected = Color.Transparent, BorderColorHighlighted = Color.Transparent, Text = "Save Now", - TooltipText = "Saves now and restarts the auto save timer." + TooltipText = "Saves now and restarts the auto save timer.", + TextColor = Style.Current.Statusbar.TextColor }; _saveNowButton.LocalX += 120; _saveNowButton.Clicked += () => _autoSaveNow = true; @@ -590,7 +591,8 @@ namespace FlaxEditor BackgroundColorSelected = Color.Transparent, BorderColorHighlighted = Color.Transparent, Text = "Cancel", - TooltipText = "Cancels this auto save." + TooltipText = "Cancels this auto save.", + TextColor = Style.Current.Statusbar.TextColor }; _cancelSaveButton.LocalX += 180; _cancelSaveButton.Clicked += () => diff --git a/Source/Editor/GUI/StatusBar.cs b/Source/Editor/GUI/StatusBar.cs index f8f7ae839..93c8f7218 100644 --- a/Source/Editor/GUI/StatusBar.cs +++ b/Source/Editor/GUI/StatusBar.cs @@ -33,7 +33,7 @@ namespace FlaxEditor.GUI /// /// Gets or sets the status text color /// - public Color TextColor { get; set; } = Style.Current.Foreground; + public Color TextColor { get; set; } = Style.Current.Statusbar.TextColor; /// /// Initializes a new instance of the class. diff --git a/Source/Editor/Modules/ProgressReportingModule.cs b/Source/Editor/Modules/ProgressReportingModule.cs index 16acd7f8b..22ae5ea98 100644 --- a/Source/Editor/Modules/ProgressReportingModule.cs +++ b/Source/Editor/Modules/ProgressReportingModule.cs @@ -129,6 +129,7 @@ namespace FlaxEditor.Modules else { Editor.UI.UpdateProgress(string.Empty, 0); + Editor.UI.UpdateStatusBar(); } } diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 08ec09de8..a4a96d075 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -279,9 +279,9 @@ namespace FlaxEditor.Modules Color color; if (Editor.StateMachine.IsPlayMode) - color = Color.OrangeRed; + color = Style.Current.Statusbar.PlayMode; else - color = Style.Current.BackgroundSelected; + color = Style.Current.Statusbar.Normal; string text; if (_statusMessages != null && _statusMessages.Count != 0) @@ -293,6 +293,11 @@ namespace FlaxEditor.Modules else text = "Ready"; + if(ProgressVisible) + { + color = Style.Current.Statusbar.Loading; + } + StatusBar.Text = text; StatusBar.StatusColor = color; _contentStats = contentStats; @@ -338,7 +343,7 @@ namespace FlaxEditor.Modules internal void ProgressFailed(string message) { _progressFailed = true; - StatusBar.StatusColor = Color.Red; + StatusBar.StatusColor = Style.Current.Statusbar.Failed; StatusBar.Text = message; _outputLogButton.Visible = true; } @@ -391,6 +396,10 @@ namespace FlaxEditor.Modules { UpdateStatusBar(); } + else if(ProgressVisible) + { + UpdateStatusBar(); + } } private class CustomWindowBorderControl : Control @@ -753,6 +762,7 @@ namespace FlaxEditor.Modules AnchorPreset = AnchorPresets.HorizontalStretchMiddle, Parent = progressPanel, Offsets = new Margin(progressBarRightMargin, progressBarWidth + progressBarLeftMargin + progressBarRightMargin, 0, 0), + TextColor = Style.Current.Statusbar.TextColor }; UpdateStatusBar(); diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index 68aa11626..4584395be 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -244,6 +244,15 @@ namespace FlaxEditor.Options CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC), ProgressNormal = Color.FromBgra(0xFF0ad328), + Statusbar = new Style.StatusbarStyle() + { + TextColor = Color.White, + Normal = Color.FromBgra(0xFF007ACC), + PlayMode = Color.ParseHex("#2f9135"), + Failed = Color.ParseHex("#9c2424"), + Loading = Color.ParseHex("#2d2d30") + }, + // Fonts FontTitle = options.Interface.TitleFont.GetFont(), FontLarge = options.Interface.LargeFont.GetFont(), diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index 68da64bf7..4215f09b3 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -277,6 +277,14 @@ namespace FlaxEngine TextBoxBackgroundSelected = Color.FromBgra(0xFF3F3F46), CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC), SharedTooltip = new Tooltip(), + Statusbar = new Style.StatusbarStyle() + { + TextColor = Color.White, + Normal = Color.FromBgra(0xFF007ACC), + PlayMode = Color.ParseHex("#2f9135"), + Failed = Color.ParseHex("#9c2424"), + Loading = Color.ParseHex("#2d2d30") + } }; style.DragWindow = style.BackgroundSelected * 0.7f; diff --git a/Source/Engine/UI/GUI/Style.cs b/Source/Engine/UI/GUI/Style.cs index fac65e22f..c98c37b78 100644 --- a/Source/Engine/UI/GUI/Style.cs +++ b/Source/Engine/UI/GUI/Style.cs @@ -164,6 +164,12 @@ namespace FlaxEngine.GUI [EditorOrder(200)] public Color ProgressNormal; + /// + /// The status bar style + /// + [EditorOrder(210)] + public StatusbarStyle Statusbar; + /// /// The arrow right icon. /// @@ -241,5 +247,37 @@ namespace FlaxEngine.GUI /// [EditorOrder(340)] public Tooltip SharedTooltip; + + /// + /// Style for the Statusbar + /// + [System.Serializable, ShowInEditor] + public struct StatusbarStyle + { + /// + /// Color of the text in the Statusbar + /// + public Color TextColor; + + /// + /// Color of the Statusbar in its default state + /// + public Color Normal; + + /// + /// Color of the Statusbar when in Play Mode + /// + public Color PlayMode; + + /// + /// Color of the Statusbar when in loading state (e.g. when importing assets) + /// + public Color Loading; + + /// + /// Color of the Statusbar in its failed state (e.g. with compilation errors) + /// + public Color Failed; + } } } From ad29dd0c929aa76b949b0bbb1dc6281372eaee0f Mon Sep 17 00:00:00 2001 From: Luke Schneider Date: Thu, 21 Sep 2023 08:37:13 -0500 Subject: [PATCH 084/188] MMB Panning Added MMB panning to VisjectSurfaces (materials/etc). --- Source/Editor/Surface/VisjectSurface.Input.cs | 35 ++++++++++++++++++- Source/Editor/Surface/VisjectSurface.cs | 10 ++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index 7264321c3..517040cb7 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -213,6 +213,21 @@ namespace FlaxEditor.Surface return; } + if (_middleMouseDown) { + // Calculate delta + var delta = location - _middleMouseDownPos; + if (delta.LengthSquared > 0.01f) { + // Move view + _mouseMoveAmount += delta.Length; + _rootControl.Location += delta; + _middleMouseDownPos = location; + Cursor = CursorType.SizeAll; + } + + // Handled + return; + } + // Check if user is selecting or moving node(s) if (_leftMouseDown) { @@ -269,6 +284,10 @@ namespace FlaxEditor.Surface _rightMouseDown = false; Cursor = CursorType.Default; } + if (_middleMouseDown) { + _middleMouseDown = false; + Cursor = CursorType.Default; + } _isMovingSelection = false; ConnectingEnd(null); @@ -380,6 +399,7 @@ namespace FlaxEditor.Surface _isMovingSelection = false; _rightMouseDown = false; _leftMouseDown = false; + _middleMouseDown = false; return true; } @@ -398,6 +418,12 @@ namespace FlaxEditor.Surface { _rightMouseDown = true; _rightMouseDownPos = location; + Debug.Log("The right mouse button is down in a window"); + } + if (button == MouseButton.Middle) { + _middleMouseDown = true; + _middleMouseDownPos = location; + Debug.Log("The middle mouse button is down in a window"); } // Check if any node is under the mouse @@ -444,7 +470,7 @@ namespace FlaxEditor.Surface Focus(); return true; } - if (_rightMouseDown) + if (_rightMouseDown || _middleMouseDown) { // Start navigating StartMouseCapture(); @@ -513,6 +539,12 @@ namespace FlaxEditor.Surface } _mouseMoveAmount = 0; } + if (_middleMouseDown && button == MouseButton.Middle) { + _middleMouseDown = false; + EndMouseCapture(); + Cursor = CursorType.Default; + _mouseMoveAmount = 0; + } // Base bool handled = base.OnMouseUp(location, button); @@ -523,6 +555,7 @@ namespace FlaxEditor.Surface // Clear flags _rightMouseDown = false; _leftMouseDown = false; + _middleMouseDown = false; return true; } diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 22aba4855..29dbeb1c6 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -59,6 +59,11 @@ namespace FlaxEditor.Surface /// protected bool _rightMouseDown; + /// + /// The middle mouse down flag. + /// + protected bool _middleMouseDown; + /// /// The left mouse down position. /// @@ -69,6 +74,11 @@ namespace FlaxEditor.Surface /// protected Float2 _rightMouseDownPos = Float2.Minimum; + /// + /// The middle mouse down position. + /// + protected Float2 _middleMouseDownPos = Float2.Minimum; + /// /// The mouse position. /// From 65bf13ea4f1d4ba3f698c1b48deb43d24fd5571a Mon Sep 17 00:00:00 2001 From: Luke Schneider Date: Thu, 21 Sep 2023 08:38:18 -0500 Subject: [PATCH 085/188] Removed debug messages --- Source/Editor/Surface/VisjectSurface.Input.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index 517040cb7..f51d89e82 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -418,12 +418,10 @@ namespace FlaxEditor.Surface { _rightMouseDown = true; _rightMouseDownPos = location; - Debug.Log("The right mouse button is down in a window"); } if (button == MouseButton.Middle) { _middleMouseDown = true; _middleMouseDownPos = location; - Debug.Log("The middle mouse button is down in a window"); } // Check if any node is under the mouse From 80a3bb2ae21bc93d6f6559103a29b46e2fcedc6f Mon Sep 17 00:00:00 2001 From: davevanegdom Date: Thu, 21 Sep 2023 17:10:49 +0200 Subject: [PATCH 086/188] Replaced string color lookups --- Source/Editor/Options/OptionsModule.cs | 6 +++--- Source/Engine/Scripting/Scripting.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index 4584395be..05a75fa4a 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -248,9 +248,9 @@ namespace FlaxEditor.Options { TextColor = Color.White, Normal = Color.FromBgra(0xFF007ACC), - PlayMode = Color.ParseHex("#2f9135"), - Failed = Color.ParseHex("#9c2424"), - Loading = Color.ParseHex("#2d2d30") + PlayMode = Color.FromBgra(0xFF2F9135), + Failed = Color.FromBgra(0xFF9C2424), + Loading = Color.FromBgra(0xFF2D2D30) }, // Fonts diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index 4215f09b3..7e3226412 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -281,9 +281,9 @@ namespace FlaxEngine { TextColor = Color.White, Normal = Color.FromBgra(0xFF007ACC), - PlayMode = Color.ParseHex("#2f9135"), - Failed = Color.ParseHex("#9c2424"), - Loading = Color.ParseHex("#2d2d30") + PlayMode = Color.FromBgra(0xFF2F9135), + Failed = Color.FromBgra(0xFF9C2424), + Loading = Color.FromBgra(0xFF2D2D30) } }; style.DragWindow = style.BackgroundSelected * 0.7f; From d19984d3e9b54b87b6caa494d5c19e6cbb735834 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Thu, 21 Sep 2023 18:02:00 +0200 Subject: [PATCH 087/188] - Pressing space to open visject context menu now ignores the space char as input --- Source/Editor/Surface/VisjectSurface.Input.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index 7264321c3..afc1c1363 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -706,6 +706,8 @@ namespace FlaxEditor.Surface { if (_inputBrackets.Count == 0) { + if (currentInputText.StartsWith(' ')) + currentInputText = ""; ResetInput(); ShowPrimaryMenu(_mousePos, false, currentInputText); } From 7d69abadb705ffc7fcde92a820bace208fe3768e Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Thu, 21 Sep 2023 19:42:52 +0300 Subject: [PATCH 088/188] Fix script reload crash with .NET libraries --- Source/Engine/Engine/NativeInterop.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 8a8d543f1..b1d2e0cde 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -1099,12 +1099,12 @@ namespace FlaxEngine.Interop private delegate IntPtr InvokeThunkDelegate(ManagedHandle instanceHandle, IntPtr param1, IntPtr param2, IntPtr param3, IntPtr param4, IntPtr param5, IntPtr param6, IntPtr param7); /// - /// Returns all types that that owned by this assembly. + /// Returns all types owned by this assembly. /// private static Type[] GetAssemblyTypes(Assembly assembly) { var referencedAssemblies = assembly.GetReferencedAssemblies(); - var allAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + var allAssemblies = Utils.GetAssemblies(); var referencedTypes = new List(); foreach (var assemblyName in referencedAssemblies) { From bf68d17afe060354ea7fba0883b790cf5db83d3e Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Thu, 21 Sep 2023 19:18:11 +0300 Subject: [PATCH 089/188] Create VS folder structure for BuildScripts and FlaxEngine source files --- .../VisualStudio/CSSDKProjectGenerator.cs | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs index 5fba0dc43..d8aece342 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs @@ -174,8 +174,35 @@ namespace Flax.Build.Projects.VisualStudio else fileType = "None"; - var projectPath = Utilities.MakePathRelativeTo(file, projectDirectory); - csProjectFileContent.AppendLine(string.Format(" <{0} Include=\"{1}\" />", fileType, projectPath)); + var filePath = file.Replace('/', '\\'); // Normalize path + var projectPath = Utilities.MakePathRelativeTo(filePath, projectDirectory); + string linkPath = null; + if (projectPath.StartsWith(@"..\..\..\")) + { + // Create folder structure for project external files + var sourceIndex = filePath.LastIndexOf(@"\Source\"); + if (sourceIndex != -1) + { + projectPath = filePath; + string fileProjectRoot = filePath.Substring(0, sourceIndex); + string fileProjectName = Path.GetFileName(fileProjectRoot); + string fileProjectRelativePath = filePath.Substring(sourceIndex + 1); + + // Remove Source-directory from path + if (fileProjectRelativePath.IndexOf('\\') != -1) + fileProjectRelativePath = fileProjectRelativePath.Substring(fileProjectRelativePath.IndexOf('\\') + 1); + + if (fileProjectRoot == project.SourceFolderPath) + linkPath = fileProjectRelativePath; + else // BuildScripts project + linkPath = Path.Combine(fileProjectName, fileProjectRelativePath); + } + } + + if (!string.IsNullOrEmpty(linkPath)) + csProjectFileContent.AppendLine(string.Format(" <{0} Include=\"{1}\" Link=\"{2}\" />", fileType, projectPath, linkPath)); + else + csProjectFileContent.AppendLine(string.Format(" <{0} Include=\"{1}\" />", fileType, projectPath)); } if (project.GeneratedSourceFiles != null) @@ -188,7 +215,8 @@ namespace Flax.Build.Projects.VisualStudio else fileType = "None"; - csProjectFileContent.AppendLine(string.Format(" <{0} Visible=\"false\" Include=\"{1}\" />", fileType, file)); + var filePath = file.Replace('/', '\\'); + csProjectFileContent.AppendLine(string.Format(" <{0} Visible=\"false\" Include=\"{1}\" />", fileType, filePath)); } } From bbf88832754c9f3ecc543b70fcf85f3e1dc92419 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Thu, 21 Sep 2023 20:12:29 +0300 Subject: [PATCH 090/188] Fix docking hint areas not hiding after dragging a window --- Source/Editor/GUI/Docking/DockHintWindow.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Editor/GUI/Docking/DockHintWindow.cs b/Source/Editor/GUI/Docking/DockHintWindow.cs index 6dc700731..52c5dcd3c 100644 --- a/Source/Editor/GUI/Docking/DockHintWindow.cs +++ b/Source/Editor/GUI/Docking/DockHintWindow.cs @@ -476,6 +476,7 @@ namespace FlaxEditor.GUI.Docking settings.ShowInTaskbar = false; settings.ActivateWhenFirstShown = false; settings.IsTopmost = true; + settings.ShowAfterFirstPaint = false; win = Platform.CreateWindow(ref settings); From 5fc9176ce7110030a96c728a46d1eb9e37765200 Mon Sep 17 00:00:00 2001 From: davevanegdom Date: Thu, 21 Sep 2023 23:18:46 +0200 Subject: [PATCH 091/188] Removed customization for "Normal" and "TextColor" --- Source/Editor/Editor.cs | 6 ++---- Source/Editor/GUI/StatusBar.cs | 2 +- Source/Editor/Modules/UIModule.cs | 5 ++--- Source/Editor/Options/OptionsModule.cs | 2 -- Source/Engine/Scripting/Scripting.cs | 2 -- Source/Engine/UI/GUI/Style.cs | 10 ---------- 6 files changed, 5 insertions(+), 22 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 308e40bc1..7f0359331 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -568,8 +568,7 @@ namespace FlaxEditor BackgroundColorSelected = Color.Transparent, BorderColorHighlighted = Color.Transparent, Text = "Save Now", - TooltipText = "Saves now and restarts the auto save timer.", - TextColor = Style.Current.Statusbar.TextColor + TooltipText = "Saves now and restarts the auto save timer." }; _saveNowButton.LocalX += 120; _saveNowButton.Clicked += () => _autoSaveNow = true; @@ -591,8 +590,7 @@ namespace FlaxEditor BackgroundColorSelected = Color.Transparent, BorderColorHighlighted = Color.Transparent, Text = "Cancel", - TooltipText = "Cancels this auto save.", - TextColor = Style.Current.Statusbar.TextColor + TooltipText = "Cancels this auto save." }; _cancelSaveButton.LocalX += 180; _cancelSaveButton.Clicked += () => diff --git a/Source/Editor/GUI/StatusBar.cs b/Source/Editor/GUI/StatusBar.cs index 93c8f7218..f8f7ae839 100644 --- a/Source/Editor/GUI/StatusBar.cs +++ b/Source/Editor/GUI/StatusBar.cs @@ -33,7 +33,7 @@ namespace FlaxEditor.GUI /// /// Gets or sets the status text color /// - public Color TextColor { get; set; } = Style.Current.Statusbar.TextColor; + public Color TextColor { get; set; } = Style.Current.Foreground; /// /// Initializes a new instance of the class. diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index a4a96d075..69e610c07 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -281,7 +281,7 @@ namespace FlaxEditor.Modules if (Editor.StateMachine.IsPlayMode) color = Style.Current.Statusbar.PlayMode; else - color = Style.Current.Statusbar.Normal; + color = Style.Current.BackgroundSelected; string text; if (_statusMessages != null && _statusMessages.Count != 0) @@ -761,8 +761,7 @@ namespace FlaxEditor.Modules HorizontalAlignment = TextAlignment.Far, AnchorPreset = AnchorPresets.HorizontalStretchMiddle, Parent = progressPanel, - Offsets = new Margin(progressBarRightMargin, progressBarWidth + progressBarLeftMargin + progressBarRightMargin, 0, 0), - TextColor = Style.Current.Statusbar.TextColor + Offsets = new Margin(progressBarRightMargin, progressBarWidth + progressBarLeftMargin + progressBarRightMargin, 0, 0) }; UpdateStatusBar(); diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index 05a75fa4a..c2d744239 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -246,8 +246,6 @@ namespace FlaxEditor.Options Statusbar = new Style.StatusbarStyle() { - TextColor = Color.White, - Normal = Color.FromBgra(0xFF007ACC), PlayMode = Color.FromBgra(0xFF2F9135), Failed = Color.FromBgra(0xFF9C2424), Loading = Color.FromBgra(0xFF2D2D30) diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index 7e3226412..d43804586 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -279,8 +279,6 @@ namespace FlaxEngine SharedTooltip = new Tooltip(), Statusbar = new Style.StatusbarStyle() { - TextColor = Color.White, - Normal = Color.FromBgra(0xFF007ACC), PlayMode = Color.FromBgra(0xFF2F9135), Failed = Color.FromBgra(0xFF9C2424), Loading = Color.FromBgra(0xFF2D2D30) diff --git a/Source/Engine/UI/GUI/Style.cs b/Source/Engine/UI/GUI/Style.cs index c98c37b78..8f34a703c 100644 --- a/Source/Engine/UI/GUI/Style.cs +++ b/Source/Engine/UI/GUI/Style.cs @@ -254,16 +254,6 @@ namespace FlaxEngine.GUI [System.Serializable, ShowInEditor] public struct StatusbarStyle { - /// - /// Color of the text in the Statusbar - /// - public Color TextColor; - - /// - /// Color of the Statusbar in its default state - /// - public Color Normal; - /// /// Color of the Statusbar when in Play Mode /// From 521b1803521f2de86e76c8414d0f282231745573 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 21 Sep 2023 18:56:45 -0500 Subject: [PATCH 092/188] Fix issue with copying and poasting in same folder. --- Source/Editor/Windows/ContentWindow.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 2628735eb..285302d03 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -722,7 +722,12 @@ namespace FlaxEditor.Windows { var item = Editor.ContentDatabase.Find(sourcePath); if (item != null) - Editor.ContentDatabase.Copy(item, Path.Combine(CurrentViewFolder.Path, item.FileName)); + { + var newPath = StringUtils.NormalizePath(Path.Combine(CurrentViewFolder.Path, item.FileName)); + if (sourcePath.Equals(newPath)) + newPath = GetClonedAssetPath(item); + Editor.ContentDatabase.Copy(item, newPath); + } else importFiles.Add(sourcePath); } From 7012832050829eef4f90758a735622e38944b97e Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 21 Sep 2023 21:29:21 -0500 Subject: [PATCH 093/188] Add filter to content window to hide generated files. Hide by default. --- Source/Editor/Windows/ContentWindow.cs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 2628735eb..2151028fa 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -46,7 +46,7 @@ namespace FlaxEditor.Windows private TextBox _itemsSearchBox; private ViewDropdown _viewDropdown; private SortType _sortType; - private bool _showEngineFiles = true, _showPluginsFiles = true, _showAllFiles = true; + private bool _showEngineFiles = true, _showPluginsFiles = true, _showAllFiles = true, _showGeneratedFiles = false; private RootContentTreeNode _root; @@ -106,6 +106,19 @@ namespace FlaxEditor.Windows } } + internal bool ShowGeneratedFiles + { + get => _showGeneratedFiles; + set + { + if (_showGeneratedFiles != value) + { + _showGeneratedFiles = value; + RefreshView(); + } + } + } + internal bool ShowAllFiles { get => _showAllFiles; @@ -314,6 +327,12 @@ namespace FlaxEditor.Windows b.Checked = ShowPluginsFiles; b.CloseMenuOnClick = false; b.AutoCheck = true; + + b = show.ContextMenu.AddButton("Generated files", () => ShowGeneratedFiles = !ShowGeneratedFiles); + b.TooltipText = "Shows generated files"; + b.Checked = ShowGeneratedFiles; + b.CloseMenuOnClick = false; + b.AutoCheck = true; b = show.ContextMenu.AddButton("All files", () => ShowAllFiles = !ShowAllFiles); b.TooltipText = "Shows all files including other than assets and source code"; @@ -969,6 +988,8 @@ namespace FlaxEditor.Windows var items = target.Folder.Children; if (!_showAllFiles) items = items.Where(x => !(x is FileItem)).ToList(); + if (!_showGeneratedFiles) + items = items.Where(x => !(x.Path.Contains(".Gen.cs") || x.Path.Contains(".Gen.h") || x.Path.Contains(".Gen.cpp") || x.Path.Contains(".csproj") || x.Path.Contains(".CSharp"))).ToList(); _view.ShowItems(items, _sortType, false, true); } } @@ -1145,6 +1166,7 @@ namespace FlaxEditor.Windows writer.WriteAttributeString("ShowEngineFiles", ShowEngineFiles.ToString()); writer.WriteAttributeString("ShowPluginsFiles", ShowPluginsFiles.ToString()); writer.WriteAttributeString("ShowAllFiles", ShowAllFiles.ToString()); + writer.WriteAttributeString("ShowGeneratedFiles", ShowGeneratedFiles.ToString()); writer.WriteAttributeString("ViewType", _view.ViewType.ToString()); } @@ -1162,6 +1184,8 @@ namespace FlaxEditor.Windows ShowPluginsFiles = value2; if (bool.TryParse(node.GetAttribute("ShowAllFiles"), out value2)) ShowAllFiles = value2; + if (bool.TryParse(node.GetAttribute("ShowGeneratedFiles"), out value2)) + ShowGeneratedFiles = value2; if (Enum.TryParse(node.GetAttribute("ViewType"), out ContentViewType viewType)) _view.ViewType = viewType; } From 1714bc243a28740a0521ef922d8c7cc4102695b2 Mon Sep 17 00:00:00 2001 From: MineBill Date: Fri, 22 Sep 2023 11:03:59 +0300 Subject: [PATCH 094/188] Fix not capturing the the Debug.Log line in the DebugLogWindow --- Source/Editor/Windows/DebugLogWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs index 49b03ede1..09a93d300 100644 --- a/Source/Editor/Windows/DebugLogWindow.cs +++ b/Source/Editor/Windows/DebugLogWindow.cs @@ -544,7 +544,7 @@ namespace FlaxEditor.Windows if (noLocation) { desc.LocationFile = match.Groups[2].Value; - int.TryParse(match.Groups[5].Value, out desc.LocationLine); + int.TryParse(match.Groups[4].Value, out desc.LocationLine); noLocation = false; } fineStackTrace.AppendLine(match.Groups[0].Value); From 017967d5f83914c61dc08c30af3c2e8343816aa7 Mon Sep 17 00:00:00 2001 From: Andrew Spiering Date: Fri, 22 Sep 2023 02:13:49 -0700 Subject: [PATCH 095/188] Fixing non-windows platforms csproj files * Seems there was a hardcoded path ? for the prebuilt binaries for the FlaxEngine dll --- Source/Tools/Flax.Build/Build/Builder.Projects.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/Builder.Projects.cs b/Source/Tools/Flax.Build/Build/Builder.Projects.cs index 4b606a36d..52c6d4ac9 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Projects.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Projects.cs @@ -497,7 +497,7 @@ namespace Flax.Build else if (dependencyModule.BinaryModuleName == "FlaxEngine") { // TODO: instead of this hack find a way to reference the prebuilt target bindings binary (example: game C# project references FlaxEngine C# prebuilt dll) - project.CSharp.FileReferences.Add(Path.Combine(Globals.EngineRoot, "Binaries/Editor/Win64/Development/FlaxEngine.CSharp.dll")); + project.CSharp.FileReferences.Add(Path.Combine(Globals.EngineRoot, $"Binaries/Editor/{project.Configurations[0].PlatformName}/Development/FlaxEngine.CSharp.dll")); } } } @@ -519,7 +519,7 @@ namespace Flax.Build project.Dependencies.Remove(flaxDependency); // TODO: instead of this hack find a way to reference the prebuilt target bindings binary (example: game C# project references FlaxEngine C# prebuilt dll) - project.CSharp.FileReferences.Add(Path.Combine(Globals.EngineRoot, "Binaries/Editor/Win64/Development/FlaxEngine.CSharp.dll")); + project.CSharp.FileReferences.Add(Path.Combine(Globals.EngineRoot, $"Binaries/Editor/{project.Configurations[0].PlatformName}/Development/FlaxEngine.CSharp.dll")); // Remove FlaxEngine from projects to prevent duplicated types errors in Intellisense (eg. Actor type defined in both FlaxEngine.CSharp.dll and FlaxEngine.csproj) flaxDependencyToRemove = flaxDependency; From 1537f49e736750898e0e4b1d3a92aaaa65c5d9b7 Mon Sep 17 00:00:00 2001 From: Andrew Spiering Date: Fri, 22 Sep 2023 02:41:09 -0700 Subject: [PATCH 096/188] Fixing Editor Path * Again the path was hardcoded to win64 --- .../VisualStudio/VisualStudioProjectGenerator.cs | 10 ++++++++-- Source/Tools/Flax.Build/Utilities/Utilities.cs | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index 6653a8dac..16ab543ad 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -405,6 +405,8 @@ namespace Flax.Build.Projects.VisualStudio vcSolutionFileContent.AppendLine("EndProject"); } + var globalPlatformName = ""; + // Global configuration { vcSolutionFileContent.AppendLine("Global"); @@ -422,6 +424,10 @@ namespace Flax.Build.Projects.VisualStudio foreach (var configuration in project.Configurations) { + // We just grab the platform name from the first config + if (string.IsNullOrEmpty(globalPlatformName)) + globalPlatformName = configuration.PlatformName; + configurations.Add(new SolutionConfiguration(configuration)); } } @@ -558,8 +564,8 @@ namespace Flax.Build.Projects.VisualStudio { var profiles = new Dictionary(); var profile = new StringBuilder(); - var editorPath = Path.Combine(Globals.EngineRoot, "Binaries/Editor/Win64/Development/FlaxEditor.exe").Replace('/', '\\').Replace("\\", "\\\\"); - var workspacePath = solutionDirectory.Replace('/', '\\').Replace("\\", "\\\\"); + var editorPath = Utilities.NormalizePath(Path.Combine(Globals.EngineRoot, $"Binaries/Editor/{globalPlatformName}/Development/FlaxEditor{Utilities.GetPlatformExecutableExt()}")); + var workspacePath = Utilities.NormalizePath(solutionDirectory); foreach (var project in projects) { if (project.Type == TargetType.DotNetCore) diff --git a/Source/Tools/Flax.Build/Utilities/Utilities.cs b/Source/Tools/Flax.Build/Utilities/Utilities.cs index af6777199..994e11c8e 100644 --- a/Source/Tools/Flax.Build/Utilities/Utilities.cs +++ b/Source/Tools/Flax.Build/Utilities/Utilities.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Text; namespace Flax.Build @@ -746,5 +747,19 @@ namespace Flax.Build text = text.Replace(findWhat, replaceWith); File.WriteAllText(file, text); } + + /// + /// Returns back the exe ext for the current platform + /// + public static string GetPlatformExecutableExt() + { + var extEnding = ".exe"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + extEnding = ""; + } + + return extEnding; + } } } From ab7ca683bb684745c2ee0751e5d178a490000289 Mon Sep 17 00:00:00 2001 From: Andrew Spiering Date: Fri, 22 Sep 2023 02:59:38 -0700 Subject: [PATCH 097/188] Addressing Feedback * use a better method of getting Editor binary folder --- Source/Tools/Flax.Build/Build/Builder.Projects.cs | 4 ++-- .../VisualStudio/VisualStudioProjectGenerator.cs | 10 ++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/Builder.Projects.cs b/Source/Tools/Flax.Build/Build/Builder.Projects.cs index 52c6d4ac9..166e78036 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Projects.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Projects.cs @@ -497,7 +497,7 @@ namespace Flax.Build else if (dependencyModule.BinaryModuleName == "FlaxEngine") { // TODO: instead of this hack find a way to reference the prebuilt target bindings binary (example: game C# project references FlaxEngine C# prebuilt dll) - project.CSharp.FileReferences.Add(Path.Combine(Globals.EngineRoot, $"Binaries/Editor/{project.Configurations[0].PlatformName}/Development/FlaxEngine.CSharp.dll")); + project.CSharp.FileReferences.Add(Path.Combine(Globals.EngineRoot, Platform.GetEditorBinaryDirectory(), "Development/FlaxEngine.CSharp.dll")); } } } @@ -519,7 +519,7 @@ namespace Flax.Build project.Dependencies.Remove(flaxDependency); // TODO: instead of this hack find a way to reference the prebuilt target bindings binary (example: game C# project references FlaxEngine C# prebuilt dll) - project.CSharp.FileReferences.Add(Path.Combine(Globals.EngineRoot, $"Binaries/Editor/{project.Configurations[0].PlatformName}/Development/FlaxEngine.CSharp.dll")); + project.CSharp.FileReferences.Add(Path.Combine(Globals.EngineRoot, Platform.GetEditorBinaryDirectory(), "Development/FlaxEngine.CSharp.dll")); // Remove FlaxEngine from projects to prevent duplicated types errors in Intellisense (eg. Actor type defined in both FlaxEngine.CSharp.dll and FlaxEngine.csproj) flaxDependencyToRemove = flaxDependency; diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index 16ab543ad..8cbd6b01a 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -405,8 +405,6 @@ namespace Flax.Build.Projects.VisualStudio vcSolutionFileContent.AppendLine("EndProject"); } - var globalPlatformName = ""; - // Global configuration { vcSolutionFileContent.AppendLine("Global"); @@ -423,11 +421,7 @@ namespace Flax.Build.Projects.VisualStudio continue; foreach (var configuration in project.Configurations) - { - // We just grab the platform name from the first config - if (string.IsNullOrEmpty(globalPlatformName)) - globalPlatformName = configuration.PlatformName; - + { configurations.Add(new SolutionConfiguration(configuration)); } } @@ -564,7 +558,7 @@ namespace Flax.Build.Projects.VisualStudio { var profiles = new Dictionary(); var profile = new StringBuilder(); - var editorPath = Utilities.NormalizePath(Path.Combine(Globals.EngineRoot, $"Binaries/Editor/{globalPlatformName}/Development/FlaxEditor{Utilities.GetPlatformExecutableExt()}")); + var editorPath = Utilities.NormalizePath(Path.Combine(Globals.EngineRoot, Platform.GetEditorBinaryDirectory(), $"Development/FlaxEditor{Utilities.GetPlatformExecutableExt()}")); var workspacePath = Utilities.NormalizePath(solutionDirectory); foreach (var project in projects) { From abcfc8af35d45226623ab6050b88c20914e05cf1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 22 Sep 2023 12:04:41 +0200 Subject: [PATCH 098/188] Add using exact C# method profiler data for profiler events in overriden thunk method callbacks --- Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index da94a0056..4e5d5c158 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1452,7 +1452,8 @@ namespace Flax.Build.Bindings var useThunk = buildData.Platform.HasDynamicCodeExecutionSupport && Configuration.AOTMode == DotNetAOTModes.None; if (useThunk) { - contents.AppendLine($" PROFILE_CPU_NAMED(\"{classInfo.FullNameManaged}::{functionInfo.Name}\");"); + //contents.AppendLine($" PROFILE_CPU_NAMED(\"{classInfo.FullNameManaged}::{functionInfo.Name}\");"); + contents.AppendLine(" PROFILE_CPU_SRC_LOC(method->ProfilerData);"); // Convert parameters into managed format as boxed values var thunkParams = string.Empty; From 31b3f56ddb35830398b82d915430ff42d63b79f8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 22 Sep 2023 12:37:13 +0200 Subject: [PATCH 099/188] Fix visibility of some internal `NativeInterop` methods for game bindings --- Source/Engine/Engine/NativeInterop.cs | 53 ++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 8a8d543f1..dfef614b3 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -24,7 +24,8 @@ namespace FlaxEngine.Interop /// /// Provides a Mono-like API for native code to access managed runtime. /// - internal static unsafe partial class NativeInterop + [HideInEditor] + public static unsafe partial class NativeInterop { internal static Dictionary AssemblyLocations = new(); @@ -147,7 +148,13 @@ namespace FlaxEngine.Interop NativeMemory.AlignedFree(ptr); } - internal static T[] GCHandleArrayToManagedArray(ManagedArray ptrArray) where T : class + /// + /// Converts array of GC Handles from native runtime to managed array. + /// + /// Array element type. + /// Input array. + /// Output array. + public static T[] GCHandleArrayToManagedArray(ManagedArray ptrArray) where T : class { Span span = ptrArray.ToSpan(); T[] managedArray = new T[ptrArray.Length]; @@ -156,7 +163,12 @@ namespace FlaxEngine.Interop return managedArray; } - internal static IntPtr[] ManagedArrayToGCHandleArray(Array array) + /// + /// Converts managed array wrapper into array of GC Handles for native runtime. + /// + /// Input array. + /// Output array. + public static IntPtr[] ManagedArrayToGCHandleArray(Array array) { if (array.Length == 0) return Array.Empty(); @@ -170,13 +182,26 @@ namespace FlaxEngine.Interop return pointerArray; } - internal static ManagedArray ManagedArrayToGCHandleWrappedArray(Array array) + /// + /// Converts managed array wrapper into array of GC Handles for native runtime. + /// + /// Input array. + /// Output array. + public static ManagedArray ManagedArrayToGCHandleWrappedArray(Array array) { IntPtr[] pointerArray = ManagedArrayToGCHandleArray(array); return ManagedArray.WrapNewArray(pointerArray, array.GetType()); } - internal static TDst[] ConvertArray(Span src, Func convertFunc) + /// + /// Converts array with a custom converter function for each element. + /// + /// Input data type. + /// Output data type. + /// The input array. + /// Converter callback. + /// The output array. + public static TDst[] ConvertArray(Span src, Func convertFunc) { TDst[] dst = new TDst[src.Length]; for (int i = 0; i < src.Length; i++) @@ -184,7 +209,15 @@ namespace FlaxEngine.Interop return dst; } - internal static TDst[] ConvertArray(TSrc[] src, Func convertFunc) + /// + /// Converts array with a custom converter function for each element. + /// + /// Input data type. + /// Output data type. + /// The input array. + /// Converter callback. + /// The output array. + public static TDst[] ConvertArray(TSrc[] src, Func convertFunc) { TDst[] dst = new TDst[src.Length]; for (int i = 0; i < src.Length; i++) @@ -1024,11 +1057,12 @@ namespace FlaxEngine.Interop private static uint pinnedBoxedValuesPointer = 0; private static (IntPtr ptr, int size)[] pinnedAllocations = new (IntPtr ptr, int size)[256]; private static uint pinnedAllocationsPointer = 0; - + private delegate TInternal ToNativeDelegate(T value); + private delegate IntPtr UnboxerDelegate(object value, object converter); - private static ConcurrentDictionary unboxers = new (1, 3); + private static ConcurrentDictionary unboxers = new(1, 3); private static MethodInfo unboxerMethod = typeof(ValueTypeUnboxer).GetMethod(nameof(ValueTypeUnboxer.UnboxPointer), BindingFlags.Static | BindingFlags.NonPublic); private static MethodInfo unboxerToNativeMethod = typeof(ValueTypeUnboxer).GetMethod(nameof(ValueTypeUnboxer.UnboxPointerWithConverter), BindingFlags.Static | BindingFlags.NonPublic); @@ -1089,7 +1123,8 @@ namespace FlaxEngine.Interop return new IntPtr(Unsafe.AsPointer(ref Unsafe.Unbox(value))); } - private static IntPtr UnboxPointerWithConverter(object value, object converter) where T : struct where TInternal : struct + private static IntPtr UnboxPointerWithConverter(object value, object converter) where T : struct + where TInternal : struct { ToNativeDelegate toNative = Unsafe.As>(converter); return PinValue(toNative(Unsafe.Unbox(value))); From a0de513a43e8c68a86c771468367153c2282cee9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 22 Sep 2023 12:37:35 +0200 Subject: [PATCH 100/188] Optimize updating scripts tick arrays --- Source/Engine/Level/Scene/SceneTicking.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Level/Scene/SceneTicking.cpp b/Source/Engine/Level/Scene/SceneTicking.cpp index 235d7bbab..34de25a2e 100644 --- a/Source/Engine/Level/Scene/SceneTicking.cpp +++ b/Source/Engine/Level/Scene/SceneTicking.cpp @@ -32,7 +32,7 @@ void SceneTicking::TickData::RemoveTick(void* callee) { for (int32 i = 0; i < Ticks.Count(); i++) { - if (Ticks[i].Callee == callee) + if (Ticks.Get()[i].Callee == callee) { Ticks.RemoveAt(i); break; @@ -45,7 +45,7 @@ void SceneTicking::TickData::Tick() TickScripts(Scripts); for (int32 i = 0; i < Ticks.Count(); i++) - Ticks[i].Call(); + Ticks.Get()[i].Call(); } #if USE_EDITOR @@ -54,7 +54,7 @@ void SceneTicking::TickData::RemoveTickExecuteInEditor(void* callee) { for (int32 i = 0; i < TicksExecuteInEditor.Count(); i++) { - if (TicksExecuteInEditor[i].Callee == callee) + if (TicksExecuteInEditor.Get()[i].Callee == callee) { TicksExecuteInEditor.RemoveAt(i); break; @@ -67,7 +67,7 @@ void SceneTicking::TickData::TickExecuteInEditor() TickScripts(ScriptsExecuteInEditor); for (int32 i = 0; i < TicksExecuteInEditor.Count(); i++) - TicksExecuteInEditor[i].Call(); + TicksExecuteInEditor.Get()[i].Call(); } #endif From 62c2008cbcb1f395a4e3c20f19263187381b0caa Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 22 Sep 2023 12:50:50 +0200 Subject: [PATCH 101/188] Fix crash in C# `JobSystem.Dispatch` due to GC collecting Delegate object --- Source/Engine/Engine/NativeInterop.cs | 14 ++++++++++++++ .../Bindings/BindingsGenerator.CSharp.cs | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index dfef614b3..e0ef84a61 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -148,6 +148,20 @@ namespace FlaxEngine.Interop NativeMemory.AlignedFree(ptr); } + /// + /// Converts a delegate into a function pointer that is callable from unmanaged code via but cached delegate to prevent collecting it by GC. + /// + /// The type of delegate to convert. + /// The delegate to be passed to unmanaged code. + /// A value that can be passed to unmanaged code, which, in turn, can use it to call the underlying managed delegate. + public static IntPtr GetFunctionPointerForDelegate(TDelegate d) where TDelegate : notnull + { + // Example use-case: C# script runs actions via JobSystem.Dispatch which causes crash due to GC collecting Delegate object + ManagedHandle.Alloc(d, GCHandleType.Weak); + + return Marshal.GetFunctionPointerForDelegate(d); + } + /// /// Converts array of GC Handles from native runtime to managed array. /// diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 6a9aa7669..3685cb271 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -464,7 +464,7 @@ namespace Flax.Build.Bindings return "FlaxEngine.Object.GetUnmanagedPtr({0})"; case "Function": // delegate - return "Marshal.GetFunctionPointerForDelegate({0})"; + return "NativeInterop.GetFunctionPointerForDelegate({0})"; default: var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) From 787479357e14632661f704fabd1edea2ecb4c3b3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 22 Sep 2023 12:57:07 +0200 Subject: [PATCH 102/188] Codestyle fix #1495 --- .../Projects/VisualStudio/VisualStudioProjectGenerator.cs | 2 +- Source/Tools/Flax.Build/Utilities/Utilities.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index 8cbd6b01a..ecf70f9f8 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -421,7 +421,7 @@ namespace Flax.Build.Projects.VisualStudio continue; foreach (var configuration in project.Configurations) - { + { configurations.Add(new SolutionConfiguration(configuration)); } } diff --git a/Source/Tools/Flax.Build/Utilities/Utilities.cs b/Source/Tools/Flax.Build/Utilities/Utilities.cs index 994e11c8e..59e13aa73 100644 --- a/Source/Tools/Flax.Build/Utilities/Utilities.cs +++ b/Source/Tools/Flax.Build/Utilities/Utilities.cs @@ -754,9 +754,9 @@ namespace Flax.Build public static string GetPlatformExecutableExt() { var extEnding = ".exe"; - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - extEnding = ""; + extEnding = ""; } return extEnding; From d0679c1f9bd6c7c1dfef3ac4808f5e9e3cfc025a Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Fri, 22 Sep 2023 15:28:46 +0200 Subject: [PATCH 103/188] - Made Visject connection bezier curves bend around nodes --- Source/Editor/Surface/Elements/OutputBox.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Surface/Elements/OutputBox.cs b/Source/Editor/Surface/Elements/OutputBox.cs index 67b6e8409..2a2b87486 100644 --- a/Source/Editor/Surface/Elements/OutputBox.cs +++ b/Source/Editor/Surface/Elements/OutputBox.cs @@ -33,11 +33,20 @@ namespace FlaxEditor.Surface.Elements /// The connection thickness. public static void DrawConnection(ref Float2 start, ref Float2 end, ref Color color, float thickness = 1) { + // Control points parameters + const float minControlLength = 100f; + const float maxControlLength = 150f; + var dst = (end - start).Length; + var yDst = Mathf.Abs(start.Y - end.Y); + // Calculate control points - var dst = (end - start) * new Float2(0.5f, 0.05f); - var control1 = new Float2(start.X + dst.X, start.Y + dst.Y); - var control2 = new Float2(end.X - dst.X, end.Y + dst.Y); - + var minControlDst = dst * 0.5f; + var maxControlDst = Mathf.Max(Mathf.Min(maxControlLength, dst), minControlLength); + var controlDst = Mathf.Lerp(minControlDst, maxControlDst, Mathf.Clamp(yDst / minControlLength, 0f, 1f)); + + var control1 = new Float2(start.X + controlDst, start.Y); + var control2 = new Float2(end.X - controlDst, end.Y); + // Draw line Render2D.DrawBezier(start, control1, control2, end, color, thickness); From bd864c42fae9ae227721cf4a4a3cdc3847227711 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 22 Sep 2023 08:40:38 -0500 Subject: [PATCH 104/188] Add popup to editor options to remind the user to save or not. --- Source/Editor/Windows/EditorOptionsWindow.cs | 31 ++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Source/Editor/Windows/EditorOptionsWindow.cs b/Source/Editor/Windows/EditorOptionsWindow.cs index 2942f98c0..d53d4f632 100644 --- a/Source/Editor/Windows/EditorOptionsWindow.cs +++ b/Source/Editor/Windows/EditorOptionsWindow.cs @@ -216,5 +216,36 @@ namespace FlaxEditor.Windows base.OnDestroy(); } + + /// + protected override bool OnClosing(ClosingReason reason) + { + // Block closing only on user events + if (reason == ClosingReason.User) + { + // Check if asset has been edited and not saved (and still has linked item) + if (_isDataDirty && _options != null) + { + // Ask user for further action + var result = MessageBox.Show( + "Editor options have been edited. Save before closing?", + "Save before closing?", + MessageBoxButtons.YesNoCancel + ); + if (result == DialogResult.OK || result == DialogResult.Yes) + { + // Save and close + SaveData(); + } + else if (result == DialogResult.Cancel || result == DialogResult.Abort) + { + // Cancel closing + return true; + } + } + } + + return base.OnClosing(reason); + } } } From 08d04ea6fb5cc4d231926bc60afb065467936156 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 22 Sep 2023 16:54:23 +0300 Subject: [PATCH 105/188] Fix exceptions in Debug Log Window not using correct line numbers Additions to fix in commit 1714bc243a28740a0521ef922d8c7cc4102695b2 --- Source/Editor/Windows/DebugLogWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs index 09a93d300..24e38d9af 100644 --- a/Source/Editor/Windows/DebugLogWindow.cs +++ b/Source/Editor/Windows/DebugLogWindow.cs @@ -573,7 +573,7 @@ namespace FlaxEditor.Windows if (match.Success) { desc.LocationFile = match.Groups[2].Value; - int.TryParse(match.Groups[3].Value, out desc.LocationLine); + int.TryParse(match.Groups[4].Value, out desc.LocationLine); } } From 15859134a3fb59c14fda1d17f0d80aa1eee04be0 Mon Sep 17 00:00:00 2001 From: Meh Date: Fri, 22 Sep 2023 17:33:17 +0200 Subject: [PATCH 106/188] Refactor splash screen quotes Removed bad and weird quotes, added nice ones. --- Source/Editor/Windows/SplashScreen.cpp | 37 +++++++------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index 7cf7a7f78..c682210cf 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -18,34 +18,30 @@ const Char* SplashScreenQuotes[] = TEXT("Loading"), TEXT("Unloading"), TEXT("Reloading"), - TEXT("Reloading gun"), + TEXT("Downloading more RAM"), TEXT("Consuming your RAM"), TEXT("Burning your CPU"), - TEXT("#BetterThanUnity"), TEXT("Rendering buttons"), TEXT("Collecting crash data"), - TEXT("Downloading porn"), #if PLATFORM_WINDOWS - TEXT("Removing 'C:\\Windows\\'"), + TEXT("We're getting everything ready for you."), #elif PLATFORM_LINUX - TEXT("Time to switch to Windows?"), - TEXT("Installing Windows 10"), + TEXT("don't compare Macbooks to oranges."), + TEXT("trying to exit vim"), + TEXT("sudo flax --loadproject"), #elif PLATFORM_MAC - TEXT("Hacking your iPhone"), + TEXT("don't compare Macbooks to oranges."), TEXT("Why does macbook heat up?\nBecause it doesn't have windows"), #endif - TEXT("Kappa!"), TEXT("How you doin'?"), TEXT("Why so serious?"), TEXT("Bond. James Bond."), TEXT("To infinity and beyond!"), TEXT("Houston, we have a problem"), - TEXT("NotImplementedEngineException"), TEXT("Made in Poland"), TEXT("We like you"), TEXT("Compiling the compiler"), TEXT("Flax it up!"), - TEXT("Fun fact: Fortnite runs on Flax"), TEXT("Toss a coin to your Witcher!!!"), TEXT("Holy Moly!"), TEXT("Just Read the Instructions"), @@ -60,7 +56,6 @@ const Char* SplashScreenQuotes[] = TEXT("They see me loadin'"), TEXT("Loadin' loadin' and loadin' loadin'"), TEXT("Procedurally generating buttons"), - TEXT("Out of Memory Exception!"), TEXT("Running Big Bang simulation"), TEXT("Calculating infinity"), TEXT("Dividing infinity by zero"), @@ -70,10 +65,7 @@ const Char* SplashScreenQuotes[] = TEXT("Whatever you do, do it well.\n~Walt Disney"), TEXT("Here's Johnny!"), TEXT("Did you see that? No... I don't think so"), - TEXT("Collecting unreal power"), TEXT("Stay safe, friend"), - TEXT("trolololololololololololo"), - TEXT("xD"), TEXT("Come to the dark side"), TEXT("Flax Facts: This is a loading screen"), TEXT("Don't Stop Me Now"), @@ -87,7 +79,6 @@ const Char* SplashScreenQuotes[] = TEXT("A martini. Shaken, not stirred"), TEXT("Hasta la vista, baby"), TEXT("Winter is coming"), - TEXT("You know nothing, Jon Snow"), TEXT("Create something awesome!"), TEXT("Well Polished Engine"), TEXT("Error 404: Joke Not Found"), @@ -96,45 +87,37 @@ const Char* SplashScreenQuotes[] = TEXT("Loading Simulation"), TEXT("Get ready for a surprise!"), TEXT("Coffee is my fuel"), - TEXT("Installing a free copy of Cyberpunk 2077"), TEXT("With great power comes great electricity bill"), TEXT("Flax was made in the same city as Witcher 3"), TEXT("So JavaScript is a scripting version of Java"), TEXT("Good things take time.\n~Someone"), - TEXT("Get shit done"), TEXT("Hold Tight! Loading Flax"), TEXT("That's one small step for a man,\none giant leap for mankind"), TEXT("Remember to save your work frequently"), TEXT("In case of fire:\ngit commit, git push, leave building"), TEXT("Keep calm and make games"), TEXT("You're breathtaking!!!"), - TEXT("Do regular dogs see police dogs & think,\nOh no it's a cop?"), - TEXT("Dear Santa,\nDefine naughty."), TEXT("Blah, blah"), TEXT("My PRECIOUS!!!!"), TEXT("YOU SHALL NOT PASS!"), TEXT("You have my bow.\nAnd my axe!"), TEXT("To the bridge of Khazad-dum."), - TEXT("One ring to rule them all.\nOne ring to find them."), - TEXT("Ladies and gentelman, we got him"), - TEXT("Cyberpunk of game engines"), - TEXT("That's what she said"), TEXT("Compiling Shaders (93,788)"), TEXT("Hello There"), TEXT("BAGUETTE"), - TEXT("All we had to do was follow the damn train, CJ"), TEXT("Here we go again"), TEXT("@everyone"), TEXT("Potato"), TEXT("Python is a programming snek"), TEXT("Flax will start when pigs will fly"), - TEXT("I'm the android sent by CyberLife"), - TEXT("Fancy-ass ray tracing, rtx on, lighting"), TEXT("ZOINKS"), TEXT("Scooby dooby doo"), TEXT("You shall not load!"), TEXT("The roof, the roof, the roof is on fire!"), - TEXT("I've seen better documentation...\nFrom ransomware gangs!"), + TEXT("Slava Ukraini!"), + TEXT("RTX off... for now!"), + TEXT("Increasing Fiber count"), + TEXT("Now this is podracing!"), TEXT("Slava Ukraini!"), }; From b82436cf4e6f9dd3b9c3abd08c9db0890eb3bd51 Mon Sep 17 00:00:00 2001 From: Meh Date: Fri, 22 Sep 2023 17:57:19 +0200 Subject: [PATCH 107/188] Refactor splash screen quotes removed bad ones and added more nice quotes. --- Source/Editor/Windows/SplashScreen.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index c682210cf..f26e45e81 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -26,12 +26,13 @@ const Char* SplashScreenQuotes[] = #if PLATFORM_WINDOWS TEXT("We're getting everything ready for you."), #elif PLATFORM_LINUX - TEXT("don't compare Macbooks to oranges."), - TEXT("trying to exit vim"), - TEXT("sudo flax --loadproject"), + TEXT("Don't compare Macbooks to oranges."), + TEXT("Trying to exit vim"), + TEXT("Sudo flax --loadproject"), #elif PLATFORM_MAC TEXT("don't compare Macbooks to oranges."), TEXT("Why does macbook heat up?\nBecause it doesn't have windows"), + TEXT("Starting Direc... um, Vulkan renderer."), #endif TEXT("How you doin'?"), TEXT("Why so serious?"), @@ -73,7 +74,6 @@ const Char* SplashScreenQuotes[] = TEXT("Made with Flax"), TEXT("This is the way"), TEXT("The quick brown fox jumps over the lazy dog"), - TEXT("Hit The Road Jack"), TEXT("You have 7 lives left"), TEXT("May the Force be with you"), TEXT("A martini. Shaken, not stirred"), @@ -84,7 +84,7 @@ const Char* SplashScreenQuotes[] = TEXT("Error 404: Joke Not Found"), TEXT("Rushing B"), TEXT("Putting pineapple on pizza"), - TEXT("Loading Simulation"), + TEXT("Entering the Matrix"), TEXT("Get ready for a surprise!"), TEXT("Coffee is my fuel"), TEXT("With great power comes great electricity bill"), @@ -102,7 +102,8 @@ const Char* SplashScreenQuotes[] = TEXT("YOU SHALL NOT PASS!"), TEXT("You have my bow.\nAnd my axe!"), TEXT("To the bridge of Khazad-dum."), - TEXT("Compiling Shaders (93,788)"), + TEXT("That's what she said"), + TEXT("We could be compiling shaders here"), TEXT("Hello There"), TEXT("BAGUETTE"), TEXT("Here we go again"), @@ -119,6 +120,9 @@ const Char* SplashScreenQuotes[] = TEXT("Increasing Fiber count"), TEXT("Now this is podracing!"), TEXT("Slava Ukraini!"), + TEXT("Weird flax, but ok"), + TEXT("Reticulating Splines"), + TEXT("Discombobulating"), }; SplashScreen::~SplashScreen() From 7b051ce827bf1169625c683685158c4c2c300dde Mon Sep 17 00:00:00 2001 From: Meh Date: Fri, 22 Sep 2023 18:07:42 +0200 Subject: [PATCH 108/188] Update SplashScreen.cpp --- Source/Editor/Windows/SplashScreen.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index f26e45e81..e986257f6 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -119,7 +119,6 @@ const Char* SplashScreenQuotes[] = TEXT("RTX off... for now!"), TEXT("Increasing Fiber count"), TEXT("Now this is podracing!"), - TEXT("Slava Ukraini!"), TEXT("Weird flax, but ok"), TEXT("Reticulating Splines"), TEXT("Discombobulating"), From 45231a8b04ec1eadca008122261e290c63de8a8a Mon Sep 17 00:00:00 2001 From: Meh Date: Fri, 22 Sep 2023 18:13:31 +0200 Subject: [PATCH 109/188] Update SplashScreen.cpp --- Source/Editor/Windows/SplashScreen.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index e986257f6..a2d180a63 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -26,7 +26,6 @@ const Char* SplashScreenQuotes[] = #if PLATFORM_WINDOWS TEXT("We're getting everything ready for you."), #elif PLATFORM_LINUX - TEXT("Don't compare Macbooks to oranges."), TEXT("Trying to exit vim"), TEXT("Sudo flax --loadproject"), #elif PLATFORM_MAC From 25708e2875377a743936404c2428a3d767191704 Mon Sep 17 00:00:00 2001 From: Meh Date: Fri, 22 Sep 2023 19:01:37 +0200 Subject: [PATCH 110/188] add more quotes --- Source/Editor/Windows/SplashScreen.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index a2d180a63..a2e299347 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -26,6 +26,7 @@ const Char* SplashScreenQuotes[] = #if PLATFORM_WINDOWS TEXT("We're getting everything ready for you."), #elif PLATFORM_LINUX + TEXT("Try it on a Raspberry"), TEXT("Trying to exit vim"), TEXT("Sudo flax --loadproject"), #elif PLATFORM_MAC @@ -101,6 +102,7 @@ const Char* SplashScreenQuotes[] = TEXT("YOU SHALL NOT PASS!"), TEXT("You have my bow.\nAnd my axe!"), TEXT("To the bridge of Khazad-dum."), + TEXT("One ring to rule them all.\nOne ring to find them."), TEXT("That's what she said"), TEXT("We could be compiling shaders here"), TEXT("Hello There"), @@ -121,6 +123,9 @@ const Char* SplashScreenQuotes[] = TEXT("Weird flax, but ok"), TEXT("Reticulating Splines"), TEXT("Discombobulating"), + TEXT("Who is signing all these integers?!"), + TEXT("Flax fact: Flax was called Celelej once."), + TEXT("Changing text overflow setti-"), }; SplashScreen::~SplashScreen() From df23174debbbaffd9a3312dc8bf788711b17078b Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Fri, 22 Sep 2023 19:23:16 +0200 Subject: [PATCH 111/188] - Added proper control backspace support --- Source/Engine/UI/GUI/Common/TextBoxBase.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs index a4ccbe5d2..60b201986 100644 --- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs @@ -11,6 +11,11 @@ namespace FlaxEngine.GUI /// public abstract class TextBoxBase : ContainerControl { + /// + /// The delete control character (used for text filtering). + /// + protected const char DelChar = (char)0x7F; + /// /// The text separators (used for words skipping). /// @@ -351,6 +356,10 @@ namespace FlaxEngine.GUI if (value.IndexOf('\r') != -1) value = value.Replace("\r", ""); + // Filter text (handle backspace control character) + if(value.IndexOf(DelChar) != -1) + value = value.Replace(DelChar.ToString(), ""); + // Clamp length if (value.Length > MaxLength) value = value.Substring(0, MaxLength); @@ -673,6 +682,8 @@ namespace FlaxEngine.GUI // Filter text if (str.IndexOf('\r') != -1) str = str.Replace("\r", ""); + if (str.IndexOf(DelChar) != -1) + str = str.Replace(DelChar.ToString(), ""); if (!IsMultiline && str.IndexOf('\n') != -1) str = str.Replace("\n", ""); @@ -1327,6 +1338,15 @@ namespace FlaxEngine.GUI if (IsReadOnly) return true; + if (ctrDown) + { + int prevWordBegin = FindPrevWordBegin(); + _text = _text.Remove(prevWordBegin, CaretPosition-prevWordBegin); + SetSelection(prevWordBegin); + OnTextChanged(); + return true; + } + int left = SelectionLeft; if (HasSelection) { From d11e3b5bd4d4c2678c6c02e362cd6c6d238c6aa5 Mon Sep 17 00:00:00 2001 From: Meh Date: Fri, 22 Sep 2023 19:25:16 +0200 Subject: [PATCH 112/188] Update SplashScreen.cpp --- Source/Editor/Windows/SplashScreen.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index a2e299347..d2371a540 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -34,6 +34,7 @@ const Char* SplashScreenQuotes[] = TEXT("Why does macbook heat up?\nBecause it doesn't have windows"), TEXT("Starting Direc... um, Vulkan renderer."), #endif + TEXT("Kappa!"), TEXT("How you doin'?"), TEXT("Why so serious?"), TEXT("Bond. James Bond."), From 248304a78f3ded68bc05e2cc0b488e02619f54a5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 22 Sep 2023 19:43:00 +0200 Subject: [PATCH 113/188] Fix regression of importing only a single material for models with split objects enabled #1483 --- .../Tools/ModelTool/ModelTool.Assimp.cpp | 7 ++++++- .../Tools/ModelTool/ModelTool.OpenFBX.cpp | 18 ++++++++++++++++-- Source/Engine/Tools/ModelTool/ModelTool.cpp | 4 ---- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index ab9b6b606..7eff75514 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -566,12 +566,17 @@ bool ImportMaterials(ImportedModelData& result, AssimpImporterData& data, String return false; } +bool IsMeshInvalid(const aiMesh* aMesh) +{ + return aMesh->mPrimitiveTypes != aiPrimitiveType_TRIANGLE || aMesh->mNumVertices == 0 || aMesh->mNumFaces == 0 || aMesh->mFaces[0].mNumIndices != 3; +} + bool ImportMesh(int32 i, ImportedModelData& result, AssimpImporterData& data, String& errorMsg) { const auto aMesh = data.Scene->mMeshes[i]; // Skip invalid meshes - if (aMesh->mPrimitiveTypes != aiPrimitiveType_TRIANGLE || aMesh->mNumVertices == 0 || aMesh->mNumFaces == 0 || aMesh->mFaces[0].mNumIndices != 3) + if (IsMeshInvalid(aMesh)) return false; // Skip unused meshes diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index ab55df7f6..215eadaba 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -923,8 +923,6 @@ bool ImportMesh(int32 index, ImportedModelData& result, OpenFbxImporterData& dat const auto aMesh = data.Scene->getMesh(index); const auto aGeometry = aMesh->getGeometry(); const auto trianglesCount = aGeometry->getVertexCount() / 3; - - // Skip invalid meshes if (IsMeshInvalid(aMesh)) return false; @@ -1272,6 +1270,22 @@ bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, Opt const auto meshIndex = Math::Clamp(options.ObjectIndex, 0, meshCount - 1); if (ImportMesh(meshIndex, data, *context, errorMsg)) return true; + + // Let the firstly imported mesh import all materials from all meshes (index 0 is importing all following ones before itself during splitting - see code above) + if (options.ObjectIndex == 1) + { + for (int32 i = 0; i < meshCount; i++) + { + const auto aMesh = context->Scene->getMesh(i); + if (i == 1 || IsMeshInvalid(aMesh)) + continue; + for (int32 j = 0; j < aMesh->getMaterialCount(); j++) + { + const ofbx::Material* aMaterial = aMesh->getMaterial(j); + context->AddMaterial(data, aMaterial); + } + } + } } else { diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 99873c016..15c7cbc32 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -910,10 +910,6 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op { auto& texture = data.Textures[i]; - // When splitting imported meshes allow only the first mesh to import assets (mesh[0] is imported after all following ones so import assets during mesh[1]) - if (!options.SplitObjects && options.ObjectIndex != 1 && options.ObjectIndex != -1) - continue; - // Auto-import textures if (autoImportOutput.IsEmpty() || (data.Types & ImportDataTypes::Textures) == ImportDataTypes::None || texture.FilePath.IsEmpty()) continue; From 963300c2cbe2f4906e4ea5f1c37ed1a30c74e23b Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Sat, 23 Sep 2023 11:56:45 +0200 Subject: [PATCH 114/188] - Implemented shift tab support for Container Controls --- Source/Editor/GUI/Dialogs/Dialog.cs | 6 ++++- Source/Editor/Windows/EditorWindow.cs | 3 ++- Source/Engine/UI/GUI/ContainerControl.cs | 28 +++++++++++++++++++----- Source/Engine/UI/GUI/Control.cs | 1 + Source/Engine/UI/GUI/Enums.cs | 5 +++++ 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/Source/Editor/GUI/Dialogs/Dialog.cs b/Source/Editor/GUI/Dialogs/Dialog.cs index 8910cfe49..c7d565f9c 100644 --- a/Source/Editor/GUI/Dialogs/Dialog.cs +++ b/Source/Editor/GUI/Dialogs/Dialog.cs @@ -290,7 +290,11 @@ namespace FlaxEditor.GUI.Dialogs OnCancel(); return true; case KeyboardKeys.Tab: - Root?.Navigate(NavDirection.Next); + if (Root != null) + { + bool shiftDown = Root.GetKey(KeyboardKeys.Shift); + Root?.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next); + } return true; } return false; diff --git a/Source/Editor/Windows/EditorWindow.cs b/Source/Editor/Windows/EditorWindow.cs index 1ff0363f1..60817a51b 100644 --- a/Source/Editor/Windows/EditorWindow.cs +++ b/Source/Editor/Windows/EditorWindow.cs @@ -207,7 +207,8 @@ namespace FlaxEditor.Windows case KeyboardKeys.Tab: if (CanUseNavigation && Root != null) { - Root.Navigate(NavDirection.Next); + bool shiftDown = Root.GetKey(KeyboardKeys.Shift); + Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next); return true; } break; diff --git a/Source/Engine/UI/GUI/ContainerControl.cs b/Source/Engine/UI/GUI/ContainerControl.cs index 2de96d0b3..d8d5c6e5f 100644 --- a/Source/Engine/UI/GUI/ContainerControl.cs +++ b/Source/Engine/UI/GUI/ContainerControl.cs @@ -507,15 +507,19 @@ namespace FlaxEngine.GUI // Perform automatic navigation based on the layout var result = NavigationRaycast(direction, location, visited); - if (result == null && direction == NavDirection.Next) + var rightMostLocation = location; + if (result == null && (direction == NavDirection.Next || direction == NavDirection.Previous)) { // Try wrap the navigation over the layout based on the direction var visitedWrap = new List(visited); - result = NavigationWrap(direction, location, visitedWrap); + result = NavigationWrap(direction, location, visitedWrap, out rightMostLocation); } if (result != null) { - result = result.OnNavigate(direction, result.PointFromParent(location), this, visited); + // HACK: only the 'previous' direction needs the rightMostLocation so i used a ternary conditional operator. + // The rightMostLocation can probably become a 'desired raycast origin' that gets calculated correctly in the NavigationWrap method. + var useLocation = direction == NavDirection.Previous ? rightMostLocation : location; + result = result.OnNavigate(direction, result.PointFromParent(useLocation), this, visited); if (result != null) return result; } @@ -551,8 +555,9 @@ namespace FlaxEngine.GUI /// The navigation direction. /// The navigation start location (in the control-space). /// The list with visited controls. Used to skip recursive navigation calls when doing traversal across the UI hierarchy. + /// Returns the rightmost location of the parent container for the raycast used by the child container /// The target navigation control or null if didn't performed any navigation. - protected virtual Control NavigationWrap(NavDirection direction, Float2 location, List visited) + protected virtual Control NavigationWrap(NavDirection direction, Float2 location, List visited, out Float2 rightMostLocation) { // This searches form a child that calls this navigation event (see Control.OnNavigate) to determinate the layout wrapping size based on that child size var currentChild = RootWindow?.FocusedControl; @@ -566,15 +571,22 @@ namespace FlaxEngine.GUI case NavDirection.Next: predictedLocation = new Float2(0, location.Y + layoutSize.Y); break; + case NavDirection.Previous: + predictedLocation = new Float2(Size.X, location.Y - layoutSize.Y); + break; } if (new Rectangle(Float2.Zero, Size).Contains(ref predictedLocation)) { var result = NavigationRaycast(direction, predictedLocation, visited); if (result != null) - return result; + { + rightMostLocation = predictedLocation; + return result; + } } } - return Parent?.NavigationWrap(direction, PointToParent(ref location), visited); + rightMostLocation = location; + return Parent?.NavigationWrap(direction, PointToParent(ref location), visited, out rightMostLocation); } private static bool CanGetAutoFocus(Control c) @@ -613,6 +625,10 @@ namespace FlaxEngine.GUI uiDir1 = new Float2(1, 0); uiDir2 = new Float2(0, 1); break; + case NavDirection.Previous: + uiDir1 = new Float2(-1, 0); + uiDir2 = new Float2(0, -1); + break; } Control result = null; var minDistance = float.MaxValue; diff --git a/Source/Engine/UI/GUI/Control.cs b/Source/Engine/UI/GUI/Control.cs index 3bc2610a4..747d407e9 100644 --- a/Source/Engine/UI/GUI/Control.cs +++ b/Source/Engine/UI/GUI/Control.cs @@ -634,6 +634,7 @@ namespace FlaxEngine.GUI case NavDirection.Left: return new Float2(0, size.Y * 0.5f); case NavDirection.Right: return new Float2(size.X, size.Y * 0.5f); case NavDirection.Next: return Float2.Zero; + case NavDirection.Previous: return new Float2(Size.X, Size.Y); default: return size * 0.5f; } } diff --git a/Source/Engine/UI/GUI/Enums.cs b/Source/Engine/UI/GUI/Enums.cs index d0b4fb61c..9672dcb9b 100644 --- a/Source/Engine/UI/GUI/Enums.cs +++ b/Source/Engine/UI/GUI/Enums.cs @@ -202,5 +202,10 @@ namespace FlaxEngine.GUI /// The next item (right with layout wrapping). /// Next, + + /// + /// The previous item (left with layout wrapping). + /// + Previous, } } From 642766d9cc1bcf1395549b0434c9f64c4863847d Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 26 Aug 2023 12:19:37 +0300 Subject: [PATCH 115/188] Add Editor input options for Play Current Scenes and running cooked game --- Source/Editor/GUI/ContextMenu/ContextMenu.cs | 19 +++++++++ Source/Editor/Modules/UIModule.cs | 44 ++++++++++++-------- Source/Editor/Options/InputOptions.cs | 18 ++++++-- Source/Editor/Windows/SceneEditorWindow.cs | 3 ++ 4 files changed, 63 insertions(+), 21 deletions(-) diff --git a/Source/Editor/GUI/ContextMenu/ContextMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenu.cs index f2e5d30e3..25f45a1f8 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenu.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenu.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using FlaxEditor.Options; using FlaxEngine; using FlaxEngine.GUI; @@ -269,6 +270,24 @@ namespace FlaxEditor.GUI.ContextMenu return item; } + /// + /// Adds the button. + /// + /// The text. + /// The input binding. + /// On button clicked event. + /// Created context menu item control. + public ContextMenuButton AddButton(string text, InputBinding binding, Action clicked) + { + var item = new ContextMenuButton(this, text, binding.ToString()) + { + Parent = _panel + }; + item.Clicked += clicked; + SortButtons(); + return item; + } + /// /// Gets the child menu (with that name). /// diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 08ec09de8..9130797bf 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -62,6 +62,8 @@ namespace FlaxEditor.Modules private ContextMenuButton _menuGamePlayCurrentScenes; private ContextMenuButton _menuGameStop; private ContextMenuButton _menuGamePause; + private ContextMenuButton _menuGameCookAndRun; + private ContextMenuButton _menuGameRunCookedGame; private ContextMenuButton _menuToolsBuildScenes; private ContextMenuButton _menuToolsBakeLightmaps; private ContextMenuButton _menuToolsClearLightmaps; @@ -510,7 +512,7 @@ namespace FlaxEditor.Modules MenuFile = MainMenu.AddButton("File"); var cm = MenuFile.ContextMenu; cm.VisibleChanged += OnMenuFileShowHide; - _menuSaveAll = cm.AddButton("Save All", inputOptions.Save.ToString(), Editor.SaveAll); + _menuSaveAll = cm.AddButton("Save All", inputOptions.Save, Editor.SaveAll); _menuFileSaveScenes = cm.AddButton("Save scenes", Editor.Scene.SaveScenes); _menuFileCloseScenes = cm.AddButton("Close scenes", Editor.Scene.CloseAllScenes); cm.AddSeparator(); @@ -526,18 +528,18 @@ namespace FlaxEditor.Modules MenuEdit = MainMenu.AddButton("Edit"); cm = MenuEdit.ContextMenu; cm.VisibleChanged += OnMenuEditShowHide; - _menuEditUndo = cm.AddButton(string.Empty, inputOptions.Undo.ToString(), Editor.PerformUndo); - _menuEditRedo = cm.AddButton(string.Empty, inputOptions.Redo.ToString(), Editor.PerformRedo); + _menuEditUndo = cm.AddButton(string.Empty, inputOptions.Undo, Editor.PerformUndo); + _menuEditRedo = cm.AddButton(string.Empty, inputOptions.Redo, Editor.PerformRedo); cm.AddSeparator(); - _menuEditCut = cm.AddButton("Cut", inputOptions.Cut.ToString(), Editor.SceneEditing.Cut); - _menuEditCopy = cm.AddButton("Copy", inputOptions.Copy.ToString(), Editor.SceneEditing.Copy); - _menuEditPaste = cm.AddButton("Paste", inputOptions.Paste.ToString(), Editor.SceneEditing.Paste); + _menuEditCut = cm.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut); + _menuEditCopy = cm.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy); + _menuEditPaste = cm.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste); cm.AddSeparator(); - _menuEditDelete = cm.AddButton("Delete", inputOptions.Delete.ToString(), Editor.SceneEditing.Delete); - _menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate.ToString(), Editor.SceneEditing.Duplicate); + _menuEditDelete = cm.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete); + _menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate); cm.AddSeparator(); - _menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll.ToString(), Editor.SceneEditing.SelectAllScenes); - _menuEditFind = cm.AddButton("Find", inputOptions.Search.ToString(), Editor.Windows.SceneWin.Search); + _menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes); + _menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search); // Scene MenuScene = MainMenu.AddButton("Scene"); @@ -555,18 +557,20 @@ namespace FlaxEditor.Modules cm = MenuGame.ContextMenu; cm.VisibleChanged += OnMenuGameShowHide; - _menuGamePlayGame = cm.AddButton("Play Game", Editor.Simulation.RequestPlayGameOrStopPlay); - _menuGamePlayCurrentScenes = cm.AddButton("Play Current Scenes", Editor.Simulation.RequestPlayScenesOrStopPlay); - _menuGameStop = cm.AddButton("Stop Game", Editor.Simulation.RequestStopPlay); - _menuGamePause = cm.AddButton("Pause", inputOptions.Pause.ToString(), Editor.Simulation.RequestPausePlay); + _menuGamePlayGame = cm.AddButton("Play Game", inputOptions.Play, Editor.Simulation.RequestPlayGameOrStopPlay); + _menuGamePlayCurrentScenes = cm.AddButton("Play Current Scenes", inputOptions.PlayCurrentScenes, Editor.Simulation.RequestPlayScenesOrStopPlay); + _menuGameStop = cm.AddButton("Stop Game", inputOptions.Play, Editor.Simulation.RequestStopPlay); + _menuGamePause = cm.AddButton("Pause", inputOptions.Pause, Editor.Simulation.RequestPausePlay); cm.AddSeparator(); var numberOfClientsMenu = cm.AddChildMenu("Number of game clients"); _numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu); cm.AddSeparator(); - cm.AddButton("Cook & Run", Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after."); - cm.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked).LinkTooltip("Runs the game build from the last cooking output. Use Cook&Play or Game Cooker first."); + _menuGameCookAndRun = cm.AddButton("Cook & Run", inputOptions.CookAndRun, Editor.Windows.GameCookerWin.BuildAndRun); + _menuGameCookAndRun.LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after."); + _menuGameRunCookedGame = cm.AddButton("Run cooked game", inputOptions.RunCookedGame, Editor.Windows.GameCookerWin.RunCooked); + _menuGameRunCookedGame.LinkTooltip("Runs the game build from the last cooking output. Use 'Cook & Run' or Game Cooker first."); // Tools MenuTools = MainMenu.AddButton("Tools"); @@ -642,8 +646,12 @@ namespace FlaxEditor.Modules _menuEditDuplicate.ShortKeys = inputOptions.Duplicate.ToString(); _menuEditSelectAll.ShortKeys = inputOptions.SelectAll.ToString(); _menuEditFind.ShortKeys = inputOptions.Search.ToString(); - _menuGamePlayCurrentScenes.ShortKeys = inputOptions.Play.ToString(); + _menuGamePlayGame.ShortKeys = inputOptions.Play.ToString(); + _menuGamePlayCurrentScenes.ShortKeys = inputOptions.PlayCurrentScenes.ToString(); _menuGamePause.ShortKeys = inputOptions.Pause.ToString(); + _menuGameStop.ShortKeys = inputOptions.Play.ToString(); + _menuGameCookAndRun.ShortKeys = inputOptions.CookAndRun.ToString(); + _menuGameRunCookedGame.ShortKeys = inputOptions.RunCookedGame.ToString(); MainMenuShortcutKeysUpdated?.Invoke(); } @@ -692,7 +700,7 @@ namespace FlaxEditor.Modules playActionGroup.SelectedChanged = SetPlayAction; Editor.Options.OptionsChanged += options => { playActionGroup.Selected = options.Interface.PlayButtonAction; }; - _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game({inputOptions.Pause})"); + _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game ({inputOptions.Pause})"); _toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game"); UpdateToolstrip(); diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index e0a51622b..9285532a1 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -85,17 +85,29 @@ namespace FlaxEditor.Options public InputBinding SnapToGround = new InputBinding(KeyboardKeys.End); [DefaultValue(typeof(InputBinding), "F5")] - [EditorDisplay("Scene"), EditorOrder(510)] + [EditorDisplay("Scene", "Play/Stop"), EditorOrder(510)] public InputBinding Play = new InputBinding(KeyboardKeys.F5); + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Play Current Scenes/Stop"), EditorOrder(520)] + public InputBinding PlayCurrentScenes = new InputBinding(KeyboardKeys.None); + [DefaultValue(typeof(InputBinding), "F6")] - [EditorDisplay("Scene"), EditorOrder(520)] + [EditorDisplay("Scene"), EditorOrder(530)] public InputBinding Pause = new InputBinding(KeyboardKeys.F6); [DefaultValue(typeof(InputBinding), "F11")] - [EditorDisplay("Scene"), EditorOrder(530)] + [EditorDisplay("Scene"), EditorOrder(540)] public InputBinding StepFrame = new InputBinding(KeyboardKeys.F11); + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Cook & Run"), EditorOrder(550)] + public InputBinding CookAndRun = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Run cooked game"), EditorOrder(560)] + public InputBinding RunCookedGame = new InputBinding(KeyboardKeys.None); + #endregion #region Debugger diff --git a/Source/Editor/Windows/SceneEditorWindow.cs b/Source/Editor/Windows/SceneEditorWindow.cs index 72ff3be33..290f02089 100644 --- a/Source/Editor/Windows/SceneEditorWindow.cs +++ b/Source/Editor/Windows/SceneEditorWindow.cs @@ -39,8 +39,11 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.Delete, Editor.SceneEditing.Delete); InputActions.Add(options => options.Search, () => Editor.Windows.SceneWin.Search()); InputActions.Add(options => options.Play, Editor.Simulation.DelegatePlayOrStopPlayInEditor); + InputActions.Add(options => options.PlayCurrentScenes, Editor.Simulation.RequestPlayScenesOrStopPlay); InputActions.Add(options => options.Pause, Editor.Simulation.RequestResumeOrPause); InputActions.Add(options => options.StepFrame, Editor.Simulation.RequestPlayOneFrame); + InputActions.Add(options => options.CookAndRun, () => Editor.Windows.GameCookerWin.BuildAndRun()); + InputActions.Add(options => options.RunCookedGame, () => Editor.Windows.GameCookerWin.RunCooked()); } } } From 61b4738b6ab0bea024dcaf6f35b0cc8f0b11b188 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 26 Aug 2023 13:05:00 +0300 Subject: [PATCH 116/188] Fix Editor bindings with no modifiers triggering with modifiers pressed --- Source/Editor/Options/InputBinding.cs | 81 ++++++++++++++++++--------- 1 file changed, 55 insertions(+), 26 deletions(-) diff --git a/Source/Editor/Options/InputBinding.cs b/Source/Editor/Options/InputBinding.cs index 2e18dab9e..2ee64275e 100644 --- a/Source/Editor/Options/InputBinding.cs +++ b/Source/Editor/Options/InputBinding.cs @@ -134,6 +134,57 @@ namespace FlaxEditor.Options return false; } + private bool ProcessModifiers(Control control) + { + var root = control.Root; + bool ctrlPressed = root.GetKey(KeyboardKeys.Control); + bool shiftPressed = root.GetKey(KeyboardKeys.Shift); + bool altPressed = root.GetKey(KeyboardKeys.Alt); + + bool mod1 = false; + if (Modifier1 == KeyboardKeys.None) + mod1 = true; + else if (Modifier1 == KeyboardKeys.Control) + { + mod1 = ctrlPressed; + ctrlPressed = false; + } + else if (Modifier1 == KeyboardKeys.Shift) + { + mod1 = shiftPressed; + shiftPressed = false; + } + else if (Modifier1 == KeyboardKeys.Alt) + { + mod1 = altPressed; + altPressed = false; + } + + bool mod2 = false; + if (Modifier2 == KeyboardKeys.None) + mod2 = true; + else if (Modifier2 == KeyboardKeys.Control) + { + mod2 = ctrlPressed; + ctrlPressed = false; + } + else if (Modifier2 == KeyboardKeys.Shift) + { + mod2 = shiftPressed; + shiftPressed = false; + } + else if (Modifier2 == KeyboardKeys.Alt) + { + mod2 = altPressed; + altPressed = false; + } + + // Check if any unhandled modifiers are not pressed + if (mod1 && mod2) + return !ctrlPressed && !shiftPressed && !altPressed; + return false; + } + /// /// Processes this input binding to check if state matches. /// @@ -142,19 +193,7 @@ namespace FlaxEditor.Options public bool Process(Control control) { var root = control.Root; - - if (root.GetKey(Key)) - { - if (Modifier1 == KeyboardKeys.None || root.GetKey(Modifier1)) - { - if (Modifier2 == KeyboardKeys.None || root.GetKey(Modifier2)) - { - return true; - } - } - } - - return false; + return root.GetKey(Key) && ProcessModifiers(control); } /// @@ -165,20 +204,10 @@ namespace FlaxEditor.Options /// True if input has been processed, otherwise false. public bool Process(Control control, KeyboardKeys key) { - var root = control.Root; + if (key != Key) + return false; - if (key == Key) - { - if (Modifier1 == KeyboardKeys.None || root.GetKey(Modifier1)) - { - if (Modifier2 == KeyboardKeys.None || root.GetKey(Modifier2)) - { - return true; - } - } - } - - return false; + return ProcessModifiers(control); } /// From 17e1afb04a9dd7f7d2e93e9ec337c0bce71fca9e Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Thu, 14 Sep 2023 19:48:27 +0300 Subject: [PATCH 117/188] Add Editor input option for toggling gizmo transform space --- Source/Editor/Options/InputOptions.cs | 4 ++++ Source/Editor/Viewport/MainEditorGizmoViewport.cs | 1 + Source/Editor/Viewport/PrefabWindowViewport.cs | 1 + 3 files changed, 6 insertions(+) diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index 9285532a1..a294fbf50 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -144,6 +144,10 @@ namespace FlaxEditor.Options [EditorDisplay("Gizmo"), EditorOrder(1020)] public InputBinding ScaleMode = new InputBinding(KeyboardKeys.Alpha3); + [DefaultValue(typeof(InputBinding), "Alpha4")] + [EditorDisplay("Gizmo"), EditorOrder(1030)] + public InputBinding ToggleTransformSpace = new InputBinding(KeyboardKeys.Alpha4); + #endregion #region Viewport diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 9cbfce562..2c8ec8504 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -390,6 +390,7 @@ namespace FlaxEditor.Viewport InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate); InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate); InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale); + InputActions.Add(options => options.ToggleTransformSpace, () => { OnTransformSpaceToggle(transformSpaceToggle); transformSpaceToggle.Checked = !transformSpaceToggle.Checked; }); InputActions.Add(options => options.LockFocusSelection, LockFocusSelection); InputActions.Add(options => options.FocusSelection, FocusSelection); InputActions.Add(options => options.RotateSelection, RotateSelection); diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index ec6a983ce..a9fe4766f 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -233,6 +233,7 @@ namespace FlaxEditor.Viewport InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate); InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate); InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale); + InputActions.Add(options => options.ToggleTransformSpace, () => { OnTransformSpaceToggle(transformSpaceToggle); transformSpaceToggle.Checked = !transformSpaceToggle.Checked; }); InputActions.Add(options => options.FocusSelection, ShowSelectedActors); SetUpdate(ref _update, OnUpdate); From 964913013db6fd85fe9ce936f72499b201a9a2ad Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Thu, 14 Sep 2023 19:48:55 +0300 Subject: [PATCH 118/188] Add shortcut key to gizmo buttons --- Source/Editor/Viewport/MainEditorGizmoViewport.cs | 9 +++++---- Source/Editor/Viewport/PrefabWindowViewport.cs | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 2c8ec8504..6c52ce46b 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -194,6 +194,7 @@ namespace FlaxEditor.Viewport { _editor = editor; _dragAssets = new DragAssets(ValidateDragItem); + var inputOptions = editor.Options.Options.Input; // Prepare rendering task Task.ActorsSource = ActorsSources.Scenes; @@ -250,7 +251,7 @@ namespace FlaxEditor.Viewport var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true) { Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World, - TooltipText = "Gizmo transform space (world or local)", + TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})", Parent = transformSpaceWidget }; transformSpaceToggle.Toggled += OnTransformSpaceToggle; @@ -347,7 +348,7 @@ namespace FlaxEditor.Viewport _gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true) { Tag = TransformGizmoBase.Mode.Translate, - TooltipText = "Translate gizmo mode", + TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})", Checked = true, Parent = gizmoMode }; @@ -355,14 +356,14 @@ namespace FlaxEditor.Viewport _gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true) { Tag = TransformGizmoBase.Mode.Rotate, - TooltipText = "Rotate gizmo mode", + TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})", Parent = gizmoMode }; _gizmoModeRotate.Toggled += OnGizmoModeToggle; _gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true) { Tag = TransformGizmoBase.Mode.Scale, - TooltipText = "Scale gizmo mode", + TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})", Parent = gizmoMode }; _gizmoModeScale.Toggled += OnGizmoModeToggle; diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index a9fe4766f..c02d6b6e8 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -84,6 +84,7 @@ namespace FlaxEditor.Viewport _dragAssets = new DragAssets(ValidateDragItem); ShowDebugDraw = true; ShowEditorPrimitives = true; + var inputOptions = window.Editor.Options.Options.Input; // Prepare rendering task Task.ActorsSource = ActorsSources.CustomActors; @@ -113,7 +114,7 @@ namespace FlaxEditor.Viewport var transformSpaceToggle = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Globe32, null, true) { Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World, - TooltipText = "Gizmo transform space (world or local)", + TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})", Parent = transformSpaceWidget }; transformSpaceToggle.Toggled += OnTransformSpaceToggle; @@ -205,7 +206,7 @@ namespace FlaxEditor.Viewport _gizmoModeTranslate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Translate32, null, true) { Tag = TransformGizmoBase.Mode.Translate, - TooltipText = "Translate gizmo mode", + TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})", Checked = true, Parent = gizmoMode }; @@ -213,14 +214,14 @@ namespace FlaxEditor.Viewport _gizmoModeRotate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Rotate32, null, true) { Tag = TransformGizmoBase.Mode.Rotate, - TooltipText = "Rotate gizmo mode", + TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})", Parent = gizmoMode }; _gizmoModeRotate.Toggled += OnGizmoModeToggle; _gizmoModeScale = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Scale32, null, true) { Tag = TransformGizmoBase.Mode.Scale, - TooltipText = "Scale gizmo mode", + TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})", Parent = gizmoMode }; _gizmoModeScale.Toggled += OnGizmoModeToggle; From 69ce189c5d8f71c0cd563bb97b068541e8bb48ea Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 16 Sep 2023 19:52:58 +0300 Subject: [PATCH 119/188] Add input bindings for Scene viewport related actions and Pilot Actor --- Source/Editor/Modules/UIModule.cs | 8 ++++---- Source/Editor/Options/InputOptions.cs | 16 ++++++++++++++++ .../Windows/SceneTreeWindow.ContextMenu.cs | 15 ++++++++------- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 9130797bf..d54012cc3 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -545,10 +545,10 @@ namespace FlaxEditor.Modules MenuScene = MainMenu.AddButton("Scene"); cm = MenuScene.ContextMenu; cm.VisibleChanged += OnMenuSceneShowHide; - _menuSceneMoveActorToViewport = cm.AddButton("Move actor to viewport", MoveActorToViewport); - _menuSceneAlignActorWithViewport = cm.AddButton("Align actor with viewport", AlignActorWithViewport); - _menuSceneAlignViewportWithActor = cm.AddButton("Align viewport with actor", AlignViewportWithActor); - _menuScenePilotActor = cm.AddButton("Pilot actor", PilotActor); + _menuSceneMoveActorToViewport = cm.AddButton("Move actor to viewport", inputOptions.MoveActorToViewport, MoveActorToViewport); + _menuSceneAlignActorWithViewport = cm.AddButton("Align actor with viewport", inputOptions.AlignActorWithViewport, AlignActorWithViewport); + _menuSceneAlignViewportWithActor = cm.AddButton("Align viewport with actor", inputOptions.AlignViewportWithActor, AlignViewportWithActor); + _menuScenePilotActor = cm.AddButton("Pilot actor", inputOptions.PilotActor, PilotActor); cm.AddSeparator(); _menuSceneCreateTerrain = cm.AddButton("Create terrain", CreateTerrain); diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index a294fbf50..9559e9142 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -108,6 +108,22 @@ namespace FlaxEditor.Options [EditorDisplay("Scene", "Run cooked game"), EditorOrder(560)] public InputBinding RunCookedGame = new InputBinding(KeyboardKeys.None); + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Move actor to viewport"), EditorOrder(570)] + public InputBinding MoveActorToViewport = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Align actor with viewport"), EditorOrder(571)] + public InputBinding AlignActorWithViewport = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Align viewport with actor"), EditorOrder(572)] + public InputBinding AlignViewportWithActor = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene"), EditorOrder(573)] + public InputBinding PilotActor = new InputBinding(KeyboardKeys.None); + #endregion #region Debugger diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index e48e59f80..63d110368 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -26,6 +26,7 @@ namespace FlaxEditor.Windows bool hasSthSelected = Editor.SceneEditing.HasSthSelected; bool isSingleActorSelected = Editor.SceneEditing.SelectionCount == 1 && Editor.SceneEditing.Selection[0] is ActorNode; bool canEditScene = Editor.StateMachine.CurrentState.CanEditScene && Level.IsAnySceneLoaded; + var inputOptions = Editor.Options.Options.Input; // Create popup @@ -44,17 +45,17 @@ namespace FlaxEditor.Windows if (hasSthSelected) { - contextMenu.AddButton(Editor.Windows.EditWin.IsPilotActorActive ? "Stop piloting actor" : "Pilot actor", Editor.UI.PilotActor); + contextMenu.AddButton(Editor.Windows.EditWin.IsPilotActorActive ? "Stop piloting actor" : "Pilot actor", inputOptions.PilotActor, Editor.UI.PilotActor); } contextMenu.AddSeparator(); // Basic editing options - b = contextMenu.AddButton("Rename", Rename); + b = contextMenu.AddButton("Rename", inputOptions.Rename, Rename); b.Enabled = isSingleActorSelected; - b = contextMenu.AddButton("Duplicate", Editor.SceneEditing.Duplicate); + b = contextMenu.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate); b.Enabled = hasSthSelected; if (isSingleActorSelected) @@ -116,17 +117,17 @@ namespace FlaxEditor.Windows } } } - b = contextMenu.AddButton("Delete", Editor.SceneEditing.Delete); + b = contextMenu.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete); b.Enabled = hasSthSelected; contextMenu.AddSeparator(); - b = contextMenu.AddButton("Copy", Editor.SceneEditing.Copy); + b = contextMenu.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy); b.Enabled = hasSthSelected; - contextMenu.AddButton("Paste", Editor.SceneEditing.Paste); + contextMenu.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste); - b = contextMenu.AddButton("Cut", Editor.SceneEditing.Cut); + b = contextMenu.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut); b.Enabled = canEditScene; // Prefab options From 0122a9f699c50787906e1b706088ec1aed3982f6 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 16 Sep 2023 22:39:04 +0300 Subject: [PATCH 120/188] Implement viewport camera rotation toggle for trackpad users --- Source/Editor/Options/InputBinding.cs | 27 +++++++++++++++++--- Source/Editor/Options/InputOptions.cs | 4 +++ Source/Editor/Viewport/EditorViewport.cs | 32 ++++++++++++++++-------- 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/Source/Editor/Options/InputBinding.cs b/Source/Editor/Options/InputBinding.cs index 2ee64275e..eb61c0f68 100644 --- a/Source/Editor/Options/InputBinding.cs +++ b/Source/Editor/Options/InputBinding.cs @@ -136,10 +136,19 @@ namespace FlaxEditor.Options private bool ProcessModifiers(Control control) { - var root = control.Root; - bool ctrlPressed = root.GetKey(KeyboardKeys.Control); - bool shiftPressed = root.GetKey(KeyboardKeys.Shift); - bool altPressed = root.GetKey(KeyboardKeys.Alt); + return ProcessModifiers(control.Root.GetKey); + } + + private bool ProcessModifiers(Window window) + { + return ProcessModifiers(window.GetKey); + } + + private bool ProcessModifiers(Func getKeyFunc) + { + bool ctrlPressed = getKeyFunc(KeyboardKeys.Control); + bool shiftPressed = getKeyFunc(KeyboardKeys.Shift); + bool altPressed = getKeyFunc(KeyboardKeys.Alt); bool mod1 = false; if (Modifier1 == KeyboardKeys.None) @@ -210,6 +219,16 @@ namespace FlaxEditor.Options return ProcessModifiers(control); } + /// + /// Processes this input binding to check if state matches. + /// + /// The input providing window. + /// True if input has been processed, otherwise false. + public bool Process(Window window) + { + return window.GetKey(Key) && ProcessModifiers(window); + } + /// public override string ToString() { diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index 9559e9142..1ad7907cd 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -192,6 +192,10 @@ namespace FlaxEditor.Options [EditorDisplay("Viewport"), EditorOrder(1550)] public InputBinding Down = new InputBinding(KeyboardKeys.Q); + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Viewport", "Toggle Camera Rotation"), EditorOrder(1560)] + public InputBinding CameraToggleRotation = new InputBinding(KeyboardKeys.None); + [DefaultValue(typeof(InputBinding), "Numpad0")] [EditorDisplay("Viewport"), EditorOrder(1600)] public InputBinding ViewpointFront = new InputBinding(KeyboardKeys.Numpad0); diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index ad7e06ba5..928922108 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -137,7 +137,7 @@ namespace FlaxEditor.Viewport // Input - private bool _isControllingMouse, _isViewportControllingMouse; + private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown; private int _deltaFilteringStep; private Float2 _startPos; private Float2 _mouseDeltaLast; @@ -685,6 +685,7 @@ namespace FlaxEditor.Viewport InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Back").Orientation))); InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Right").Orientation))); InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Left").Orientation))); + InputActions.Add(options => options.CameraToggleRotation, () => _isVirtualMouseRightDown = !_isVirtualMouseRightDown); // Link for task event task.Begin += OnRenderBegin; @@ -1048,6 +1049,15 @@ namespace FlaxEditor.Viewport // Track controlling mouse state change bool wasControllingMouse = _prevInput.IsControllingMouse; _isControllingMouse = _input.IsControllingMouse; + + // Simulate holding mouse right down for trackpad users + if (_prevInput.IsMouseRightDown && !_input.IsMouseRightDown) + _isVirtualMouseRightDown = false; + if (_wasVirtualMouseRightDown) + wasControllingMouse = true; + if (_isVirtualMouseRightDown) + _isControllingMouse = _isVirtualMouseRightDown; + if (wasControllingMouse != _isControllingMouse) { if (_isControllingMouse) @@ -1061,16 +1071,18 @@ namespace FlaxEditor.Viewport OnLeftMouseButtonDown(); else if (_prevInput.IsMouseLeftDown && !_input.IsMouseLeftDown) OnLeftMouseButtonUp(); - // - if (!_prevInput.IsMouseRightDown && _input.IsMouseRightDown) + + if ((!_prevInput.IsMouseRightDown && _input.IsMouseRightDown) || (!_wasVirtualMouseRightDown && _isVirtualMouseRightDown)) OnRightMouseButtonDown(); - else if (_prevInput.IsMouseRightDown && !_input.IsMouseRightDown) + else if ((_prevInput.IsMouseRightDown && !_input.IsMouseRightDown) || (_wasVirtualMouseRightDown && !_isVirtualMouseRightDown)) OnRightMouseButtonUp(); - // + if (!_prevInput.IsMouseMiddleDown && _input.IsMouseMiddleDown) OnMiddleMouseButtonDown(); else if (_prevInput.IsMouseMiddleDown && !_input.IsMouseMiddleDown) OnMiddleMouseButtonUp(); + + _wasVirtualMouseRightDown = _isVirtualMouseRightDown; } // Get clamped delta time (more stable during lags) @@ -1088,7 +1100,7 @@ namespace FlaxEditor.Viewport bool isAltDown = _input.IsAltDown; bool lbDown = _input.IsMouseLeftDown; bool mbDown = _input.IsMouseMiddleDown; - bool rbDown = _input.IsMouseRightDown; + bool rbDown = _input.IsMouseRightDown || _isVirtualMouseRightDown; bool wheelInUse = Math.Abs(_input.MouseWheelDelta) > Mathf.Epsilon; _input.IsPanning = !isAltDown && mbDown && !rbDown; @@ -1098,7 +1110,7 @@ namespace FlaxEditor.Viewport _input.IsOrbiting = isAltDown && lbDown && !mbDown && !rbDown; // Control move speed with RMB+Wheel - rmbWheel = useMovementSpeed && _input.IsMouseRightDown && wheelInUse; + rmbWheel = useMovementSpeed && (_input.IsMouseRightDown || _isVirtualMouseRightDown) && wheelInUse; if (rmbWheel) { float step = 4.0f; @@ -1165,7 +1177,7 @@ namespace FlaxEditor.Viewport // Calculate smooth mouse delta not dependant on viewport size var offset = _viewMousePos - _startPos; - if (_input.IsZooming && !_input.IsMouseRightDown && !_input.IsMouseLeftDown && !_input.IsMouseMiddleDown && !_isOrtho && !rmbWheel) + if (_input.IsZooming && !_input.IsMouseRightDown && !_input.IsMouseLeftDown && !_input.IsMouseMiddleDown && !_isOrtho && !rmbWheel && !_isVirtualMouseRightDown) { offset = Float2.Zero; } @@ -1213,7 +1225,7 @@ namespace FlaxEditor.Viewport UpdateView(dt, ref moveDelta, ref mouseDelta, out var centerMouse); // Move mouse back to the root position - if (centerMouse && (_input.IsMouseRightDown || _input.IsMouseLeftDown || _input.IsMouseMiddleDown)) + if (centerMouse && (_input.IsMouseRightDown || _input.IsMouseLeftDown || _input.IsMouseMiddleDown || _isVirtualMouseRightDown)) { var center = PointToWindow(_startPos); win.MousePosition = center; @@ -1229,7 +1241,7 @@ namespace FlaxEditor.Viewport } else { - if (_input.IsMouseLeftDown || _input.IsMouseRightDown) + if (_input.IsMouseLeftDown || _input.IsMouseRightDown || _isVirtualMouseRightDown) { // Calculate smooth mouse delta not dependant on viewport size var offset = _viewMousePos - _startPos; From 2023aa8c94db72848f77095be90c5f3b317acc53 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 16 Sep 2023 23:16:17 +0300 Subject: [PATCH 121/188] Add viewport camera movement speed adjustment input bindings --- Source/Editor/Options/InputOptions.cs | 20 ++++++--- Source/Editor/Viewport/EditorViewport.cs | 52 +++++++++++++++--------- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index 1ad7907cd..27faf48a2 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -196,28 +196,36 @@ namespace FlaxEditor.Options [EditorDisplay("Viewport", "Toggle Camera Rotation"), EditorOrder(1560)] public InputBinding CameraToggleRotation = new InputBinding(KeyboardKeys.None); + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Viewport", "Increase Camera Move Speed"), EditorOrder(1570)] + public InputBinding CameraIncreaseMoveSpeed = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Viewport", "Decrease Camera Move Speed"), EditorOrder(1571)] + public InputBinding CameraDecreaseMoveSpeed = new InputBinding(KeyboardKeys.None); + [DefaultValue(typeof(InputBinding), "Numpad0")] - [EditorDisplay("Viewport"), EditorOrder(1600)] + [EditorDisplay("Viewport"), EditorOrder(1700)] public InputBinding ViewpointFront = new InputBinding(KeyboardKeys.Numpad0); [DefaultValue(typeof(InputBinding), "Numpad5")] - [EditorDisplay("Viewport"), EditorOrder(1610)] + [EditorDisplay("Viewport"), EditorOrder(1710)] public InputBinding ViewpointBack = new InputBinding(KeyboardKeys.Numpad5); [DefaultValue(typeof(InputBinding), "Numpad4")] - [EditorDisplay("Viewport"), EditorOrder(1620)] + [EditorDisplay("Viewport"), EditorOrder(1720)] public InputBinding ViewpointLeft = new InputBinding(KeyboardKeys.Numpad4); [DefaultValue(typeof(InputBinding), "Numpad6")] - [EditorDisplay("Viewport"), EditorOrder(1630)] + [EditorDisplay("Viewport"), EditorOrder(1730)] public InputBinding ViewpointRight = new InputBinding(KeyboardKeys.Numpad6); [DefaultValue(typeof(InputBinding), "Numpad8")] - [EditorDisplay("Viewport"), EditorOrder(1640)] + [EditorDisplay("Viewport"), EditorOrder(1740)] public InputBinding ViewpointTop = new InputBinding(KeyboardKeys.Numpad8); [DefaultValue(typeof(InputBinding), "Numpad2")] - [EditorDisplay("Viewport"), EditorOrder(1650)] + [EditorDisplay("Viewport"), EditorOrder(1750)] public InputBinding ViewpointBottom = new InputBinding(KeyboardKeys.Numpad2); #endregion diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 928922108..9ecd7107b 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -686,6 +686,8 @@ namespace FlaxEditor.Viewport InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Right").Orientation))); InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Left").Orientation))); InputActions.Add(options => options.CameraToggleRotation, () => _isVirtualMouseRightDown = !_isVirtualMouseRightDown); + InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(1)); + InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(-1)); // Link for task event task.Begin += OnRenderBegin; @@ -723,6 +725,30 @@ namespace FlaxEditor.Viewport } } + /// + /// Increases or decreases the camera movement speed. + /// + /// The stepping direction for speed adjustment. + protected void AdjustCameraMoveSpeed(int step) + { + int camValueIndex = -1; + for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++) + { + if (Mathf.NearEqual(EditorViewportCameraSpeedValues[i], _movementSpeed)) + { + camValueIndex = i; + break; + } + } + if (camValueIndex == -1) + return; + + if (step > 0) + MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Min(camValueIndex + 1, EditorViewportCameraSpeedValues.Length - 1)]; + else if (step < 0) + MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Max(camValueIndex - 1, 0)]; + } + private void OnEditorOptionsChanged(EditorOptions options) { _mouseSensitivity = options.Viewport.MouseSensitivity; @@ -1113,29 +1139,17 @@ namespace FlaxEditor.Viewport rmbWheel = useMovementSpeed && (_input.IsMouseRightDown || _isVirtualMouseRightDown) && wheelInUse; if (rmbWheel) { - float step = 4.0f; + const float step = 4.0f; _wheelMovementChangeDeltaSum += _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity; - int camValueIndex = -1; - for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++) + if (_wheelMovementChangeDeltaSum >= step) { - if (Mathf.NearEqual(EditorViewportCameraSpeedValues[i], _movementSpeed)) - { - camValueIndex = i; - break; - } + _wheelMovementChangeDeltaSum -= step; + AdjustCameraMoveSpeed(1); } - if (camValueIndex != -1) + else if (_wheelMovementChangeDeltaSum <= -step) { - if (_wheelMovementChangeDeltaSum >= step) - { - _wheelMovementChangeDeltaSum -= step; - MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Min(camValueIndex + 1, EditorViewportCameraSpeedValues.Length - 1)]; - } - else if (_wheelMovementChangeDeltaSum <= -step) - { - _wheelMovementChangeDeltaSum += step; - MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Max(camValueIndex - 1, 0)]; - } + _wheelMovementChangeDeltaSum += step; + AdjustCameraMoveSpeed(-1); } } } From efbc757369cc6f5e7029b0db58ffda628a6a6073 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 16 Sep 2023 23:23:50 +0300 Subject: [PATCH 122/188] Cancel camera rotation toggle when entering playmode or hitting escape --- Source/Editor/Viewport/EditorViewport.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 9ecd7107b..34dbe561c 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -1077,8 +1077,8 @@ namespace FlaxEditor.Viewport _isControllingMouse = _input.IsControllingMouse; // Simulate holding mouse right down for trackpad users - if (_prevInput.IsMouseRightDown && !_input.IsMouseRightDown) - _isVirtualMouseRightDown = false; + if ((_prevInput.IsMouseRightDown && !_input.IsMouseRightDown) || win.GetKeyDown(KeyboardKeys.Escape)) + _isVirtualMouseRightDown = false; // Cancel when mouse right or escape is pressed if (_wasVirtualMouseRightDown) wasControllingMouse = true; if (_isVirtualMouseRightDown) @@ -1385,6 +1385,7 @@ namespace FlaxEditor.Viewport { OnControlMouseEnd(RootWindow.Window); _isControllingMouse = false; + _isVirtualMouseRightDown = false; } } From 8ed57863b88761022d38b6354e59ac507d56c17d Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sun, 17 Sep 2023 00:14:18 +0300 Subject: [PATCH 123/188] Add Editor input bindings for Tools menu items --- Source/Editor/Editor.cs | 65 +++++++++++++++++ Source/Editor/Modules/UIModule.cs | 84 ++++++---------------- Source/Editor/Options/InputOptions.cs | 44 ++++++++++-- Source/Editor/Windows/SceneEditorWindow.cs | 8 +++ 4 files changed, 134 insertions(+), 67 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 7f0359331..06a6f0979 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; @@ -13,6 +14,7 @@ using FlaxEditor.Content.Thumbnails; using FlaxEditor.Modules; using FlaxEditor.Modules.SourceCodeEditing; using FlaxEditor.Options; +using FlaxEditor.SceneGraph.Actors; using FlaxEditor.States; using FlaxEditor.Windows; using FlaxEditor.Windows.Assets; @@ -1274,6 +1276,69 @@ namespace FlaxEditor Scene.MarkSceneEdited(scenes); } + /// + /// Bakes all environmental probes in the scene. + /// + public void BakeAllEnvProbes() + { + Scene.ExecuteOnGraph(node => + { + if (node is EnvironmentProbeNode envProbeNode && envProbeNode.IsActive) + { + ((EnvironmentProbe)envProbeNode.Actor).Bake(); + node.ParentScene.IsEdited = true; + } + else if (node is SkyLightNode skyLightNode && skyLightNode.IsActive && skyLightNode.Actor is SkyLight skyLight && skyLight.Mode == SkyLight.Modes.CaptureScene) + { + skyLight.Bake(); + node.ParentScene.IsEdited = true; + } + + return node.IsActive; + }); + } + + /// + /// Builds CSG for all open scenes. + /// + public void BuildCSG() + { + var scenes = Level.Scenes; + scenes.ToList().ForEach(x => x.BuildCSG(0)); + Scene.MarkSceneEdited(scenes); + } + + /// + /// Builds Nav mesh for all open scenes. + /// + public void BuildNavMesh() + { + var scenes = Level.Scenes; + scenes.ToList().ForEach(x => Navigation.BuildNavMesh(x, 0)); + Scene.MarkSceneEdited(scenes); + } + + /// + /// Builds SDF for all static models in the scene. + /// + public void BuildAllMeshesSDF() + { + // TODO: async maybe with progress reporting? + Scene.ExecuteOnGraph(node => + { + if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel) + { + if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) && staticModel.Model != null && !staticModel.Model.IsVirtual && staticModel.Model.SDF.Texture == null) + { + Log("Generating SDF for " + staticModel.Model); + if (!staticModel.Model.GenerateSDF()) + staticModel.Model.Save(); + } + } + return true; + }); + } + #endregion #region Internal Calls diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index d54012cc3..bde2e610a 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -70,9 +70,10 @@ namespace FlaxEditor.Modules private ContextMenuButton _menuToolsBakeAllEnvProbes; private ContextMenuButton _menuToolsBuildCSGMesh; private ContextMenuButton _menuToolsBuildNavMesh; - private ContextMenuButton _menuToolsBuildAllMesgesSDF; + private ContextMenuButton _menuToolsBuildAllMeshesSDF; private ContextMenuButton _menuToolsCancelBuilding; private ContextMenuButton _menuToolsSetTheCurrentSceneViewAsDefault; + private ContextMenuButton _menuToolsTakeScreenshot; private ContextMenuChildMenu _menuWindowApplyWindowLayout; private ToolStripButton _toolStripSaveAll; @@ -576,14 +577,14 @@ namespace FlaxEditor.Modules MenuTools = MainMenu.AddButton("Tools"); cm = MenuTools.ContextMenu; cm.VisibleChanged += OnMenuToolsShowHide; - _menuToolsBuildScenes = cm.AddButton("Build scenes data", "Ctrl+F10", Editor.BuildScenesOrCancel); + _menuToolsBuildScenes = cm.AddButton("Build scenes data", inputOptions.BuildScenesData, Editor.BuildScenesOrCancel); cm.AddSeparator(); - _menuToolsBakeLightmaps = cm.AddButton("Bake lightmaps", Editor.BakeLightmapsOrCancel); - _menuToolsClearLightmaps = cm.AddButton("Clear lightmaps data", Editor.ClearLightmaps); - _menuToolsBakeAllEnvProbes = cm.AddButton("Bake all env probes", BakeAllEnvProbes); - _menuToolsBuildCSGMesh = cm.AddButton("Build CSG mesh", BuildCSG); - _menuToolsBuildNavMesh = cm.AddButton("Build Nav Mesh", BuildNavMesh); - _menuToolsBuildAllMesgesSDF = cm.AddButton("Build all meshes SDF", BuildAllMeshesSDF); + _menuToolsBakeLightmaps = cm.AddButton("Bake lightmaps", inputOptions.BakeLightmaps, Editor.BakeLightmapsOrCancel); + _menuToolsClearLightmaps = cm.AddButton("Clear lightmaps data", inputOptions.ClearLightmaps, Editor.ClearLightmaps); + _menuToolsBakeAllEnvProbes = cm.AddButton("Bake all env probes", inputOptions.BakeEnvProbes, Editor.BakeAllEnvProbes); + _menuToolsBuildCSGMesh = cm.AddButton("Build CSG mesh", inputOptions.BuildCSG, Editor.BuildCSG); + _menuToolsBuildNavMesh = cm.AddButton("Build Nav Mesh", inputOptions.BuildNav, Editor.BuildNavMesh); + _menuToolsBuildAllMeshesSDF = cm.AddButton("Build all meshes SDF", inputOptions.BuildSDF, Editor.BuildAllMeshesSDF); cm.AddSeparator(); cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow); _menuToolsCancelBuilding = cm.AddButton("Cancel building game", () => GameCooker.Cancel()); @@ -591,7 +592,7 @@ namespace FlaxEditor.Modules cm.AddButton("Profiler", Editor.Windows.ProfilerWin.FocusOrShow); cm.AddSeparator(); _menuToolsSetTheCurrentSceneViewAsDefault = cm.AddButton("Set current scene view as project default", SetTheCurrentSceneViewAsDefault); - cm.AddButton("Take screenshot", "F12", Editor.Windows.TakeScreenshot); + _menuToolsTakeScreenshot = cm.AddButton("Take screenshot", inputOptions.TakeScreenshot, Editor.Windows.TakeScreenshot); cm.AddSeparator(); cm.AddButton("Plugins", () => Editor.Windows.PluginsWin.Show()); cm.AddButton("Options", () => Editor.Windows.EditorOptionsWin.Show()); @@ -652,6 +653,14 @@ namespace FlaxEditor.Modules _menuGameStop.ShortKeys = inputOptions.Play.ToString(); _menuGameCookAndRun.ShortKeys = inputOptions.CookAndRun.ToString(); _menuGameRunCookedGame.ShortKeys = inputOptions.RunCookedGame.ToString(); + _menuToolsBuildScenes.ShortKeys = inputOptions.BuildScenesData.ToString(); + _menuToolsBakeLightmaps.ShortKeys = inputOptions.BakeLightmaps.ToString(); + _menuToolsClearLightmaps.ShortKeys = inputOptions.ClearLightmaps.ToString(); + _menuToolsBakeAllEnvProbes.ShortKeys = inputOptions.BakeEnvProbes.ToString(); + _menuToolsBuildCSGMesh.ShortKeys = inputOptions.BuildCSG.ToString(); + _menuToolsBuildNavMesh.ShortKeys = inputOptions.BuildNav.ToString(); + _menuToolsBuildAllMeshesSDF.ShortKeys = inputOptions.BuildSDF.ToString(); + _menuToolsTakeScreenshot.ShortKeys = inputOptions.TakeScreenshot.ToString(); MainMenuShortcutKeysUpdated?.Invoke(); } @@ -676,10 +685,10 @@ namespace FlaxEditor.Modules ToolStrip.AddSeparator(); // Cook scenes - _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip("Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options (Ctrl+F10)"); + _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip($"Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options ({inputOptions.BuildScenesData})"); // Cook and run - _toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip("Cook & Run - build game for the current platform and run it locally"); + _toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip($"Cook & Run - build game for the current platform and run it locally ({inputOptions.Play})"); _toolStripCook.ContextMenu = new ContextMenu(); _toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked); _toolStripCook.ContextMenu.AddSeparator(); @@ -880,7 +889,7 @@ namespace FlaxEditor.Modules _menuToolsBakeLightmaps.Text = isBakingLightmaps ? "Cancel baking lightmaps" : "Bake lightmaps"; _menuToolsClearLightmaps.Enabled = canEdit; _menuToolsBakeAllEnvProbes.Enabled = canEdit; - _menuToolsBuildAllMesgesSDF.Enabled = canEdit && !isBakingLightmaps; + _menuToolsBuildAllMeshesSDF.Enabled = canEdit && !isBakingLightmaps; _menuToolsBuildCSGMesh.Enabled = canEdit; _menuToolsBuildNavMesh.Enabled = canEdit; _menuToolsCancelBuilding.Enabled = GameCooker.IsRunning; @@ -980,57 +989,6 @@ namespace FlaxEditor.Modules new Tools.Terrain.CreateTerrainDialog().Show(Editor.Windows.MainWindow); } - private void BakeAllEnvProbes() - { - Editor.Scene.ExecuteOnGraph(node => - { - if (node is EnvironmentProbeNode envProbeNode && envProbeNode.IsActive) - { - ((EnvironmentProbe)envProbeNode.Actor).Bake(); - node.ParentScene.IsEdited = true; - } - else if (node is SkyLightNode skyLightNode && skyLightNode.IsActive && skyLightNode.Actor is SkyLight skyLight && skyLight.Mode == SkyLight.Modes.CaptureScene) - { - skyLight.Bake(); - node.ParentScene.IsEdited = true; - } - - return node.IsActive; - }); - } - - private void BuildCSG() - { - var scenes = Level.Scenes; - scenes.ToList().ForEach(x => x.BuildCSG(0)); - Editor.Scene.MarkSceneEdited(scenes); - } - - private void BuildNavMesh() - { - var scenes = Level.Scenes; - scenes.ToList().ForEach(x => Navigation.BuildNavMesh(x, 0)); - Editor.Scene.MarkSceneEdited(scenes); - } - - private void BuildAllMeshesSDF() - { - // TODO: async maybe with progress reporting? - Editor.Scene.ExecuteOnGraph(node => - { - if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel) - { - if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) && staticModel.Model != null && !staticModel.Model.IsVirtual && staticModel.Model.SDF.Texture == null) - { - Editor.Log("Generating SDF for " + staticModel.Model); - if (!staticModel.Model.GenerateSDF()) - staticModel.Model.Save(); - } - } - return true; - }); - } - private void SetTheCurrentSceneViewAsDefault() { var projectInfo = Editor.GameProject; diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index 27faf48a2..52d7343dd 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -126,22 +126,58 @@ namespace FlaxEditor.Options #endregion + #region Tools + + [DefaultValue(typeof(InputBinding), "Ctrl+F10")] + [EditorDisplay("Tools", "Build scenes data"), EditorOrder(600)] + public InputBinding BuildScenesData = new InputBinding(KeyboardKeys.F10, KeyboardKeys.Control); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Bake lightmaps"), EditorOrder(601)] + public InputBinding BakeLightmaps = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Clear lightmaps data"), EditorOrder(602)] + public InputBinding ClearLightmaps = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Bake all env probes"), EditorOrder(603)] + public InputBinding BakeEnvProbes = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Build CSG mesh"), EditorOrder(604)] + public InputBinding BuildCSG = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Build Nav Mesh"), EditorOrder(605)] + public InputBinding BuildNav = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Build all meshes SDF"), EditorOrder(606)] + public InputBinding BuildSDF = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "F12")] + [EditorDisplay("Tools", "Take screenshot"), EditorOrder(607)] + public InputBinding TakeScreenshot = new InputBinding(KeyboardKeys.F12); + + #endregion + #region Debugger [DefaultValue(typeof(InputBinding), "F5")] - [EditorDisplay("Debugger", "Continue"), EditorOrder(610)] + [EditorDisplay("Debugger", "Continue"), EditorOrder(810)] public InputBinding DebuggerContinue = new InputBinding(KeyboardKeys.F5); [DefaultValue(typeof(InputBinding), "F10")] - [EditorDisplay("Debugger", "Step Over"), EditorOrder(620)] + [EditorDisplay("Debugger", "Step Over"), EditorOrder(820)] public InputBinding DebuggerStepOver = new InputBinding(KeyboardKeys.F10); [DefaultValue(typeof(InputBinding), "F11")] - [EditorDisplay("Debugger", "Step Into"), EditorOrder(630)] + [EditorDisplay("Debugger", "Step Into"), EditorOrder(830)] public InputBinding DebuggerStepInto = new InputBinding(KeyboardKeys.F11); [DefaultValue(typeof(InputBinding), "Shift+F11")] - [EditorDisplay("Debugger", "Step Out"), EditorOrder(640)] + [EditorDisplay("Debugger", "Step Out"), EditorOrder(840)] public InputBinding DebuggerStepOut = new InputBinding(KeyboardKeys.F11, KeyboardKeys.Shift); #endregion diff --git a/Source/Editor/Windows/SceneEditorWindow.cs b/Source/Editor/Windows/SceneEditorWindow.cs index 290f02089..529eb5ebc 100644 --- a/Source/Editor/Windows/SceneEditorWindow.cs +++ b/Source/Editor/Windows/SceneEditorWindow.cs @@ -44,6 +44,14 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.StepFrame, Editor.Simulation.RequestPlayOneFrame); InputActions.Add(options => options.CookAndRun, () => Editor.Windows.GameCookerWin.BuildAndRun()); InputActions.Add(options => options.RunCookedGame, () => Editor.Windows.GameCookerWin.RunCooked()); + InputActions.Add(options => options.BuildScenesData, Editor.BuildScenesOrCancel); + InputActions.Add(options => options.BakeLightmaps, Editor.BakeLightmapsOrCancel); + InputActions.Add(options => options.ClearLightmaps, Editor.ClearLightmaps); + InputActions.Add(options => options.BakeEnvProbes, Editor.BakeAllEnvProbes); + InputActions.Add(options => options.BuildCSG, Editor.BuildCSG); + InputActions.Add(options => options.BuildNav, Editor.BuildNavMesh); + InputActions.Add(options => options.BuildSDF, Editor.BuildAllMeshesSDF); + InputActions.Add(options => options.TakeScreenshot, Editor.Windows.TakeScreenshot); } } } From a1cbaba743c0b9311051898e01f51e32fd5c7669 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sun, 17 Sep 2023 00:15:26 +0300 Subject: [PATCH 124/188] Add Editor input bindings for Profiler window and Profiler actions --- Source/Editor/Modules/UIModule.cs | 6 ++++-- Source/Editor/Options/InputOptions.cs | 16 ++++++++++++++++ Source/Editor/Windows/GameWindow.cs | 3 +++ Source/Editor/Windows/Profiler/ProfilerWindow.cs | 4 ++++ Source/Editor/Windows/SceneEditorWindow.cs | 3 +++ 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index bde2e610a..e675b4fe0 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -72,6 +72,7 @@ namespace FlaxEditor.Modules private ContextMenuButton _menuToolsBuildNavMesh; private ContextMenuButton _menuToolsBuildAllMeshesSDF; private ContextMenuButton _menuToolsCancelBuilding; + private ContextMenuButton _menuToolsProfilerWindow; private ContextMenuButton _menuToolsSetTheCurrentSceneViewAsDefault; private ContextMenuButton _menuToolsTakeScreenshot; private ContextMenuChildMenu _menuWindowApplyWindowLayout; @@ -589,7 +590,7 @@ namespace FlaxEditor.Modules cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow); _menuToolsCancelBuilding = cm.AddButton("Cancel building game", () => GameCooker.Cancel()); cm.AddSeparator(); - cm.AddButton("Profiler", Editor.Windows.ProfilerWin.FocusOrShow); + _menuToolsProfilerWindow = cm.AddButton("Profiler", inputOptions.ProfilerWindow, () => Editor.Windows.ProfilerWin.FocusOrShow()); cm.AddSeparator(); _menuToolsSetTheCurrentSceneViewAsDefault = cm.AddButton("Set current scene view as project default", SetTheCurrentSceneViewAsDefault); _menuToolsTakeScreenshot = cm.AddButton("Take screenshot", inputOptions.TakeScreenshot, Editor.Windows.TakeScreenshot); @@ -611,7 +612,7 @@ namespace FlaxEditor.Modules cm.AddButton("Output Log", Editor.Windows.OutputLogWin.FocusOrShow); cm.AddButton("Graphics Quality", Editor.Windows.GraphicsQualityWin.FocusOrShow); cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow); - cm.AddButton("Profiler", Editor.Windows.ProfilerWin.FocusOrShow); + cm.AddButton("Profiler", inputOptions.ProfilerWindow, Editor.Windows.ProfilerWin.FocusOrShow); cm.AddButton("Content Search", Editor.ContentFinding.ShowSearch); cm.AddButton("Visual Script Debugger", Editor.Windows.VisualScriptDebuggerWin.FocusOrShow); cm.AddSeparator(); @@ -660,6 +661,7 @@ namespace FlaxEditor.Modules _menuToolsBuildCSGMesh.ShortKeys = inputOptions.BuildCSG.ToString(); _menuToolsBuildNavMesh.ShortKeys = inputOptions.BuildNav.ToString(); _menuToolsBuildAllMeshesSDF.ShortKeys = inputOptions.BuildSDF.ToString(); + _menuToolsProfilerWindow.ShortKeys = inputOptions.ProfilerWindow.ToString(); _menuToolsTakeScreenshot.ShortKeys = inputOptions.TakeScreenshot.ToString(); MainMenuShortcutKeysUpdated?.Invoke(); diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index 52d7343dd..b19a46596 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -162,6 +162,22 @@ namespace FlaxEditor.Options #endregion + #region Profiler + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Profiler", "Open Profiler Window"), EditorOrder(630)] + public InputBinding ProfilerWindow = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Profiler", "Start/Stop Profiler"), EditorOrder(631)] + public InputBinding ProfilerStartStop = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Profiler", "Clear Profiler data"), EditorOrder(632)] + public InputBinding ProfilerClear = new InputBinding(KeyboardKeys.None); + + #endregion + #region Debugger [DefaultValue(typeof(InputBinding), "F5")] diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 513542fdc..ed1799827 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -306,6 +306,9 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.Play, Editor.Simulation.DelegatePlayOrStopPlayInEditor); InputActions.Add(options => options.Pause, Editor.Simulation.RequestResumeOrPause); InputActions.Add(options => options.StepFrame, Editor.Simulation.RequestPlayOneFrame); + InputActions.Add(options => options.ProfilerWindow, () => Editor.Windows.ProfilerWin.FocusOrShow()); + InputActions.Add(options => options.ProfilerStartStop, () => { Editor.Windows.ProfilerWin.LiveRecording = !Editor.Windows.ProfilerWin.LiveRecording; Editor.UI.AddStatusMessage($"Profiling {(Editor.Windows.ProfilerWin.LiveRecording ? "started" : "stopped")}."); }); + InputActions.Add(options => options.ProfilerClear, () => { Editor.Windows.ProfilerWin.Clear(); Editor.UI.AddStatusMessage($"Profiling results cleared."); }); } private void ChangeViewportRatio(ViewportScaleOptions v) diff --git a/Source/Editor/Windows/Profiler/ProfilerWindow.cs b/Source/Editor/Windows/Profiler/ProfilerWindow.cs index f5a5c6f86..e4ad5d64a 100644 --- a/Source/Editor/Windows/Profiler/ProfilerWindow.cs +++ b/Source/Editor/Windows/Profiler/ProfilerWindow.cs @@ -116,6 +116,10 @@ namespace FlaxEditor.Windows.Profiler Parent = this }; _tabs.SelectedTabChanged += OnSelectedTabChanged; + + InputActions.Add(options => options.ProfilerWindow, Hide); + InputActions.Add(options => options.ProfilerStartStop, () => LiveRecording = !LiveRecording); + InputActions.Add(options => options.ProfilerClear, Clear); } /// diff --git a/Source/Editor/Windows/SceneEditorWindow.cs b/Source/Editor/Windows/SceneEditorWindow.cs index 529eb5ebc..0241207ac 100644 --- a/Source/Editor/Windows/SceneEditorWindow.cs +++ b/Source/Editor/Windows/SceneEditorWindow.cs @@ -52,6 +52,9 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.BuildNav, Editor.BuildNavMesh); InputActions.Add(options => options.BuildSDF, Editor.BuildAllMeshesSDF); InputActions.Add(options => options.TakeScreenshot, Editor.Windows.TakeScreenshot); + InputActions.Add(options => options.ProfilerWindow, () => Editor.Windows.ProfilerWin.FocusOrShow()); + InputActions.Add(options => options.ProfilerStartStop, () => { Editor.Windows.ProfilerWin.LiveRecording = !Editor.Windows.ProfilerWin.LiveRecording; Editor.UI.AddStatusMessage($"Profiling {(Editor.Windows.ProfilerWin.LiveRecording ? "started" : "stopped")}."); }); + InputActions.Add(options => options.ProfilerClear, () => { Editor.Windows.ProfilerWin.Clear(); Editor.UI.AddStatusMessage($"Profiling results cleared."); }); } } } From 52cf4df641eff1b65ce6c1af8e912700b9c72556 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 23 Sep 2023 15:21:33 +0300 Subject: [PATCH 125/188] Add input bindings for File menu actions --- Source/Editor/Modules/UIModule.cs | 23 ++++++---- Source/Editor/Options/InputOptions.cs | 24 ++++++++++ Source/Editor/Utilities/Utils.cs | 53 +++++++++++++++++++++- Source/Editor/Windows/SceneEditorWindow.cs | 37 +-------------- 4 files changed, 92 insertions(+), 45 deletions(-) diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index e675b4fe0..6b3c705cc 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -42,8 +42,10 @@ namespace FlaxEditor.Modules private ContextMenuButton _menuFileSaveScenes; private ContextMenuButton _menuFileCloseScenes; + private ContextMenuButton _menuFileOpenScriptsProject; private ContextMenuButton _menuFileGenerateScriptsProjectFiles; - private ContextMenuButton _menuSaveAll; + private ContextMenuButton _menuFileRecompileScripts; + private ContextMenuButton _menuFileSaveAll; private ContextMenuButton _menuEditUndo; private ContextMenuButton _menuEditRedo; private ContextMenuButton _menuEditCut; @@ -514,13 +516,13 @@ namespace FlaxEditor.Modules MenuFile = MainMenu.AddButton("File"); var cm = MenuFile.ContextMenu; cm.VisibleChanged += OnMenuFileShowHide; - _menuSaveAll = cm.AddButton("Save All", inputOptions.Save, Editor.SaveAll); - _menuFileSaveScenes = cm.AddButton("Save scenes", Editor.Scene.SaveScenes); - _menuFileCloseScenes = cm.AddButton("Close scenes", Editor.Scene.CloseAllScenes); + _menuFileSaveAll = cm.AddButton("Save All", inputOptions.Save, Editor.SaveAll); + _menuFileSaveScenes = cm.AddButton("Save scenes", inputOptions.SaveScenes, Editor.Scene.SaveScenes); + _menuFileCloseScenes = cm.AddButton("Close scenes", inputOptions.CloseScenes, Editor.Scene.CloseAllScenes); cm.AddSeparator(); - cm.AddButton("Open scripts project", Editor.CodeEditing.OpenSolution); - _menuFileGenerateScriptsProjectFiles = cm.AddButton("Generate scripts project files", Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync); - cm.AddButton("Recompile scripts", ScriptsBuilder.Compile); + _menuFileOpenScriptsProject = cm.AddButton("Open scripts project", inputOptions.OpenScriptsProject, Editor.CodeEditing.OpenSolution); + _menuFileGenerateScriptsProjectFiles = cm.AddButton("Generate scripts project files", inputOptions.GenerateScriptsProject, Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync); + _menuFileRecompileScripts = cm.AddButton("Recompile scripts", inputOptions.RecompileScripts, ScriptsBuilder.Compile); cm.AddSeparator(); cm.AddButton("Open project...", OpenProject); cm.AddSeparator(); @@ -639,7 +641,12 @@ namespace FlaxEditor.Modules { var inputOptions = options.Input; - _menuSaveAll.ShortKeys = inputOptions.Save.ToString(); + _menuFileSaveAll.ShortKeys = inputOptions.Save.ToString(); + _menuFileSaveScenes.ShortKeys = inputOptions.SaveScenes.ToString(); + _menuFileCloseScenes.ShortKeys = inputOptions.CloseScenes.ToString(); + _menuFileOpenScriptsProject.ShortKeys = inputOptions.OpenScriptsProject.ToString(); + _menuFileGenerateScriptsProjectFiles.ShortKeys = inputOptions.GenerateScriptsProject.ToString(); + _menuFileRecompileScripts.ShortKeys = inputOptions.RecompileScripts.ToString(); _menuEditUndo.ShortKeys = inputOptions.Undo.ToString(); _menuEditRedo.ShortKeys = inputOptions.Redo.ToString(); _menuEditCut.ShortKeys = inputOptions.Cut.ToString(); diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index b19a46596..90d098bb6 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -78,6 +78,30 @@ namespace FlaxEditor.Options #endregion + #region File + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("File"), EditorOrder(300)] + public InputBinding SaveScenes = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("File"), EditorOrder(310)] + public InputBinding CloseScenes = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("File"), EditorOrder(320)] + public InputBinding OpenScriptsProject = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("File"), EditorOrder(330)] + public InputBinding GenerateScriptsProject = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("File"), EditorOrder(340)] + public InputBinding RecompileScripts = new InputBinding(KeyboardKeys.None); + + #endregion + #region Scene [DefaultValue(typeof(InputBinding), "End")] diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index fe801bbaf..134f9cae4 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -18,10 +18,10 @@ using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Tree; using FlaxEditor.SceneGraph; -using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; +using FlaxEditor.Windows; namespace FlaxEngine { @@ -1235,5 +1235,56 @@ namespace FlaxEditor.Utilities } return s; } + + /// + /// Binds global input actions for the window. + /// + /// The editor window. + public static void SetupCommonInputActions(EditorWindow window) + { + var inputActions = window.InputActions; + + // Setup input actions + inputActions.Add(options => options.Save, Editor.Instance.SaveAll); + inputActions.Add(options => options.Undo, () => + { + Editor.Instance.PerformUndo(); + window.Focus(); + }); + inputActions.Add(options => options.Redo, () => + { + Editor.Instance.PerformRedo(); + window.Focus(); + }); + inputActions.Add(options => options.Cut, Editor.Instance.SceneEditing.Cut); + inputActions.Add(options => options.Copy, Editor.Instance.SceneEditing.Copy); + inputActions.Add(options => options.Paste, Editor.Instance.SceneEditing.Paste); + inputActions.Add(options => options.Duplicate, Editor.Instance.SceneEditing.Duplicate); + inputActions.Add(options => options.SelectAll, Editor.Instance.SceneEditing.SelectAllScenes); + inputActions.Add(options => options.Delete, Editor.Instance.SceneEditing.Delete); + inputActions.Add(options => options.Search, () => Editor.Instance.Windows.SceneWin.Search()); + inputActions.Add(options => options.Play, Editor.Instance.Simulation.DelegatePlayOrStopPlayInEditor); + inputActions.Add(options => options.PlayCurrentScenes, Editor.Instance.Simulation.RequestPlayScenesOrStopPlay); + inputActions.Add(options => options.Pause, Editor.Instance.Simulation.RequestResumeOrPause); + inputActions.Add(options => options.StepFrame, Editor.Instance.Simulation.RequestPlayOneFrame); + inputActions.Add(options => options.CookAndRun, () => Editor.Instance.Windows.GameCookerWin.BuildAndRun()); + inputActions.Add(options => options.RunCookedGame, () => Editor.Instance.Windows.GameCookerWin.RunCooked()); + inputActions.Add(options => options.BuildScenesData, Editor.Instance.BuildScenesOrCancel); + inputActions.Add(options => options.BakeLightmaps, Editor.Instance.BakeLightmapsOrCancel); + inputActions.Add(options => options.ClearLightmaps, Editor.Instance.ClearLightmaps); + inputActions.Add(options => options.BakeEnvProbes, Editor.Instance.BakeAllEnvProbes); + inputActions.Add(options => options.BuildCSG, Editor.Instance.BuildCSG); + inputActions.Add(options => options.BuildNav, Editor.Instance.BuildNavMesh); + inputActions.Add(options => options.BuildSDF, Editor.Instance.BuildAllMeshesSDF); + inputActions.Add(options => options.TakeScreenshot, Editor.Instance.Windows.TakeScreenshot); + inputActions.Add(options => options.ProfilerWindow, () => Editor.Instance.Windows.ProfilerWin.FocusOrShow()); + inputActions.Add(options => options.ProfilerStartStop, () => { Editor.Instance.Windows.ProfilerWin.LiveRecording = !Editor.Instance.Windows.ProfilerWin.LiveRecording; Editor.Instance.UI.AddStatusMessage($"Profiling {(Editor.Instance.Windows.ProfilerWin.LiveRecording ? "started" : "stopped")}."); }); + inputActions.Add(options => options.ProfilerClear, () => { Editor.Instance.Windows.ProfilerWin.Clear(); Editor.Instance.UI.AddStatusMessage($"Profiling results cleared."); }); + inputActions.Add(options => options.SaveScenes, () => Editor.Instance.Scene.SaveScenes()); + inputActions.Add(options => options.CloseScenes, () => Editor.Instance.Scene.CloseAllScenes()); + inputActions.Add(options => options.OpenScriptsProject, () => Editor.Instance.CodeEditing.OpenSolution()); + inputActions.Add(options => options.GenerateScriptsProject, () => Editor.Instance.ProgressReporting.GenerateScriptsProjectFiles.RunAsync()); + inputActions.Add(options => options.RecompileScripts, ScriptsBuilder.Compile); + } } } diff --git a/Source/Editor/Windows/SceneEditorWindow.cs b/Source/Editor/Windows/SceneEditorWindow.cs index 0241207ac..04cce47e8 100644 --- a/Source/Editor/Windows/SceneEditorWindow.cs +++ b/Source/Editor/Windows/SceneEditorWindow.cs @@ -19,42 +19,7 @@ namespace FlaxEditor.Windows protected SceneEditorWindow(Editor editor, bool hideOnClose, ScrollBars scrollBars) : base(editor, hideOnClose, scrollBars) { - // Setup input actions - InputActions.Add(options => options.Save, Editor.SaveAll); - InputActions.Add(options => options.Undo, () => - { - Editor.PerformUndo(); - Focus(); - }); - InputActions.Add(options => options.Redo, () => - { - Editor.PerformRedo(); - Focus(); - }); - InputActions.Add(options => options.Cut, Editor.SceneEditing.Cut); - InputActions.Add(options => options.Copy, Editor.SceneEditing.Copy); - InputActions.Add(options => options.Paste, Editor.SceneEditing.Paste); - InputActions.Add(options => options.Duplicate, Editor.SceneEditing.Duplicate); - InputActions.Add(options => options.SelectAll, Editor.SceneEditing.SelectAllScenes); - InputActions.Add(options => options.Delete, Editor.SceneEditing.Delete); - InputActions.Add(options => options.Search, () => Editor.Windows.SceneWin.Search()); - InputActions.Add(options => options.Play, Editor.Simulation.DelegatePlayOrStopPlayInEditor); - InputActions.Add(options => options.PlayCurrentScenes, Editor.Simulation.RequestPlayScenesOrStopPlay); - InputActions.Add(options => options.Pause, Editor.Simulation.RequestResumeOrPause); - InputActions.Add(options => options.StepFrame, Editor.Simulation.RequestPlayOneFrame); - InputActions.Add(options => options.CookAndRun, () => Editor.Windows.GameCookerWin.BuildAndRun()); - InputActions.Add(options => options.RunCookedGame, () => Editor.Windows.GameCookerWin.RunCooked()); - InputActions.Add(options => options.BuildScenesData, Editor.BuildScenesOrCancel); - InputActions.Add(options => options.BakeLightmaps, Editor.BakeLightmapsOrCancel); - InputActions.Add(options => options.ClearLightmaps, Editor.ClearLightmaps); - InputActions.Add(options => options.BakeEnvProbes, Editor.BakeAllEnvProbes); - InputActions.Add(options => options.BuildCSG, Editor.BuildCSG); - InputActions.Add(options => options.BuildNav, Editor.BuildNavMesh); - InputActions.Add(options => options.BuildSDF, Editor.BuildAllMeshesSDF); - InputActions.Add(options => options.TakeScreenshot, Editor.Windows.TakeScreenshot); - InputActions.Add(options => options.ProfilerWindow, () => Editor.Windows.ProfilerWin.FocusOrShow()); - InputActions.Add(options => options.ProfilerStartStop, () => { Editor.Windows.ProfilerWin.LiveRecording = !Editor.Windows.ProfilerWin.LiveRecording; Editor.UI.AddStatusMessage($"Profiling {(Editor.Windows.ProfilerWin.LiveRecording ? "started" : "stopped")}."); }); - InputActions.Add(options => options.ProfilerClear, () => { Editor.Windows.ProfilerWin.Clear(); Editor.UI.AddStatusMessage($"Profiling results cleared."); }); + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); } } } From 385c3541c97e6cc26b292e0a6f5364c631ebef14 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 23 Sep 2023 15:21:57 +0300 Subject: [PATCH 126/188] Apply global editor bindings in most windows --- Source/Editor/Windows/ContentWindow.cs | 2 ++ Source/Editor/Windows/DebugLogWindow.cs | 1 + Source/Editor/Windows/GameWindow.cs | 9 ++------- Source/Editor/Windows/OutputLogWindow.cs | 1 + Source/Editor/Windows/Profiler/ProfilerWindow.cs | 4 ++-- Source/Editor/Windows/ToolboxWindow.cs | 2 ++ 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 285302d03..6c538f6b8 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -129,6 +129,8 @@ namespace FlaxEditor.Windows Title = "Content"; Icon = editor.Icons.Folder32; + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); + // Content database events editor.ContentDatabase.WorkspaceModified += () => _isWorkspaceDirty = true; editor.ContentDatabase.ItemRemoved += OnContentDatabaseItemRemoved; diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs index 24e38d9af..ab0c07ec5 100644 --- a/Source/Editor/Windows/DebugLogWindow.cs +++ b/Source/Editor/Windows/DebugLogWindow.cs @@ -317,6 +317,7 @@ namespace FlaxEditor.Windows Title = "Debug Log"; Icon = IconInfo; OnEditorOptionsChanged(Editor.Options.Options); + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); // Toolstrip var toolstrip = new ToolStrip(22.0f) diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index ed1799827..bbdf763fb 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -271,6 +271,8 @@ namespace FlaxEditor.Windows Title = "Game"; AutoFocus = true; + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); + var task = MainRenderTask.Instance; // Setup viewport @@ -302,13 +304,6 @@ namespace FlaxEditor.Windows // Link editor options Editor.Options.OptionsChanged += OnOptionsChanged; OnOptionsChanged(Editor.Options.Options); - - InputActions.Add(options => options.Play, Editor.Simulation.DelegatePlayOrStopPlayInEditor); - InputActions.Add(options => options.Pause, Editor.Simulation.RequestResumeOrPause); - InputActions.Add(options => options.StepFrame, Editor.Simulation.RequestPlayOneFrame); - InputActions.Add(options => options.ProfilerWindow, () => Editor.Windows.ProfilerWin.FocusOrShow()); - InputActions.Add(options => options.ProfilerStartStop, () => { Editor.Windows.ProfilerWin.LiveRecording = !Editor.Windows.ProfilerWin.LiveRecording; Editor.UI.AddStatusMessage($"Profiling {(Editor.Windows.ProfilerWin.LiveRecording ? "started" : "stopped")}."); }); - InputActions.Add(options => options.ProfilerClear, () => { Editor.Windows.ProfilerWin.Clear(); Editor.UI.AddStatusMessage($"Profiling results cleared."); }); } private void ChangeViewportRatio(ViewportScaleOptions v) diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index 0b2249a33..4ea31b35e 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -150,6 +150,7 @@ namespace FlaxEditor.Windows { Title = "Output Log"; ClipChildren = false; + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); // Setup UI _viewDropdown = new Button(2, 2, 40.0f, TextBoxBase.DefaultHeight) diff --git a/Source/Editor/Windows/Profiler/ProfilerWindow.cs b/Source/Editor/Windows/Profiler/ProfilerWindow.cs index e4ad5d64a..ee8a7e83e 100644 --- a/Source/Editor/Windows/Profiler/ProfilerWindow.cs +++ b/Source/Editor/Windows/Profiler/ProfilerWindow.cs @@ -117,9 +117,9 @@ namespace FlaxEditor.Windows.Profiler }; _tabs.SelectedTabChanged += OnSelectedTabChanged; + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); + InputActions.Bindings.RemoveAll(x => x.Callback == this.FocusOrShow); InputActions.Add(options => options.ProfilerWindow, Hide); - InputActions.Add(options => options.ProfilerStartStop, () => LiveRecording = !LiveRecording); - InputActions.Add(options => options.ProfilerClear, Clear); } /// diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 3df4d3527..b747149c5 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -353,6 +353,8 @@ namespace FlaxEditor.Windows : base(editor, true, ScrollBars.None) { Title = "Toolbox"; + + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); } /// From 080091c27181196996bb0ef512e9243ec0b77b2b Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 23 Sep 2023 15:35:45 +0300 Subject: [PATCH 127/188] Fix missing bindings for Actor related actions --- Source/Editor/Modules/UIModule.cs | 6 +++--- Source/Editor/Utilities/Utils.cs | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 6b3c705cc..1bb40469b 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -937,7 +937,7 @@ namespace FlaxEditor.Modules Editor.Windows.LoadLayout((string)button.Tag); } - private void AlignViewportWithActor() + internal void AlignViewportWithActor() { var selection = Editor.SceneEditing; if (selection.HasSthSelected && selection.Selection[0] is ActorNode node) @@ -948,7 +948,7 @@ namespace FlaxEditor.Modules } } - private void MoveActorToViewport() + internal void MoveActorToViewport() { var selection = Editor.SceneEditing; if (selection.HasSthSelected && selection.Selection[0] is ActorNode node) @@ -962,7 +962,7 @@ namespace FlaxEditor.Modules } } - private void AlignActorWithViewport() + internal void AlignActorWithViewport() { var selection = Editor.SceneEditing; if (selection.HasSthSelected && selection.Selection[0] is ActorNode node) diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 134f9cae4..863bdbedb 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -1263,6 +1263,10 @@ namespace FlaxEditor.Utilities inputActions.Add(options => options.SelectAll, Editor.Instance.SceneEditing.SelectAllScenes); inputActions.Add(options => options.Delete, Editor.Instance.SceneEditing.Delete); inputActions.Add(options => options.Search, () => Editor.Instance.Windows.SceneWin.Search()); + inputActions.Add(options => options.MoveActorToViewport, Editor.Instance.UI.MoveActorToViewport); + inputActions.Add(options => options.AlignActorWithViewport, Editor.Instance.UI.AlignActorWithViewport); + inputActions.Add(options => options.AlignViewportWithActor, Editor.Instance.UI.AlignViewportWithActor); + inputActions.Add(options => options.PilotActor, Editor.Instance.UI.PilotActor); inputActions.Add(options => options.Play, Editor.Instance.Simulation.DelegatePlayOrStopPlayInEditor); inputActions.Add(options => options.PlayCurrentScenes, Editor.Instance.Simulation.RequestPlayScenesOrStopPlay); inputActions.Add(options => options.Pause, Editor.Instance.Simulation.RequestResumeOrPause); From b6ad2523050e5c0b284e058a331da9e3b5afd26a Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Sat, 23 Sep 2023 19:14:06 +0200 Subject: [PATCH 128/188] - Removed unnecessary null-conditional operator - Removed unnecessary Float2 allocation --- Source/Editor/GUI/Dialogs/Dialog.cs | 2 +- Source/Engine/UI/GUI/Control.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Editor/GUI/Dialogs/Dialog.cs b/Source/Editor/GUI/Dialogs/Dialog.cs index c7d565f9c..07fc3ff0d 100644 --- a/Source/Editor/GUI/Dialogs/Dialog.cs +++ b/Source/Editor/GUI/Dialogs/Dialog.cs @@ -293,7 +293,7 @@ namespace FlaxEditor.GUI.Dialogs if (Root != null) { bool shiftDown = Root.GetKey(KeyboardKeys.Shift); - Root?.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next); + Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next); } return true; } diff --git a/Source/Engine/UI/GUI/Control.cs b/Source/Engine/UI/GUI/Control.cs index 747d407e9..5cb9501c0 100644 --- a/Source/Engine/UI/GUI/Control.cs +++ b/Source/Engine/UI/GUI/Control.cs @@ -634,7 +634,7 @@ namespace FlaxEngine.GUI case NavDirection.Left: return new Float2(0, size.Y * 0.5f); case NavDirection.Right: return new Float2(size.X, size.Y * 0.5f); case NavDirection.Next: return Float2.Zero; - case NavDirection.Previous: return new Float2(Size.X, Size.Y); + case NavDirection.Previous: return size; default: return size * 0.5f; } } From 9021deb49ecfbed77c135506f29f33b8ba8acf5c Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sun, 24 Sep 2023 15:38:12 +0300 Subject: [PATCH 129/188] Fix Vulkan shader compilation with source files missing the last newline --- Source/Engine/ContentImporters/ImportShader.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Source/Engine/ContentImporters/ImportShader.cpp b/Source/Engine/ContentImporters/ImportShader.cpp index 9cfb1242c..bf5b5581d 100644 --- a/Source/Engine/ContentImporters/ImportShader.cpp +++ b/Source/Engine/ContentImporters/ImportShader.cpp @@ -32,14 +32,21 @@ CreateAssetResult ImportShader::Import(CreateAssetContext& context) LOG(Warning, "Empty shader source file."); return CreateAssetResult::Error; } + + // Ensure the source code has an empty line at the end (expected by glslang) + auto sourceCodeChunkSize = sourceCodeSize + 1; + if (sourceCodeText[sourceCodeSize - 1] != '\n') + sourceCodeChunkSize++; + const auto& sourceCodeChunk = context.Data.Header.Chunks[SourceCodeChunk]; - sourceCodeChunk->Data.Allocate(sourceCodeSize + 1); + sourceCodeChunk->Data.Allocate(sourceCodeChunkSize); const auto sourceCode = sourceCodeChunk->Get(); Platform::MemoryCopy(sourceCode, sourceCodeText.Get(), sourceCodeSize); + sourceCode[sourceCodeChunkSize - 2] = '\n'; // Encrypt source code - Encryption::EncryptBytes(sourceCode, sourceCodeSize); - sourceCode[sourceCodeSize] = 0; + Encryption::EncryptBytes(sourceCode, sourceCodeChunkSize - 1); + sourceCode[sourceCodeChunkSize - 1] = 0; // Set Custom Data with Header ShaderStorage::Header20 shaderHeader; From 1e3debf1ec0dca404733d549c3f0aabe3bae02a6 Mon Sep 17 00:00:00 2001 From: Ruan Lucas <79365912+RuanLucasGD@users.noreply.github.com> Date: Sun, 24 Sep 2023 11:37:14 -0400 Subject: [PATCH 130/188] Remake #1413 --- Source/Editor/Windows/Assets/CollisionDataWindow.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Editor/Windows/Assets/CollisionDataWindow.cs b/Source/Editor/Windows/Assets/CollisionDataWindow.cs index cc1daa45b..85d4bbecc 100644 --- a/Source/Editor/Windows/Assets/CollisionDataWindow.cs +++ b/Source/Editor/Windows/Assets/CollisionDataWindow.cs @@ -200,6 +200,7 @@ namespace FlaxEditor.Windows.Assets ViewportCamera = new FPSCamera(), Parent = _split.Panel1 }; + _preview.Task.ViewFlags = ViewFlags.Reflections | ViewFlags.DebugDraw | ViewFlags.AO | ViewFlags.DirectionalLights | ViewFlags.SkyLights | ViewFlags.Shadows | ViewFlags.SpecularLight | ViewFlags.AntiAliasing | ViewFlags.ToneMapping; // Asset properties _propertiesPresenter = new CustomEditorPresenter(null); From 0de31f630f0758d6f240eba6d0726dc53bbae4da Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Sun, 24 Sep 2023 18:40:34 +0200 Subject: [PATCH 131/188] - Moved control point calculation into its own function since multiple parts of the code depend on it - 'DrawConnection' function now calls the control point calculation function - 'IntersectsConnection' function now calls the control point calculation function --- Source/Editor/Surface/Elements/OutputBox.cs | 38 +++++++++++---------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/Source/Editor/Surface/Elements/OutputBox.cs b/Source/Editor/Surface/Elements/OutputBox.cs index 2a2b87486..cf78b559c 100644 --- a/Source/Editor/Surface/Elements/OutputBox.cs +++ b/Source/Editor/Surface/Elements/OutputBox.cs @@ -33,19 +33,8 @@ namespace FlaxEditor.Surface.Elements /// The connection thickness. public static void DrawConnection(ref Float2 start, ref Float2 end, ref Color color, float thickness = 1) { - // Control points parameters - const float minControlLength = 100f; - const float maxControlLength = 150f; - var dst = (end - start).Length; - var yDst = Mathf.Abs(start.Y - end.Y); - // Calculate control points - var minControlDst = dst * 0.5f; - var maxControlDst = Mathf.Max(Mathf.Min(maxControlLength, dst), minControlLength); - var controlDst = Mathf.Lerp(minControlDst, maxControlDst, Mathf.Clamp(yDst / minControlLength, 0f, 1f)); - - var control1 = new Float2(start.X + controlDst, start.Y); - var control2 = new Float2(end.X - controlDst, end.Y); + CalculateBezierControlPoints(start, end, out var control1, out var control2); // Draw line Render2D.DrawBezier(start, control1, control2, end, color, thickness); @@ -58,6 +47,23 @@ namespace FlaxEditor.Surface.Elements */ } + private static void CalculateBezierControlPoints(Float2 start, Float2 end, out Float2 control1, out Float2 control2) + { + // Control points parameters + const float minControlLength = 100f; + const float maxControlLength = 150f; + var dst = (end - start).Length; + var yDst = Mathf.Abs(start.Y - end.Y); + + // Calculate control points + var minControlDst = dst * 0.5f; + var maxControlDst = Mathf.Max(Mathf.Min(maxControlLength, dst), minControlLength); + var controlDst = Mathf.Lerp(minControlDst, maxControlDst, Mathf.Clamp(yDst / minControlLength, 0f, 1f)); + + control1 = new Float2(start.X + controlDst, start.Y); + control2 = new Float2(end.X - controlDst, end.Y); + } + /// /// Checks if a point intersects a connection /// @@ -84,13 +90,9 @@ namespace FlaxEditor.Surface.Elements float offset = Mathf.Sign(end.Y - start.Y) * distance; if ((point.Y - (start.Y - offset)) * ((end.Y + offset) - point.Y) < 0) return false; - - // Taken from the Render2D.DrawBezier code + float squaredDistance = distance; - - var dst = (end - start) * new Float2(0.5f, 0.05f); - var control1 = new Float2(start.X + dst.X, start.Y + dst.Y); - var control2 = new Float2(end.X - dst.X, end.Y + dst.Y); + CalculateBezierControlPoints(start, end, out var control1, out var control2); var d1 = control1 - start; var d2 = control2 - control1; From 87ef35cd4fe9bf40351eba2dba45b8c0ee9fc0bd Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 24 Sep 2023 15:14:28 -0500 Subject: [PATCH 132/188] Add tooltips to move with the mouse cursor. --- Source/Engine/UI/GUI/Tooltip.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index 0a54610fd..6ab40c6aa 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -106,6 +106,7 @@ namespace FlaxEngine.GUI desc.IsTopmost = true; desc.IsRegularWindow = false; desc.HasSizingFrame = false; + desc.ShowAfterFirstPaint = true; _window = Platform.CreateWindow(ref desc); if (_window == null) throw new InvalidOperationException("Failed to create tooltip window."); @@ -197,6 +198,29 @@ namespace FlaxEngine.GUI { // Auto hide if mouse leaves control area var mousePos = Input.MouseScreenPosition; + + // Calculate popup direction + float dpiScale = _showTarget.RootWindow.DpiScale; + var dpiSize = Size * dpiScale; + var locationSS = mousePos; + var monitorBounds = Platform.GetMonitorBounds(locationSS); + var rightBottomMonitorBounds = monitorBounds.BottomRight; + var rightBottomLocationSS = locationSS + dpiSize; + + // Prioritize tooltip placement within parent window, fall back to virtual desktop + if (rightBottomMonitorBounds.Y < rightBottomLocationSS.Y) + { + // Direction: up + locationSS.Y -= dpiSize.Y + 10; + } + if (rightBottomMonitorBounds.X < rightBottomLocationSS.X) + { + // Direction: left + locationSS.X -= dpiSize.X + 20; + } + + _window.Position = locationSS + new Float2(15, 10); + var location = _showTarget.PointFromScreen(mousePos); if (!_showTarget.OnTestTooltipOverControl(ref location)) { From cb89daad36ef4c950d11a21696ea8c32f5361f0e Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 24 Sep 2023 15:15:53 -0500 Subject: [PATCH 133/188] Clean up comments. --- Source/Engine/UI/GUI/Tooltip.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index 6ab40c6aa..d4825b7ca 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -196,7 +196,6 @@ namespace FlaxEngine.GUI /// public override void Update(float deltaTime) { - // Auto hide if mouse leaves control area var mousePos = Input.MouseScreenPosition; // Calculate popup direction @@ -219,8 +218,10 @@ namespace FlaxEngine.GUI locationSS.X -= dpiSize.X + 20; } + // Move window with mouse location _window.Position = locationSS + new Float2(15, 10); + // Auto hide if mouse leaves control area var location = _showTarget.PointFromScreen(mousePos); if (!_showTarget.OnTestTooltipOverControl(ref location)) { From 8a2272b8252b81c3b8583aa8e89b669f25a61922 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 24 Sep 2023 15:40:40 -0500 Subject: [PATCH 134/188] Add has border and border thickness options. --- Source/Engine/UI/GUI/Common/Button.cs | 12 ++++++--- Source/Engine/UI/GUI/Common/CheckBox.cs | 31 ++++++++++++++++------ Source/Engine/UI/GUI/Common/TextBox.cs | 3 ++- Source/Engine/UI/GUI/Common/TextBoxBase.cs | 16 +++++++++-- 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/Button.cs b/Source/Engine/UI/GUI/Common/Button.cs index 88ee0437e..b50f3dd46 100644 --- a/Source/Engine/UI/GUI/Common/Button.cs +++ b/Source/Engine/UI/GUI/Common/Button.cs @@ -84,17 +84,23 @@ namespace FlaxEngine.GUI /// [EditorDisplay("Border Style"), EditorOrder(2010), ExpandGroups] public bool HasBorder { get; set; } = true; + + /// + /// Gets or sets the border thickness. + /// + [EditorDisplay("Border Style"), EditorOrder(2011), Limit(0)] + public float BorderThickness { get; set; } = 1.0f; /// /// Gets or sets the color of the border. /// - [EditorDisplay("Border Style"), EditorOrder(2011), ExpandGroups] + [EditorDisplay("Border Style"), EditorOrder(2012)] public Color BorderColor { get; set; } /// /// Gets or sets the border color when button is highlighted. /// - [EditorDisplay("Border Style"), EditorOrder(2012)] + [EditorDisplay("Border Style"), EditorOrder(2013)] public Color BorderColorHighlighted { get; set; } /// @@ -252,7 +258,7 @@ namespace FlaxEngine.GUI else Render2D.FillRectangle(clientRect, backgroundColor); if (HasBorder) - Render2D.DrawRectangle(clientRect, borderColor); + Render2D.DrawRectangle(clientRect, borderColor, BorderThickness); // Draw text Render2D.DrawText(_font?.GetFont(), TextMaterial, _text, clientRect, textColor, TextAlignment.Center, TextAlignment.Center); diff --git a/Source/Engine/UI/GUI/Common/CheckBox.cs b/Source/Engine/UI/GUI/Common/CheckBox.cs index 939708f36..2f1ff42a9 100644 --- a/Source/Engine/UI/GUI/Common/CheckBox.cs +++ b/Source/Engine/UI/GUI/Common/CheckBox.cs @@ -107,17 +107,29 @@ namespace FlaxEngine.GUI CacheBox(); } } + + /// + /// Gets or sets whether to have a border. + /// + [EditorDisplay("Border Style"), EditorOrder(2010), Tooltip("Whether to have a border."), ExpandGroups] + public bool HasBorder { get; set; } = true; + + /// + /// Gets or sets the border thickness. + /// + [EditorDisplay("Border Style"), EditorOrder(2011), Tooltip("The thickness of the border."), Limit(0)] + public float BorderThickness { get; set; } = 1.0f; /// /// Gets or sets the color of the border. /// - [EditorDisplay("Border Style"), EditorOrder(2010), ExpandGroups] + [EditorDisplay("Border Style"), EditorOrder(2012)] public Color BorderColor { get; set; } /// /// Gets or sets the border color when checkbox is hovered. /// - [EditorDisplay("Border Style"), EditorOrder(2011)] + [EditorDisplay("Border Style"), EditorOrder(2013)] public Color BorderColorHighlighted { get; set; } /// @@ -221,12 +233,15 @@ namespace FlaxEngine.GUI bool enabled = EnabledInHierarchy; // Border - Color borderColor = BorderColor; - if (!enabled) - borderColor *= 0.5f; - else if (_isPressed || _mouseOverBox || IsNavFocused) - borderColor = BorderColorHighlighted; - Render2D.DrawRectangle(_box.MakeExpanded(-2.0f), borderColor); + if (HasBorder) + { + Color borderColor = BorderColor; + if (!enabled) + borderColor *= 0.5f; + else if (_isPressed || _mouseOverBox || IsNavFocused) + borderColor = BorderColorHighlighted; + Render2D.DrawRectangle(_box.MakeExpanded(-2.0f), borderColor, BorderThickness); + } // Icon if (_state != CheckBoxState.Default) diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index 5ec86a94e..ee4f744a6 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -155,7 +155,8 @@ namespace FlaxEngine.GUI if (IsMouseOver || IsNavFocused) backColor = BackgroundSelectedColor; Render2D.FillRectangle(rect, backColor); - Render2D.DrawRectangle(rect, IsFocused ? BorderSelectedColor : BorderColor); + if (HasBorder) + Render2D.DrawRectangle(rect, IsFocused ? BorderSelectedColor : BorderColor, BorderThickness); // Apply view offset and clip mask if (ClipText) diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs index a4ccbe5d2..d7906c176 100644 --- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs @@ -270,16 +270,28 @@ namespace FlaxEngine.GUI [EditorDisplay("Background Style"), EditorOrder(2002), Tooltip("The speed of the selection background flashing animation.")] public float BackgroundSelectedFlashSpeed { get; set; } = 6.0f; + /// + /// Gets or sets whether to have a border. + /// + [EditorDisplay("Border Style"), EditorOrder(2010), Tooltip("Whether to have a border."), ExpandGroups] + public bool HasBorder { get; set; } = true; + + /// + /// Gets or sets the border thickness. + /// + [EditorDisplay("Border Style"), EditorOrder(2011), Tooltip("The thickness of the border."), Limit(0)] + public float BorderThickness { get; set; } = 1.0f; + /// /// Gets or sets the color of the border (Transparent if not used). /// - [EditorDisplay("Border Style"), EditorOrder(2010), Tooltip("The color of the border (Transparent if not used)."), ExpandGroups] + [EditorDisplay("Border Style"), EditorOrder(2012), Tooltip("The color of the border (Transparent if not used).")] public Color BorderColor { get; set; } /// /// Gets or sets the color of the border when control is focused (Transparent if not used). /// - [EditorDisplay("Border Style"), EditorOrder(2011), Tooltip("The color of the border when control is focused (Transparent if not used)")] + [EditorDisplay("Border Style"), EditorOrder(2013), Tooltip("The color of the border when control is focused (Transparent if not used)")] public Color BorderSelectedColor { get; set; } /// From 84ecd4943905f79bdde310890faaf425ea9581cf Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 24 Sep 2023 15:40:56 -0500 Subject: [PATCH 135/188] Fix slider clipping left most pixel of fill bar. --- Source/Engine/UI/GUI/Common/Slider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/UI/GUI/Common/Slider.cs b/Source/Engine/UI/GUI/Common/Slider.cs index 7ea2accef..6dbd5082c 100644 --- a/Source/Engine/UI/GUI/Common/Slider.cs +++ b/Source/Engine/UI/GUI/Common/Slider.cs @@ -268,7 +268,7 @@ public class Slider : ContainerControl // Draw track fill if (FillTrack) { - var fillLineRect = new Rectangle(_thumbSize.X / 2, (Height - TrackHeight - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2, TrackHeight + 2); + var fillLineRect = new Rectangle(_thumbSize.X / 2 - 1, (Height - TrackHeight - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2, TrackHeight + 2); Render2D.PushClip(ref fillLineRect); if (FillTrackBrush != null) FillTrackBrush.Draw(lineRect, TrackFillLineColor); From 90a4fba767d2f538c469b171964861d0a37122c6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 25 Sep 2023 15:39:00 +0200 Subject: [PATCH 136/188] Improve #1490 performance to use `Ordinal` string comparision and just check for extension on some generated files --- Source/Editor/Windows/ContentWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 6e0651d60..c65735c4c 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -994,7 +994,7 @@ namespace FlaxEditor.Windows if (!_showAllFiles) items = items.Where(x => !(x is FileItem)).ToList(); if (!_showGeneratedFiles) - items = items.Where(x => !(x.Path.Contains(".Gen.cs") || x.Path.Contains(".Gen.h") || x.Path.Contains(".Gen.cpp") || x.Path.Contains(".csproj") || x.Path.Contains(".CSharp"))).ToList(); + items = items.Where(x => !(x.Path.EndsWith(".Gen.cs", StringComparison.Ordinal) || x.Path.EndsWith(".Gen.h", StringComparison.Ordinal) || x.Path.EndsWith(".Gen.cpp", StringComparison.Ordinal) || x.Path.EndsWith(".csproj", StringComparison.Ordinal) || x.Path.Contains(".CSharp"))).ToList(); _view.ShowItems(items, _sortType, false, true); } } From 212a2e1dedded799c88a9be9c686f9dee87a9e7f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 25 Sep 2023 15:42:23 +0200 Subject: [PATCH 137/188] Format code from recent PRs --- Source/Editor/Surface/VisjectSurface.Input.cs | 19 ++++++++++++------- Source/Editor/Surface/VisjectSurface.cs | 2 +- Source/Engine/UI/GUI/Common/TextBoxBase.cs | 10 +++++----- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index 6830541b9..5e30ff110 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -213,10 +213,12 @@ namespace FlaxEditor.Surface return; } - if (_middleMouseDown) { + if (_middleMouseDown) + { // Calculate delta var delta = location - _middleMouseDownPos; - if (delta.LengthSquared > 0.01f) { + if (delta.LengthSquared > 0.01f) + { // Move view _mouseMoveAmount += delta.Length; _rootControl.Location += delta; @@ -284,7 +286,8 @@ namespace FlaxEditor.Surface _rightMouseDown = false; Cursor = CursorType.Default; } - if (_middleMouseDown) { + if (_middleMouseDown) + { _middleMouseDown = false; Cursor = CursorType.Default; } @@ -310,7 +313,7 @@ namespace FlaxEditor.Surface if (IsMouseOver && !_leftMouseDown && !IsPrimaryMenuOpened) { var nextViewScale = ViewScale + delta * 0.1f; - + if (delta > 0 && !_rightMouseDown) { // Scale towards mouse when zooming in @@ -325,7 +328,7 @@ namespace FlaxEditor.Surface ViewScale = nextViewScale; ViewCenterPosition = viewCenter; } - + return true; } @@ -419,7 +422,8 @@ namespace FlaxEditor.Surface _rightMouseDown = true; _rightMouseDownPos = location; } - if (button == MouseButton.Middle) { + if (button == MouseButton.Middle) + { _middleMouseDown = true; _middleMouseDownPos = location; } @@ -537,7 +541,8 @@ namespace FlaxEditor.Surface } _mouseMoveAmount = 0; } - if (_middleMouseDown && button == MouseButton.Middle) { + if (_middleMouseDown && button == MouseButton.Middle) + { _middleMouseDown = false; EndMouseCapture(); Cursor = CursorType.Default; diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 29dbeb1c6..6aed7cf68 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -912,7 +912,7 @@ namespace FlaxEditor.Surface { return _context.FindNode(id); } - + /// /// Adds the undo action to be batched (eg. if multiple undo actions is performed in a sequence during single update). /// diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs index 60b201986..9560d863a 100644 --- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs @@ -15,7 +15,7 @@ namespace FlaxEngine.GUI /// The delete control character (used for text filtering). /// protected const char DelChar = (char)0x7F; - + /// /// The text separators (used for words skipping). /// @@ -357,9 +357,9 @@ namespace FlaxEngine.GUI value = value.Replace("\r", ""); // Filter text (handle backspace control character) - if(value.IndexOf(DelChar) != -1) + if (value.IndexOf(DelChar) != -1) value = value.Replace(DelChar.ToString(), ""); - + // Clamp length if (value.Length > MaxLength) value = value.Substring(0, MaxLength); @@ -1341,12 +1341,12 @@ namespace FlaxEngine.GUI if (ctrDown) { int prevWordBegin = FindPrevWordBegin(); - _text = _text.Remove(prevWordBegin, CaretPosition-prevWordBegin); + _text = _text.Remove(prevWordBegin, CaretPosition - prevWordBegin); SetSelection(prevWordBegin); OnTextChanged(); return true; } - + int left = SelectionLeft; if (HasSelection) { From 96b6313acba1bd9bb07f7179c53ce6b29d86b871 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 25 Sep 2023 16:30:24 +0200 Subject: [PATCH 138/188] Fix crash when loading C# assembly from non-ASNSI path #1439 --- .../Engine/Engine/NativeInterop.Unmanaged.cs | 61 +++++++++++-------- Source/Engine/Scripting/Runtime/DotNet.cpp | 8 +-- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 423aae6a5..8198b0aed 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -851,39 +851,46 @@ namespace FlaxEngine.Interop *assemblyFullName = NativeAllocStringAnsi(flaxEngineAssembly.FullName); return GetAssemblyHandle(flaxEngineAssembly); } + try + { + string assemblyPath = Marshal.PtrToStringAnsi(assemblyPathPtr); - string assemblyPath = Marshal.PtrToStringAnsi(assemblyPathPtr); - - Assembly assembly; + Assembly assembly; #if FLAX_EDITOR - // Load assembly from loaded bytes to prevent file locking in Editor - var assemblyBytes = File.ReadAllBytes(assemblyPath); - using MemoryStream stream = new MemoryStream(assemblyBytes); - var pdbPath = Path.ChangeExtension(assemblyPath, "pdb"); - if (File.Exists(pdbPath)) - { - // Load including debug symbols - using FileStream pdbStream = new FileStream(Path.ChangeExtension(assemblyPath, "pdb"), FileMode.Open); - assembly = scriptingAssemblyLoadContext.LoadFromStream(stream, pdbStream); - } - else - { - assembly = scriptingAssemblyLoadContext.LoadFromStream(stream); - } + // Load assembly from loaded bytes to prevent file locking in Editor + var assemblyBytes = File.ReadAllBytes(assemblyPath); + using MemoryStream stream = new MemoryStream(assemblyBytes); + var pdbPath = Path.ChangeExtension(assemblyPath, "pdb"); + if (File.Exists(pdbPath)) + { + // Load including debug symbols + using FileStream pdbStream = new FileStream(Path.ChangeExtension(assemblyPath, "pdb"), FileMode.Open); + assembly = scriptingAssemblyLoadContext.LoadFromStream(stream, pdbStream); + } + else + { + assembly = scriptingAssemblyLoadContext.LoadFromStream(stream); + } #else - // Load assembly from file - assembly = scriptingAssemblyLoadContext.LoadFromAssemblyPath(assemblyPath); + // Load assembly from file + assembly = scriptingAssemblyLoadContext.LoadFromAssemblyPath(assemblyPath); #endif - if (assembly == null) - return new ManagedHandle(); - NativeLibrary.SetDllImportResolver(assembly, NativeLibraryImportResolver); + if (assembly == null) + return new ManagedHandle(); + NativeLibrary.SetDllImportResolver(assembly, NativeLibraryImportResolver); - // Assemblies loaded via streams have no Location: https://github.com/dotnet/runtime/issues/12822 - AssemblyLocations.Add(assembly.FullName, assemblyPath); + // Assemblies loaded via streams have no Location: https://github.com/dotnet/runtime/issues/12822 + AssemblyLocations.Add(assembly.FullName, assemblyPath); - *assemblyName = NativeAllocStringAnsi(assembly.GetName().Name); - *assemblyFullName = NativeAllocStringAnsi(assembly.FullName); - return GetAssemblyHandle(assembly); + *assemblyName = NativeAllocStringAnsi(assembly.GetName().Name); + *assemblyFullName = NativeAllocStringAnsi(assembly.FullName); + return GetAssemblyHandle(assembly); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + return new ManagedHandle(); } [UnmanagedCallersOnly] diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index c859fa961..eccd3cf71 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -704,12 +704,10 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa // TODO: Use new hostfxr delegate load_assembly_bytes? (.NET 8+) // Open .Net assembly const StringAnsi assemblyPathAnsi = assemblyPath.ToStringAnsi(); - const char* name; - const char* fullname; + const char* name = nullptr; + const char* fullname = nullptr; static void* LoadAssemblyImagePtr = GetStaticMethodPointer(TEXT("LoadAssemblyImage")); _handle = CallStaticMethod(LoadAssemblyImagePtr, assemblyPathAnsi.Get(), &name, &fullname); - _name = name; - _fullname = fullname; MCore::GC::FreeMemory((void*)name); MCore::GC::FreeMemory((void*)fullname); if (_handle == nullptr) @@ -717,6 +715,8 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa Log::CLRInnerException(TEXT(".NET assembly image is invalid at ") + assemblyPath); return true; } + _name = name; + _fullname = fullname; CachedAssemblyHandles.Add(_handle, this); // Provide new path of hot-reloaded native library path for managed DllImport From c374127de1d7979f218a49e0f29f5ba9cbed4886 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 25 Sep 2023 16:34:01 +0200 Subject: [PATCH 139/188] Fix error when loading project on non-ASCII path #1439 --- Source/Engine/Engine/NativeInterop.Unmanaged.cs | 2 +- Source/Engine/Scripting/Runtime/DotNet.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 8198b0aed..cfa71f97f 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -853,7 +853,7 @@ namespace FlaxEngine.Interop } try { - string assemblyPath = Marshal.PtrToStringAnsi(assemblyPathPtr); + string assemblyPath = Marshal.PtrToStringUni(assemblyPathPtr); Assembly assembly; #if FLAX_EDITOR diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index eccd3cf71..4a84fca0a 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -703,11 +703,10 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa { // TODO: Use new hostfxr delegate load_assembly_bytes? (.NET 8+) // Open .Net assembly - const StringAnsi assemblyPathAnsi = assemblyPath.ToStringAnsi(); const char* name = nullptr; const char* fullname = nullptr; static void* LoadAssemblyImagePtr = GetStaticMethodPointer(TEXT("LoadAssemblyImage")); - _handle = CallStaticMethod(LoadAssemblyImagePtr, assemblyPathAnsi.Get(), &name, &fullname); + _handle = CallStaticMethod(LoadAssemblyImagePtr, assemblyPath.Get(), &name, &fullname); MCore::GC::FreeMemory((void*)name); MCore::GC::FreeMemory((void*)fullname); if (_handle == nullptr) From 216a5e9f9285c3e6f2c93ba3c7e3d902876f98a5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 25 Sep 2023 16:49:18 +0200 Subject: [PATCH 140/188] Add support for displaying all active Network Peer stats in Editor Profiler tab --- Source/Editor/Windows/Profiler/Network.cs | 11 ++++++----- Source/Engine/Networking/NetworkPeer.cpp | 3 ++- Source/Engine/Networking/NetworkPeer.h | 3 +++ Source/Engine/Networking/NetworkStats.h | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Source/Editor/Windows/Profiler/Network.cs b/Source/Editor/Windows/Profiler/Network.cs index f0b93a03c..f5bbb6471 100644 --- a/Source/Editor/Windows/Profiler/Network.cs +++ b/Source/Editor/Windows/Profiler/Network.cs @@ -65,13 +65,14 @@ namespace FlaxEditor.Windows.Profiler /// public override void Update(ref SharedUpdateData sharedData) { - var peer = FlaxEngine.Networking.NetworkManager.Peer; - if (peer == null) + var peers = FlaxEngine.Networking.NetworkPeer.Peers; + var stats = new FlaxEngine.Networking.NetworkDriverStats(); + foreach (var peer in peers) { - _prevStats = new FlaxEngine.Networking.NetworkDriverStats(); - return; + var peerStats = peer.NetworkDriver.GetStats(); + stats.TotalDataSent += peerStats.TotalDataSent; + stats.TotalDataReceived += peerStats.TotalDataReceived; } - var stats = peer.NetworkDriver.GetStats(); _dataSentChart.AddSample(Mathf.Max((long)stats.TotalDataSent - (long)_prevStats.TotalDataSent, 0)); _dataReceivedChart.AddSample(Mathf.Max((long)stats.TotalDataReceived - (long)_prevStats.TotalDataReceived, 0)); _prevStats = stats; diff --git a/Source/Engine/Networking/NetworkPeer.cpp b/Source/Engine/Networking/NetworkPeer.cpp index d86824156..b39b617e0 100644 --- a/Source/Engine/Networking/NetworkPeer.cpp +++ b/Source/Engine/Networking/NetworkPeer.cpp @@ -8,9 +8,10 @@ #include "Engine/Platform/CPUInfo.h" #include "Engine/Profiler/ProfilerCPU.h" +Array NetworkPeer::Peers; + namespace { - Array Peers; uint32 LastHostId = 0; } diff --git a/Source/Engine/Networking/NetworkPeer.h b/Source/Engine/Networking/NetworkPeer.h index 06d8c1ab9..6b36e3278 100644 --- a/Source/Engine/Networking/NetworkPeer.h +++ b/Source/Engine/Networking/NetworkPeer.h @@ -15,6 +15,9 @@ API_CLASS(sealed, NoSpawn, Namespace = "FlaxEngine.Networking") class FLAXENGINE { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(NetworkPeer, ScriptingObject); + // List with all active peers. + API_FIELD(ReadOnly) static Array Peers; + public: int HostId = -1; NetworkConfig Config; diff --git a/Source/Engine/Networking/NetworkStats.h b/Source/Engine/Networking/NetworkStats.h index 42c946aa0..e6f031f3b 100644 --- a/Source/Engine/Networking/NetworkStats.h +++ b/Source/Engine/Networking/NetworkStats.h @@ -8,7 +8,7 @@ /// /// The network transport driver statistics container. Contains information about INetworkDriver usage and performance. /// -API_STRUCT(Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkDriverStats +API_STRUCT(Namespace="FlaxEngine.Networking", NoDefault) struct FLAXENGINE_API NetworkDriverStats { DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkDriverStats); From da4708825088b18321a4f073344fc7216f243fe9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 25 Sep 2023 18:34:07 +0200 Subject: [PATCH 141/188] Add conditional profiling in Editor (run only when using Profiler window) --- Source/Editor/Windows/Profiler/ProfilerWindow.cs | 8 +++++++- Source/Engine/Engine/Engine.cpp | 3 --- Source/Engine/Profiler/ProfilerCPU.cpp | 2 +- Source/Engine/Profiler/ProfilerGPU.cpp | 2 +- Source/Engine/Profiler/ProfilingTools.cpp | 11 +++++++++++ Source/Engine/Profiler/ProfilingTools.h | 10 ++++++++++ 6 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Source/Editor/Windows/Profiler/ProfilerWindow.cs b/Source/Editor/Windows/Profiler/ProfilerWindow.cs index f5a5c6f86..f68021358 100644 --- a/Source/Editor/Windows/Profiler/ProfilerWindow.cs +++ b/Source/Editor/Windows/Profiler/ProfilerWindow.cs @@ -93,7 +93,7 @@ namespace FlaxEditor.Windows.Profiler _liveRecordingButton = toolstrip.AddButton(editor.Icons.Play64); _liveRecordingButton.LinkTooltip("Live profiling events recording"); _liveRecordingButton.AutoCheck = true; - _liveRecordingButton.Clicked += () => _liveRecordingButton.Icon = LiveRecording ? editor.Icons.Stop64 : editor.Icons.Play64; + _liveRecordingButton.Clicked += OnLiveRecordingChanged; _clearButton = toolstrip.AddButton(editor.Icons.Rotate32, Clear); _clearButton.LinkTooltip("Clear data"); toolstrip.AddSeparator(); @@ -118,6 +118,12 @@ namespace FlaxEditor.Windows.Profiler _tabs.SelectedTabChanged += OnSelectedTabChanged; } + private void OnLiveRecordingChanged() + { + _liveRecordingButton.Icon = LiveRecording ? Editor.Icons.Stop64 : Editor.Icons.Play64; + ProfilingTools.Enabled = LiveRecording; + } + /// /// Adds the mode. /// diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index bafcd30b3..ec56861ba 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -102,9 +102,6 @@ int32 Engine::Main(const Char* cmdLine) Platform::SetHighDpiAwarenessEnabled(!CommandLine::Options.LowDPI.IsTrue()); Time::StartupTime = DateTime::Now(); -#if COMPILE_WITH_PROFILER - ProfilerCPU::Enabled = true; -#endif Globals::StartupFolder = Globals::BinariesFolder = Platform::GetMainDirectory(); #if USE_EDITOR Globals::StartupFolder /= TEXT("../../../.."); diff --git a/Source/Engine/Profiler/ProfilerCPU.cpp b/Source/Engine/Profiler/ProfilerCPU.cpp index 417178747..26f9e49c2 100644 --- a/Source/Engine/Profiler/ProfilerCPU.cpp +++ b/Source/Engine/Profiler/ProfilerCPU.cpp @@ -205,7 +205,7 @@ int32 ProfilerCPU::BeginEvent(const char* name) void ProfilerCPU::EndEvent(int32 index) { - if (Enabled && Thread::Current) + if (index != -1 && Thread::Current) Thread::Current->EndEvent(index); } diff --git a/Source/Engine/Profiler/ProfilerGPU.cpp b/Source/Engine/Profiler/ProfilerGPU.cpp index c100b8df0..3054abe67 100644 --- a/Source/Engine/Profiler/ProfilerGPU.cpp +++ b/Source/Engine/Profiler/ProfilerGPU.cpp @@ -14,7 +14,7 @@ RenderStatsData RenderStatsData::Counter; int32 ProfilerGPU::_depth = 0; Array ProfilerGPU::_timerQueriesPool; Array ProfilerGPU::_timerQueriesFree; -bool ProfilerGPU::Enabled = true; +bool ProfilerGPU::Enabled = false; int32 ProfilerGPU::CurrentBuffer = 0; ProfilerGPU::EventBuffer ProfilerGPU::Buffers[PROFILER_GPU_EVENTS_FRAMES]; diff --git a/Source/Engine/Profiler/ProfilingTools.cpp b/Source/Engine/Profiler/ProfilingTools.cpp index 6ce8082ba..4c6a9e19e 100644 --- a/Source/Engine/Profiler/ProfilingTools.cpp +++ b/Source/Engine/Profiler/ProfilingTools.cpp @@ -175,4 +175,15 @@ void ProfilingToolsService::Dispose() ProfilingTools::EventsGPU.SetCapacity(0); } +bool ProfilingTools::GetEnabled() +{ + return ProfilerCPU::Enabled && ProfilerGPU::Enabled; +} + +void ProfilingTools::SetEnabled(bool enabled) +{ + ProfilerCPU::Enabled = enabled; + ProfilerGPU::Enabled = enabled; +} + #endif diff --git a/Source/Engine/Profiler/ProfilingTools.h b/Source/Engine/Profiler/ProfilingTools.h index e1c010d77..af9b79d80 100644 --- a/Source/Engine/Profiler/ProfilingTools.h +++ b/Source/Engine/Profiler/ProfilingTools.h @@ -106,6 +106,16 @@ public: }; public: + /// + /// Controls the engine profiler (CPU, GPU, etc.) usage. + /// + API_PROPERTY() static bool GetEnabled(); + + /// + /// Controls the engine profiler (CPU, GPU, etc.) usage. + /// + API_PROPERTY() static void SetEnabled(bool enabled); + /// /// The current collected main stats by the profiler from the local session. Updated every frame. /// From 5ced9453d43ae4bf4689a432ba6a9ed577deabc7 Mon Sep 17 00:00:00 2001 From: Christopher Rothert Date: Mon, 25 Sep 2023 15:25:53 +0200 Subject: [PATCH 142/188] Fix editor window closing when using RMB + Ctrl + W for slow camera flight --- Source/Editor/Windows/EditorWindow.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Editor/Windows/EditorWindow.cs b/Source/Editor/Windows/EditorWindow.cs index 60817a51b..ff0673c11 100644 --- a/Source/Editor/Windows/EditorWindow.cs +++ b/Source/Editor/Windows/EditorWindow.cs @@ -192,6 +192,13 @@ namespace FlaxEditor.Windows /// public override bool OnKeyDown(KeyboardKeys key) { + // Prevent closing the editor window when using RMB + Ctrl + W to slow down the camera flight + if (Editor.Options.Options.Input.CloseTab.Process(this, key)) + { + if (Root.GetMouseButton(MouseButton.Right)) + return true; + } + if (base.OnKeyDown(key)) return true; From 1bc7ad5e9dc8668210f4ce2c871334238ae3be81 Mon Sep 17 00:00:00 2001 From: stefnotch Date: Tue, 26 Sep 2023 10:30:57 +0200 Subject: [PATCH 143/188] Update SerializeAttribute.cs --- Source/Engine/Scripting/Attributes/SerializeAttribute.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Scripting/Attributes/SerializeAttribute.cs b/Source/Engine/Scripting/Attributes/SerializeAttribute.cs index 5a557902d..da7f8c308 100644 --- a/Source/Engine/Scripting/Attributes/SerializeAttribute.cs +++ b/Source/Engine/Scripting/Attributes/SerializeAttribute.cs @@ -5,7 +5,8 @@ using System; namespace FlaxEngine { /// - /// Indicates that a field or a property of a serializable class should be serialized. This class cannot be inherited. + /// Indicates that a field or a property of a serializable class should be serialized. + /// The attribute is required to show hidden fields in the editor. /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class SerializeAttribute : Attribute From 6621e70ae7d781fd44607d3a8d33eb48e1aa5d05 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 26 Sep 2023 10:33:12 +0200 Subject: [PATCH 144/188] Add objects replication and RPC stats table to Network Profiler --- Source/Editor/Windows/Profiler/CPU.cs | 15 +- Source/Editor/Windows/Profiler/GPU.cs | 16 +- Source/Editor/Windows/Profiler/Network.cs | 196 +++++++++++++++++- .../Editor/Windows/Profiler/ProfilerMode.cs | 25 +++ .../Editor/Windows/Profiler/SamplesBuffer.cs | 2 + Source/Engine/Networking/NetworkInternal.h | 21 ++ .../Engine/Networking/NetworkReplicator.cpp | 36 +++- Source/Engine/Profiler/ProfilingTools.cpp | 39 ++++ Source/Engine/Profiler/ProfilingTools.h | 23 ++ .../Bindings/BindingsGenerator.CSharp.cs | 4 + 10 files changed, 343 insertions(+), 34 deletions(-) diff --git a/Source/Editor/Windows/Profiler/CPU.cs b/Source/Editor/Windows/Profiler/CPU.cs index 36fbf21f4..fd4061276 100644 --- a/Source/Editor/Windows/Profiler/CPU.cs +++ b/Source/Editor/Windows/Profiler/CPU.cs @@ -429,21 +429,8 @@ namespace FlaxEditor.Windows.Profiler private void UpdateTable(ref ViewRange viewRange) { _table.IsLayoutLocked = true; - int idx = 0; - while (_table.Children.Count > idx) - { - var child = _table.Children[idx]; - if (child is Row row) - { - _tableRowsCache.Add(row); - child.Parent = null; - } - else - { - idx++; - } - } + RecycleTableRows(_table, _tableRowsCache); UpdateTableInner(ref viewRange); _table.UnlockChildrenRecursive(); diff --git a/Source/Editor/Windows/Profiler/GPU.cs b/Source/Editor/Windows/Profiler/GPU.cs index 2cf75a9aa..4ed18691a 100644 --- a/Source/Editor/Windows/Profiler/GPU.cs +++ b/Source/Editor/Windows/Profiler/GPU.cs @@ -298,21 +298,7 @@ namespace FlaxEditor.Windows.Profiler private void UpdateTable() { _table.IsLayoutLocked = true; - int idx = 0; - while (_table.Children.Count > idx) - { - var child = _table.Children[idx]; - if (child is Row row) - { - _tableRowsCache.Add(row); - child.Parent = null; - } - else - { - idx++; - } - } - _table.LockChildrenRecursive(); + RecycleTableRows(_table, _tableRowsCache); UpdateTableInner(); diff --git a/Source/Editor/Windows/Profiler/Network.cs b/Source/Editor/Windows/Profiler/Network.cs index f5bbb6471..dbee0e8e7 100644 --- a/Source/Editor/Windows/Profiler/Network.cs +++ b/Source/Editor/Windows/Profiler/Network.cs @@ -1,8 +1,32 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System; +using System.Collections.Generic; +using FlaxEditor.GUI; using FlaxEngine; using FlaxEngine.GUI; +namespace FlaxEngine +{ + partial class ProfilingTools + { + partial struct NetworkEventStat + { + /// + /// Gets the event name. + /// + public unsafe string Name + { + get + { + fixed (byte* name = Name0) + return System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new IntPtr(name)); + } + } + } + } +} + namespace FlaxEditor.Windows.Profiler { /// @@ -13,6 +37,10 @@ namespace FlaxEditor.Windows.Profiler { private readonly SingleChart _dataSentChart; private readonly SingleChart _dataReceivedChart; + private readonly Table _tableRpc; + private readonly Table _tableRep; + private SamplesBuffer _events; + private List _tableRowsCache; private FlaxEngine.Networking.NetworkDriverStats _prevStats; public Network() @@ -48,11 +76,10 @@ namespace FlaxEditor.Windows.Profiler Parent = layout, }; _dataReceivedChart.SelectedSampleChanged += OnSelectedSampleChanged; - } - private static string FormatSampleBytes(float v) - { - return Utilities.Utils.FormatBytesCount((ulong)v); + // Tables + _tableRpc = InitTable(layout, "RPC Name"); + _tableRep = InitTable(layout, "Replication Name"); } /// @@ -60,11 +87,13 @@ namespace FlaxEditor.Windows.Profiler { _dataSentChart.Clear(); _dataReceivedChart.Clear(); + _events?.Clear(); } /// public override void Update(ref SharedUpdateData sharedData) { + // Gather peer stats var peers = FlaxEngine.Networking.NetworkPeer.Peers; var stats = new FlaxEngine.Networking.NetworkDriverStats(); foreach (var peer in peers) @@ -76,6 +105,12 @@ namespace FlaxEditor.Windows.Profiler _dataSentChart.AddSample(Mathf.Max((long)stats.TotalDataSent - (long)_prevStats.TotalDataSent, 0)); _dataReceivedChart.AddSample(Mathf.Max((long)stats.TotalDataReceived - (long)_prevStats.TotalDataReceived, 0)); _prevStats = stats; + + // Gather network events + var events = ProfilingTools.EventsNetwork; + if (_events == null) + _events = new SamplesBuffer(); + _events.Add(events); } /// @@ -83,6 +118,159 @@ namespace FlaxEditor.Windows.Profiler { _dataSentChart.SelectedSampleIndex = selectedFrame; _dataReceivedChart.SelectedSampleIndex = selectedFrame; + + // Update events tables + if (_events != null) + { + if (_tableRowsCache == null) + _tableRowsCache = new List(); + _tableRpc.IsLayoutLocked = true; + _tableRep.IsLayoutLocked = true; + RecycleTableRows(_tableRpc, _tableRowsCache); + RecycleTableRows(_tableRep, _tableRowsCache); + + var events = _events.Get(selectedFrame); + var rowCount = Int2.Zero; + if (events != null && events.Length != 0) + { + var rowColor2 = Style.Current.Background * 1.4f; + for (int i = 0; i < events.Length; i++) + { + var e = events[i]; + var name = e.Name; + var isRpc = name.Contains("::", StringComparison.Ordinal); + + Row row; + if (_tableRowsCache.Count != 0) + { + var last = _tableRowsCache.Count - 1; + row = _tableRowsCache[last]; + _tableRowsCache.RemoveAt(last); + } + else + { + row = new Row + { + Values = new object[5], + }; + } + { + // Name + row.Values[0] = name; + + // Count + row.Values[1] = (int)e.Count; + + // Data Size + row.Values[2] = (int)e.DataSize; + + // Message Size + row.Values[3] = (int)e.MessageSize; + + // Receivers + row.Values[4] = (float)e.Receivers / (float)e.Count; + } + + var table = isRpc ? _tableRpc : _tableRep; + row.Width = table.Width; + row.BackgroundColor = rowCount[isRpc ? 0 : 1] % 2 == 0 ? rowColor2 : Color.Transparent; + row.Parent = table; + if (isRpc) + rowCount.X++; + else + rowCount.Y++; + } + } + + _tableRpc.Visible = rowCount.X != 0; + _tableRep.Visible = rowCount.Y != 0; + _tableRpc.Children.Sort(SortRows); + _tableRep.Children.Sort(SortRows); + + _tableRpc.UnlockChildrenRecursive(); + _tableRpc.PerformLayout(); + _tableRep.UnlockChildrenRecursive(); + _tableRep.PerformLayout(); + } + } + + /// + public override void OnDestroy() + { + _tableRowsCache?.Clear(); + + base.OnDestroy(); + } + + private static Table InitTable(ContainerControl parent, string name) + { + var headerColor = Style.Current.LightBackground; + var table = new Table + { + Columns = new[] + { + new ColumnDefinition + { + UseExpandCollapseMode = true, + CellAlignment = TextAlignment.Near, + Title = name, + TitleBackgroundColor = headerColor, + }, + new ColumnDefinition + { + Title = "Count", + TitleBackgroundColor = headerColor, + }, + new ColumnDefinition + { + Title = "Data Size", + TitleBackgroundColor = headerColor, + FormatValue = FormatCellBytes, + }, + new ColumnDefinition + { + Title = "Message Size", + TitleBackgroundColor = headerColor, + FormatValue = FormatCellBytes, + }, + new ColumnDefinition + { + Title = "Receivers", + TitleBackgroundColor = headerColor, + }, + }, + Splits = new[] + { + 0.40f, + 0.15f, + 0.15f, + 0.15f, + 0.15f, + }, + Parent = parent, + }; + return table; + } + + private static string FormatSampleBytes(float v) + { + return Utilities.Utils.FormatBytesCount((ulong)v); + } + + private static string FormatCellBytes(object x) + { + return Utilities.Utils.FormatBytesCount((int)x); + } + + private static int SortRows(Control x, Control y) + { + if (x is Row xRow && y is Row yRow) + { + var xDataSize = (int)xRow.Values[2]; + var yDataSize = (int)yRow.Values[2]; + return yDataSize - xDataSize; + } + return 0; } } } diff --git a/Source/Editor/Windows/Profiler/ProfilerMode.cs b/Source/Editor/Windows/Profiler/ProfilerMode.cs index 0cc1a39ee..b8d2f0c4c 100644 --- a/Source/Editor/Windows/Profiler/ProfilerMode.cs +++ b/Source/Editor/Windows/Profiler/ProfilerMode.cs @@ -1,6 +1,8 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; +using FlaxEditor.GUI; using FlaxEditor.GUI.Tabs; using FlaxEngine; @@ -135,5 +137,28 @@ namespace FlaxEditor.Windows.Profiler { SelectedSampleChanged?.Invoke(frameIndex); } + + /// + /// Recycles all table rows to be reused. + /// + /// The table. + /// The output cache. + protected static void RecycleTableRows(Table table, List rowsCache) + { + int idx = 0; + while (table.Children.Count > idx) + { + var child = table.Children[idx]; + if (child is Row row) + { + rowsCache.Add(row); + child.Parent = null; + } + else + { + idx++; + } + } + } } } diff --git a/Source/Editor/Windows/Profiler/SamplesBuffer.cs b/Source/Editor/Windows/Profiler/SamplesBuffer.cs index abc99cfd5..999156dca 100644 --- a/Source/Editor/Windows/Profiler/SamplesBuffer.cs +++ b/Source/Editor/Windows/Profiler/SamplesBuffer.cs @@ -49,6 +49,8 @@ namespace FlaxEditor.Windows.Profiler /// The sample value public T Get(int index) { + if (index >= _data.Length || _data.Length == 0) + return default; return index == -1 ? _data[_count - 1] : _data[index]; } diff --git a/Source/Engine/Networking/NetworkInternal.h b/Source/Engine/Networking/NetworkInternal.h index 521e8a7a2..9484337d1 100644 --- a/Source/Engine/Networking/NetworkInternal.h +++ b/Source/Engine/Networking/NetworkInternal.h @@ -3,6 +3,9 @@ #pragma once #include "Types.h" +#if COMPILE_WITH_PROFILER +#include "Engine/Core/Collections/Dictionary.h" +#endif enum class NetworkMessageIDs : uint8 { @@ -35,4 +38,22 @@ public: static void OnNetworkMessageObjectDespawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); static void OnNetworkMessageObjectRole(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); static void OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); + +#if COMPILE_WITH_PROFILER + + struct ProfilerEvent + { + uint16 Count = 0; + uint16 DataSize = 0; + uint16 MessageSize = 0; + uint16 Receivers = 0; + }; + + /// + /// Enables network usage profiling tools. Captures network objects replication and RPCs send statistics. + /// + static bool EnableProfiling; + + static Dictionary, ProfilerEvent> ProfilerEvents; +#endif }; diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 8133b6cf2..316ac4ae7 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -40,6 +40,11 @@ bool NetworkReplicator::EnableLog = false; #define NETWORK_REPLICATOR_LOG(messageType, format, ...) #endif +#if COMPILE_WITH_PROFILER +bool NetworkInternal::EnableProfiling = false; +Dictionary, NetworkInternal::ProfilerEvent> NetworkInternal::ProfilerEvents; +#endif + PACK_STRUCT(struct NetworkMessageObjectReplicate { NetworkMessageIDs ID = NetworkMessageIDs::ObjectReplicate; @@ -1806,6 +1811,7 @@ void NetworkInternal::NetworkReplicatorUpdate() NetworkMessage msg = peer->BeginSendMessage(); msg.WriteStructure(msgData); msg.WriteBytes(stream->GetBuffer(), msgDataSize); + uint32 dataSize = msgDataSize, messageSize = msg.Length; if (isClient) peer->EndSendMessage(NetworkChannelType::Unreliable, msg); else @@ -1824,6 +1830,8 @@ void NetworkInternal::NetworkReplicatorUpdate() msg = peer->BeginSendMessage(); msg.WriteStructure(msgDataPart); msg.WriteBytes(stream->GetBuffer() + msgDataPart.PartStart, msgDataPart.PartSize); + messageSize += msg.Length; + dataSize += msgDataPart.PartSize; dataStart += msgDataPart.PartSize; if (isClient) peer->EndSendMessage(NetworkChannelType::Unreliable, msg); @@ -1832,7 +1840,18 @@ void NetworkInternal::NetworkReplicatorUpdate() } ASSERT_LOW_LAYER(dataStart == size); - // TODO: stats for bytes send per object type +#if COMPILE_WITH_PROFILER + // Network stats recording + if (EnableProfiling) + { + const Pair name(obj->GetTypeHandle(), StringAnsiView::Empty); + auto& profileEvent = ProfilerEvents[name]; + profileEvent.Count++; + profileEvent.DataSize += dataSize; + profileEvent.MessageSize += messageSize; + profileEvent.Receivers += isClient ? 1 : CachedTargets.Count(); + } +#endif } } @@ -1873,6 +1892,7 @@ void NetworkInternal::NetworkReplicatorUpdate() NetworkMessage msg = peer->BeginSendMessage(); msg.WriteStructure(msgData); msg.WriteBytes(e.ArgsData.Get(), e.ArgsData.Length()); + uint32 dataSize = e.ArgsData.Length(), messageSize = msg.Length, receivers = 0; NetworkChannelType channel = (NetworkChannelType)e.Info.Channel; if (e.Info.Server && isClient) { @@ -1882,13 +1902,27 @@ void NetworkInternal::NetworkReplicatorUpdate() NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Server RPC '{}::{}' called with non-empty list of targets is not supported (only server will receive it)", e.Name.First.ToString(), e.Name.Second.ToString()); #endif peer->EndSendMessage(channel, msg); + receivers = 1; } else if (e.Info.Client && (isServer || isHost)) { // Server -> Client(s) BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, e.Targets, NetworkManager::LocalClientId); peer->EndSendMessage(channel, msg, CachedTargets); + receivers = CachedTargets.Count(); } + +#if COMPILE_WITH_PROFILER + // Network stats recording + if (EnableProfiling && receivers) + { + auto& profileEvent = ProfilerEvents[e.Name]; + profileEvent.Count++; + profileEvent.DataSize += dataSize; + profileEvent.MessageSize += messageSize; + profileEvent.Receivers += receivers; + } +#endif } RpcQueue.Clear(); } diff --git a/Source/Engine/Profiler/ProfilingTools.cpp b/Source/Engine/Profiler/ProfilingTools.cpp index 4c6a9e19e..61c0e2c33 100644 --- a/Source/Engine/Profiler/ProfilingTools.cpp +++ b/Source/Engine/Profiler/ProfilingTools.cpp @@ -3,14 +3,17 @@ #if COMPILE_WITH_PROFILER #include "ProfilingTools.h" +#include "Engine/Core/Types/Pair.h" #include "Engine/Engine/Engine.h" #include "Engine/Engine/Time.h" #include "Engine/Engine/EngineService.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Networking/NetworkInternal.h" ProfilingTools::MainStats ProfilingTools::Stats; Array> ProfilingTools::EventsCPU; Array ProfilingTools::EventsGPU; +Array ProfilingTools::EventsNetwork; class ProfilingToolsService : public EngineService { @@ -120,6 +123,40 @@ void ProfilingToolsService::Update() frame.Extract(ProfilingTools::EventsGPU); } + // Get the last events from networking runtime + { + auto& networkEvents = ProfilingTools::EventsNetwork; + networkEvents.Resize(NetworkInternal::ProfilerEvents.Count()); + int32 i = 0; + for (const auto& e : NetworkInternal::ProfilerEvents) + { + const auto& src = e.Value; + auto& dst = networkEvents[i++]; + dst.Count = src.Count; + dst.DataSize = src.DataSize; + dst.MessageSize = src.MessageSize; + dst.Receivers = src.Receivers; + const StringAnsiView& typeName = e.Key.First.GetType().Fullname; + uint64 len = Math::Min(typeName.Length(), ARRAY_COUNT(dst.Name) - 10); + Platform::MemoryCopy(dst.Name, typeName.Get(), len); + const StringAnsiView& name = e.Key.Second; + if (name.HasChars()) + { + uint64 pos = len; + dst.Name[pos++] = ':'; + dst.Name[pos++] = ':'; + len = Math::Min(name.Length(), ARRAY_COUNT(dst.Name) - pos - 1); + Platform::MemoryCopy(dst.Name + pos, name.Get(), len); + dst.Name[pos + len] = 0; + } + else + { + dst.Name[len] = 0; + } + } + NetworkInternal::ProfilerEvents.Clear(); + } + #if 0 // Print CPU events to the log { @@ -173,6 +210,7 @@ void ProfilingToolsService::Dispose() ProfilingTools::EventsCPU.Clear(); ProfilingTools::EventsCPU.SetCapacity(0); ProfilingTools::EventsGPU.SetCapacity(0); + ProfilingTools::EventsNetwork.SetCapacity(0); } bool ProfilingTools::GetEnabled() @@ -184,6 +222,7 @@ void ProfilingTools::SetEnabled(bool enabled) { ProfilerCPU::Enabled = enabled; ProfilerGPU::Enabled = enabled; + NetworkInternal::EnableProfiling = enabled; } #endif diff --git a/Source/Engine/Profiler/ProfilingTools.h b/Source/Engine/Profiler/ProfilingTools.h index af9b79d80..f4039472f 100644 --- a/Source/Engine/Profiler/ProfilingTools.h +++ b/Source/Engine/Profiler/ProfilingTools.h @@ -105,6 +105,24 @@ public: API_FIELD() Array Events; }; + /// + /// The network stat. + /// + API_STRUCT(NoDefault) struct NetworkEventStat + { + DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkEventStat); + + // Amount of occurrences. + API_FIELD() uint16 Count; + // Transferred data size (in bytes). + API_FIELD() uint16 DataSize; + // Transferred message (data+header) size (in bytes). + API_FIELD() uint16 MessageSize; + // Amount of peers that will receive this message. + API_FIELD() uint16 Receivers; + API_FIELD(Private, NoArray) byte Name[120]; + }; + public: /// /// Controls the engine profiler (CPU, GPU, etc.) usage. @@ -130,6 +148,11 @@ public: /// The GPU rendering profiler events. /// API_FIELD(ReadOnly) static Array EventsGPU; + + /// + /// The networking profiler events. + /// + API_FIELD(ReadOnly) static Array EventsNetwork; }; #endif diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 3685cb271..00ab0d4b2 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -1767,6 +1767,10 @@ namespace Flax.Build.Bindings // char's are not blittable, store as short instead contents.Append($"fixed short {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}]; // {managedType}*").AppendLine(); } + else if (managedType == "byte") + { + contents.Append($"fixed byte {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}]; // {managedType}*").AppendLine(); + } else #endif { From e8088f42ac504dbfc928b6c75b81b901e7a491c4 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Tue, 26 Sep 2023 17:37:53 +0300 Subject: [PATCH 145/188] Fix string use-after-free while loading managed assemblies --- Source/Engine/Scripting/Runtime/DotNet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 4a84fca0a..685e0ec91 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -707,6 +707,8 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa const char* fullname = nullptr; static void* LoadAssemblyImagePtr = GetStaticMethodPointer(TEXT("LoadAssemblyImage")); _handle = CallStaticMethod(LoadAssemblyImagePtr, assemblyPath.Get(), &name, &fullname); + _name = StringAnsi(name); + _fullname = StringAnsi(fullname); MCore::GC::FreeMemory((void*)name); MCore::GC::FreeMemory((void*)fullname); if (_handle == nullptr) @@ -714,8 +716,6 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa Log::CLRInnerException(TEXT(".NET assembly image is invalid at ") + assemblyPath); return true; } - _name = name; - _fullname = fullname; CachedAssemblyHandles.Add(_handle, this); // Provide new path of hot-reloaded native library path for managed DllImport From e5e37f3a41ec4d25e098bc8b44cb72c8b9c51b70 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Sep 2023 11:31:52 +0200 Subject: [PATCH 146/188] Add `android_native_app_glue.h` to be included in engine package --- Source/Engine/Main/Main.Build.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Main/Main.Build.cs b/Source/Engine/Main/Main.Build.cs index 27c9fb611..120d0bc5c 100644 --- a/Source/Engine/Main/Main.Build.cs +++ b/Source/Engine/Main/Main.Build.cs @@ -81,5 +81,6 @@ public class Main : EngineModule /// public override void GetFilesToDeploy(List files) { + files.Add(Path.Combine(FolderPath, "Android/android_native_app_glue.h")); } } From 523a14af56c50811b47664c0b92184a9ac77d44a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Sep 2023 13:04:14 +0200 Subject: [PATCH 147/188] Fix missing `Variant` marshalling for scripting structures bindings #1395 --- .../Flax.Build/Bindings/BindingsGenerator.CSharp.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 00ab0d4b2..2d97ba7c7 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -1512,8 +1512,8 @@ namespace Flax.Build.Bindings type = "IntPtr"; else if (type == "bool") type = "byte"; - else if (type == "object") - type = "NativeVariant"; + else if (fieldInfo.Type.Type == "Variant") + type = "IntPtr"; else if (internalType) { internalTypeMarshaller = type + "Marshaller"; @@ -1530,9 +1530,6 @@ namespace Flax.Build.Bindings if (fieldInfo.NoArray && fieldInfo.Type.IsArray) continue; - if (type == "NativeVariant") - continue; // TODO: FIXME - if (useSeparator) { toManagedContent.Append(", "); @@ -1634,6 +1631,12 @@ namespace Flax.Build.Bindings toManagedContent.Append($"managed.{fieldInfo.Name} != 0"); toNativeContent.Append($"managed.{fieldInfo.Name} ? (byte)1 : (byte)0"); } + else if (fieldInfo.Type.Type == "Variant") + { + // Variant passed as boxed object handle + toManagedContent.Append($"ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(managed.{fieldInfo.Name})"); + toNativeContent.Append($"ManagedHandleMarshaller.NativeToManaged.ConvertToUnmanaged(managed.{fieldInfo.Name})"); + } else if (internalType) { toManagedContent.Append($"{internalTypeMarshaller}.ToManaged(managed.{fieldInfo.Name})"); From b00156f519a49bd32f663988ba65741029f0cc6f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Sep 2023 13:04:44 +0200 Subject: [PATCH 148/188] Add some improvements to particles editing #1395 --- .../Dedicated/ParticleEffectEditor.cs | 17 ++++++++++++++--- Source/Engine/Particles/ParticleEffect.cpp | 3 +-- Source/Engine/Particles/ParticleEffect.h | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs b/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs index ec71348cf..5b2cfc144 100644 --- a/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs @@ -25,9 +25,20 @@ namespace FlaxEditor.CustomEditors.Dedicated get { // All selected particle effects use the same system - var effect = (ParticleEffect)Values[0]; - var system = effect.ParticleSystem; - return system != null && Values.TrueForAll(x => (x as ParticleEffect)?.ParticleSystem == system); + var effect = Values[0] as ParticleEffect; + var system = effect ? effect.ParticleSystem : null; + if (system && Values.TrueForAll(x => x is ParticleEffect fx && fx && fx.ParticleSystem == system)) + { + // All parameters can be accessed + var parameters = effect.Parameters; + foreach (var parameter in parameters) + { + if (!parameter) + return false; + } + return true; + } + return false; } } diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 8a767ba22..788a4263f 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -402,7 +402,7 @@ SceneRenderTask* ParticleEffect::GetRenderTask() const #if USE_EDITOR -Array ParticleEffect::GetParametersOverrides() +Array& ParticleEffect::GetParametersOverrides() { CacheModifiedParameters(); return _parametersOverrides; @@ -461,7 +461,6 @@ void ParticleEffect::CacheModifiedParameters() { if (_parameters.IsEmpty()) return; - _parametersOverrides.Clear(); auto& parameters = GetParameters(); for (auto& param : parameters) diff --git a/Source/Engine/Particles/ParticleEffect.h b/Source/Engine/Particles/ParticleEffect.h index bfee23a3b..9e9792a4c 100644 --- a/Source/Engine/Particles/ParticleEffect.h +++ b/Source/Engine/Particles/ParticleEffect.h @@ -382,7 +382,7 @@ public: #if USE_EDITOR protected: // Exposed parameters overrides for Editor Undo. - API_PROPERTY(Attributes="HideInEditor, Serialize") Array GetParametersOverrides(); + API_PROPERTY(Attributes="HideInEditor, Serialize") Array& GetParametersOverrides(); API_PROPERTY() void SetParametersOverrides(const Array& value); #endif From 44292efa04c7470cee87bc53abdf64f7c5ca35cb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Sep 2023 13:38:03 +0200 Subject: [PATCH 149/188] Fix editing array of tags in Editor that is `null` initially #1451 --- Source/Editor/CustomEditors/Editors/TagEditor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index 3d2dd86aa..61e0bba7d 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -674,9 +674,9 @@ namespace FlaxEditor.CustomEditors.Editors } set { - if (Values[0] is Tag[]) + if (Values[0] is Tag[] || Values.Type.Type == typeof(Tag[])) SetValue(value); - if (Values[0] is List) + else if (Values[0] is List || Values.Type.Type == typeof(List)) SetValue(new List(value)); } } From bff18a5564e90540c4be766c22abebe0d39c5074 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Wed, 27 Sep 2023 18:41:22 +0300 Subject: [PATCH 150/188] Add `-genproject -rider` option for generating Rider compatible solution files --- Source/Tools/Flax.Build/Build/Builder.Projects.cs | 4 +++- Source/Tools/Flax.Build/Configuration.cs | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Build/Builder.Projects.cs b/Source/Tools/Flax.Build/Build/Builder.Projects.cs index 166e78036..d2b0a7f63 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Projects.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Projects.cs @@ -179,7 +179,7 @@ namespace Flax.Build using (new ProfileEventScope("GenerateProjects")) { // Pick the project format - List projectFormats = new List(); + HashSet projectFormats = new HashSet(); if (Configuration.ProjectFormatVS2022) projectFormats.Add(ProjectFormat.VisualStudio2022); @@ -191,6 +191,8 @@ namespace Flax.Build projectFormats.Add(ProjectFormat.VisualStudio2015); if (Configuration.ProjectFormatVSCode) projectFormats.Add(ProjectFormat.VisualStudioCode); + if (Configuration.ProjectFormatRider) + projectFormats.Add(ProjectFormat.VisualStudio2022); if (!string.IsNullOrEmpty(Configuration.ProjectFormatCustom)) projectFormats.Add(ProjectFormat.Custom); diff --git a/Source/Tools/Flax.Build/Configuration.cs b/Source/Tools/Flax.Build/Configuration.cs index 8fa17a235..19522f403 100644 --- a/Source/Tools/Flax.Build/Configuration.cs +++ b/Source/Tools/Flax.Build/Configuration.cs @@ -213,6 +213,12 @@ namespace Flax.Build [CommandLine("vscode", "Generates Visual Studio Code project format files. Valid only with -genproject option.")] public static bool ProjectFormatVSCode = false; + /// + /// Generates Visual Studio 2022 project format files for Rider. Valid only with -genproject option. + /// + [CommandLine("rider", "Generates Visual Studio 2022 project format files for Rider. Valid only with -genproject option.")] + public static bool ProjectFormatRider = false; + /// /// Generates code project files for a custom project format type. Valid only with -genproject option. /// From 0c00dc20a20c8fe091777b80f4e640ad9f4d5221 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Wed, 27 Sep 2023 18:42:16 +0300 Subject: [PATCH 151/188] Always generate additional Visual Studio solution files for all platforms --- Source/Tools/Flax.Build/Build/Builder.Projects.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Tools/Flax.Build/Build/Builder.Projects.cs b/Source/Tools/Flax.Build/Build/Builder.Projects.cs index d2b0a7f63..d16e2930b 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Projects.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Projects.cs @@ -199,6 +199,9 @@ namespace Flax.Build if (projectFormats.Count == 0) projectFormats.Add(Platform.BuildPlatform.DefaultProjectFormat); + // Always generate VS solution files for project (needed for C# Intellisense support) + projectFormats.Add(ProjectFormat.VisualStudio2022); + foreach (ProjectFormat projectFormat in projectFormats) GenerateProject(projectFormat); } From 22e5afdb6f0ffbfa1180f9e2517a5351118266cf Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Wed, 27 Sep 2023 20:16:46 +0300 Subject: [PATCH 152/188] Fix Visual Studio project GUIDs getting randomized during regeneration --- .../Flax.Build/Build/Builder.Projects.cs | 10 +++-- Source/Tools/Flax.Build/Projects/Project.cs | 4 +- .../Flax.Build/Projects/ProjectGenerator.cs | 2 +- .../VisualStudio/CSProjectGenerator.cs | 7 +++- .../VisualStudio/CSSDKProjectGenerator.cs | 7 +++- .../VisualStudio/VCProjectGenerator.cs | 9 +++- .../VisualStudio/VisualStudioProject.cs | 15 ------- .../VisualStudioProjectGenerator.cs | 42 ++++++++++++------- .../VisualStudioCodeProjectGenerator.cs | 2 +- .../Projects/XCodeProjectGenerator.cs | 2 +- 10 files changed, 59 insertions(+), 41 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/Builder.Projects.cs b/Source/Tools/Flax.Build/Build/Builder.Projects.cs index d16e2930b..3e403e956 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Projects.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Projects.cs @@ -228,6 +228,8 @@ namespace Flax.Build var projectToModulesBuildOptions = new Dictionary>(); Project mainSolutionProject = null; ProjectGenerator nativeProjectGenerator = ProjectGenerator.Create(projectFormat, TargetType.NativeCpp); + var solutionName = rootProject.Name; + var solutionPath = Path.Combine(workspaceRoot, solutionName + '.' + nativeProjectGenerator.SolutionFileExtension); // Group targets by project name and sort groups based on the project (ensures that referenced plugin source projects are generated firstly before main source projects) var targetGroups = new List(); @@ -549,7 +551,7 @@ namespace Flax.Build foreach (var project in projects) { Log.Verbose(project.Name + " -> " + project.Path); - project.Generate(); + project.Generate(solutionPath); } } @@ -628,7 +630,7 @@ namespace Flax.Build using (new ProfileEventScope("GenerateProject")) { Log.Verbose("Project " + rulesProjectName + " -> " + project.Path); - dotNetProjectGenerator.GenerateProject(project); + dotNetProjectGenerator.GenerateProject(project, solutionPath); } projects.Add(project); @@ -641,9 +643,9 @@ namespace Flax.Build using (new ProfileEventScope("CreateSolution")) { solution = nativeProjectGenerator.CreateSolution(); - solution.Name = rootProject.Name; + solution.Name = solutionName; solution.WorkspaceRootPath = workspaceRoot; - solution.Path = Path.Combine(workspaceRoot, solution.Name + '.' + nativeProjectGenerator.SolutionFileExtension); + solution.Path = solutionPath; solution.Projects = projects.ToArray(); solution.MainProject = mainSolutionProject; } diff --git a/Source/Tools/Flax.Build/Projects/Project.cs b/Source/Tools/Flax.Build/Projects/Project.cs index 148e97485..0c98f73bb 100644 --- a/Source/Tools/Flax.Build/Projects/Project.cs +++ b/Source/Tools/Flax.Build/Projects/Project.cs @@ -253,9 +253,9 @@ namespace Flax.Build.Projects /// /// Generates the project. /// - public virtual void Generate() + public virtual void Generate(string solutionPath) { - Generator.GenerateProject(this); + Generator.GenerateProject(this, solutionPath); } /// diff --git a/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs index 8641cf81b..a3da268a9 100644 --- a/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs @@ -52,7 +52,7 @@ namespace Flax.Build.Projects /// Generates the project. /// /// The project. - public abstract void GenerateProject(Project project); + public abstract void GenerateProject(Project project, string solutionPath); /// /// Generates the solution. diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs index 266097a3f..4e4e7dbfe 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs @@ -26,7 +26,7 @@ namespace Flax.Build.Projects.VisualStudio public override TargetType? Type => TargetType.DotNet; /// - public override void GenerateProject(Project project) + public override void GenerateProject(Project project, string solutionPath) { var csProjectFileContent = new StringBuilder(); @@ -49,6 +49,11 @@ namespace Flax.Build.Projects.VisualStudio if (vsProject.CSharp.UseFlaxVS && VisualStudioInstance.HasFlaxVS) projectTypes = ProjectTypeGuids.ToOption(ProjectTypeGuids.FlaxVS) + ';' + projectTypes; + // Try to reuse the existing project guid from solution file + vsProject.ProjectGuid = GetProjectGuid(solutionPath, vsProject.Name); + if (vsProject.ProjectGuid == Guid.Empty) + vsProject.ProjectGuid = Guid.NewGuid(); + // Header csProjectFileContent.AppendLine(""); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs index d8aece342..387a0dcff 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs @@ -26,7 +26,7 @@ namespace Flax.Build.Projects.VisualStudio public override TargetType? Type => TargetType.DotNetCore; /// - public override void GenerateProject(Project project) + public override void GenerateProject(Project project, string solutionPath) { var csProjectFileContent = new StringBuilder(); @@ -53,6 +53,11 @@ namespace Flax.Build.Projects.VisualStudio } } + // Try to reuse the existing project guid from solution file + vsProject.ProjectGuid = GetProjectGuid(solutionPath, vsProject.Name); + if (vsProject.ProjectGuid == Guid.Empty) + vsProject.ProjectGuid = Guid.NewGuid(); + // Header csProjectFileContent.AppendLine(""); csProjectFileContent.AppendLine(""); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs index 3e2a6c9b7..84607f6a1 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs @@ -55,7 +55,7 @@ namespace Flax.Build.Projects.VisualStudio } /// - public override void GenerateProject(Project project) + public override void GenerateProject(Project project, string solutionPath) { var vcProjectFileContent = new StringBuilder(); var vcFiltersFileContent = new StringBuilder(); @@ -67,6 +67,13 @@ namespace Flax.Build.Projects.VisualStudio var projectDirectory = Path.GetDirectoryName(project.Path); var filtersDirectory = project.SourceFolderPath; + // Try to reuse the existing project guid from existing files + vsProject.ProjectGuid = GetProjectGuid(vsProject.Path, vsProject.Name); + if (vsProject.ProjectGuid == Guid.Empty) + vsProject.ProjectGuid = GetProjectGuid(solutionPath, vsProject.Name); + if (vsProject.ProjectGuid == Guid.Empty) + vsProject.ProjectGuid = Guid.NewGuid(); + // Header vcProjectFileContent.AppendLine(""); vcProjectFileContent.AppendLine(string.Format("", projectFileToolVersion)); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProject.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProject.cs index 5ec0ca8e4..b9b2d2383 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProject.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProject.cs @@ -36,20 +36,5 @@ namespace Flax.Build.Projects.VisualStudio } } } - - /// - public override string Path - { - get => base.Path; - set - { - base.Path = value; - - if (ProjectGuid == Guid.Empty) - { - ProjectGuid = VisualStudioProjectGenerator.GetProjectGuid(Path); - } - } - } } } diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index ecf70f9f8..6dcc12a4f 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -22,7 +22,7 @@ namespace Flax.Build.Projects.VisualStudio public override Guid ProjectTypeGuid => ProjectTypeGuids.Android; /// - public override void Generate() + public override void Generate(string solutionPath) { var gen = (VisualStudioProjectGenerator)Generator; var projectFileToolVersion = gen.ProjectFileToolVersion; @@ -141,9 +141,10 @@ namespace Flax.Build.Projects.VisualStudio /// /// The path. /// The project ID. - public static Guid GetProjectGuid(string path) + public static Guid GetProjectGuid(string path, string projectName) { - if (File.Exists(path)) + // Look up for the guid in VC++-project file + if (File.Exists(path) && Path.GetExtension(path).Equals(".vcxproj", StringComparison.OrdinalIgnoreCase)) { try { @@ -161,8 +162,25 @@ namespace Flax.Build.Projects.VisualStudio // Hide errors } } + if (File.Exists(path) && Path.GetExtension(path).Equals(".sln", StringComparison.OrdinalIgnoreCase)) + { + try + { + Regex projectRegex = new Regex(@"Project\(.*\) = \""(\S+)\"", \""(\S+)\"", \""{(\S+)}\"""); + MatchCollection matches = projectRegex.Matches(File.ReadAllText(path)); + for (int i = 0; i < matches.Count; i++) + { + if (matches[i].Groups[1].Value == projectName) + return Guid.ParseExact(matches[i].Groups[3].Value, "D"); + } + } + catch + { + // Hide errors + } + } - return Guid.NewGuid(); + return Guid.Empty; } /// @@ -250,7 +268,7 @@ namespace Flax.Build.Projects.VisualStudio } } - // Try to extract info from the existing solution file to make random IDs stable + // Try to extract solution folder info from the existing solution file to make random IDs stable var solutionId = Guid.NewGuid(); var folderIds = new Dictionary(); if (File.Exists(solution.Path)) @@ -266,16 +284,12 @@ namespace Flax.Build.Projects.VisualStudio solutionId = Guid.ParseExact(value.Substring(15), "B"); } - var folderIdsMatch = Regex.Match(contents, "Project\\(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\"\\) = \"(.*?)\", \"(.*?)\", \"{(.*?)}\""); - if (folderIdsMatch.Success) + var folderIdMatches = new Regex("Project\\(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\"\\) = \"(.*?)\", \"(.*?)\", \"{(.*?)}\"").Matches(contents); + foreach (Match match in folderIdMatches) { - foreach (Capture capture in folderIdsMatch.Captures) - { - var value = capture.Value.Substring("Project(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"".Length); - var folder = value.Substring(0, value.IndexOf('\"')); - var folderId = Guid.ParseExact(value.Substring(folder.Length * 2 + "\", \"".Length + "\", \"".Length, 38), "B"); - folderIds["Source\\" + folder] = folderId; - } + var folder = match.Groups[1].Value; + var folderId = Guid.ParseExact(match.Groups[3].Value, "D"); + folderIds[folder] = folderId; } } catch (Exception ex) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs index eb6c2bb24..fdf1df4e2 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs @@ -38,7 +38,7 @@ namespace Flax.Build.Projects.VisualStudioCode } /// - public override void GenerateProject(Project project) + public override void GenerateProject(Project project, string solutionPath) { // Not used, solution contains all projects definitions } diff --git a/Source/Tools/Flax.Build/Projects/XCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/XCodeProjectGenerator.cs index 48ba9c0f0..85ac0ff7e 100644 --- a/Source/Tools/Flax.Build/Projects/XCodeProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/XCodeProjectGenerator.cs @@ -42,7 +42,7 @@ namespace Flax.Build.Projects } /// - public override void GenerateProject(Project project) + public override void GenerateProject(Project project, string solutionPath) { } From d7db26cf898294413775667525ab3093effb8c4f Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Wed, 27 Sep 2023 21:45:55 +0300 Subject: [PATCH 153/188] Detect latest versions of clang --- Source/Tools/Flax.Build/Platforms/Linux/LinuxPlatform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Platforms/Linux/LinuxPlatform.cs b/Source/Tools/Flax.Build/Platforms/Linux/LinuxPlatform.cs index 2fabb2c46..44a2c247a 100644 --- a/Source/Tools/Flax.Build/Platforms/Linux/LinuxPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Linux/LinuxPlatform.cs @@ -44,7 +44,7 @@ namespace Flax.Build.Platforms Compiler = Configuration.Compiler; else { - for (int ver = 15; ver >= 6; ver--) + for (int ver = 17; ver >= 6; ver--) { var compiler = "clang++-" + ver; if (Which(compiler) != null) From 8338d1cc37f604aa5270b5045402966f4400a379 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Wed, 27 Sep 2023 21:47:15 +0300 Subject: [PATCH 154/188] Fix `Which` not returning the found executable on success --- Source/Tools/Flax.Build/Platforms/Unix/UnixPlatform.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Platforms/Unix/UnixPlatform.cs b/Source/Tools/Flax.Build/Platforms/Unix/UnixPlatform.cs index 9d9206f0f..b498f66e4 100644 --- a/Source/Tools/Flax.Build/Platforms/Unix/UnixPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Unix/UnixPlatform.cs @@ -68,8 +68,11 @@ namespace Flax.Build.Platforms string path = proc.StandardOutput.ReadLine(); Log.Verbose(string.Format("which {0} exit code: {1}, result: {2}", name, proc.ExitCode, path)); - if (proc.ExitCode == 0 && string.IsNullOrEmpty(proc.StandardError.ReadToEnd())) + if (proc.ExitCode == 0) { + string err = proc.StandardError.ReadToEnd(); + if (!string.IsNullOrEmpty(err)) + Log.Verbose(string.Format("which stderr: {0}", err)); return path; } From bcccb713731d45d50c848ba968c8fda0002fe60c Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Thu, 28 Sep 2023 00:24:44 +0300 Subject: [PATCH 155/188] Show full exception message in error message box for managed exceptions --- Source/Engine/Core/Log.cpp | 16 +++++----------- .../Engine/Platform/Windows/WindowsPlatform.cpp | 9 +++++++++ Source/Engine/Scripting/Scripting.cs | 9 +++++++-- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp index 1147c4ff1..e3d97e149 100644 --- a/Source/Engine/Core/Log.cpp +++ b/Source/Engine/Core/Log.cpp @@ -251,19 +251,13 @@ void Log::Logger::Write(LogType type, const StringView& msg) OnError(type, msg); } - // Check if need to show message box with that log message - if (type == LogType::Fatal) - { + // Ensure the error gets written to the disk + if (type == LogType::Fatal || type == LogType::Error) Flush(); - // Process message further - if (type == LogType::Fatal) - Platform::Fatal(msg); - else if (type == LogType::Error) - Platform::Error(msg); - else - Platform::Info(msg); - } + // Check if need to show message box with that log message + if (type == LogType::Fatal) + Platform::Fatal(msg); } const Char* ToString(LogType e) diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 970ccfa10..c5bc44ab9 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -30,6 +30,9 @@ #endif #include "resource.h" +#define CLR_EXCEPTION 0xE0434352 +#define VCPP_EXCEPTION 0xE06D7363 + const Char* WindowsPlatform::ApplicationWindowClass = TEXT("FlaxWindow"); void* WindowsPlatform::Instance = nullptr; @@ -272,6 +275,12 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) LONG CALLBACK SehExceptionHandler(EXCEPTION_POINTERS* ep) { + if (ep->ExceptionRecord->ExceptionCode == CLR_EXCEPTION) + { + // Pass CLR exceptions back to runtime + return EXCEPTION_CONTINUE_SEARCH; + } + // Skip if engine already crashed if (Globals::FatalErrorOccurred) return EXCEPTION_CONTINUE_SEARCH; diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index 68da64bf7..3e11b3e14 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -135,8 +135,13 @@ namespace FlaxEngine { if (e.ExceptionObject is Exception exception) { - Debug.LogError("Unhandled Exception: " + exception.Message); - Debug.LogException(exception); + if (e.IsTerminating && !System.Diagnostics.Debugger.IsAttached) + Platform.Fatal($"Unhandled Exception: {exception}"); + else + { + Debug.LogError($"Unhandled Exception: {exception.Message}"); + Debug.LogException(exception); + } } } From 77f962b33d33094badf9eeaac02ea8e20a0fb473 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 28 Sep 2023 11:47:16 +0200 Subject: [PATCH 156/188] Fix soft refs in bindings to match C# version that is not generic --- Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index b16c4eb47..40e5204d5 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -299,6 +299,8 @@ namespace Flax.Build.Bindings // Object reference property if (typeInfo.IsObjectRef) return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller); + if (typeInfo.Type == "SoftTypeReference" || typeInfo.Type == "SoftObjectReference") + return typeInfo.Type; // Array or Span or DataContainer #if USE_NETCORE From 82b2f46b480583150722c15fef381baa5d4c938e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 28 Sep 2023 19:21:06 +0200 Subject: [PATCH 157/188] Post-merge tweaks for #1312 --- Source/Engine/Engine/NativeInterop.cs | 2 +- Source/Engine/Scripting/ManagedCLR/MCore.h | 2 +- Source/Engine/Scripting/Runtime/DotNet.cpp | 2 +- Source/Engine/Scripting/Runtime/Mono.cpp | 33 ++++++++++++- Source/Engine/Scripting/Runtime/None.cpp | 2 +- Source/Engine/Scripting/ScriptingObject.cpp | 53 ++------------------- Source/Engine/Scripting/ScriptingObject.h | 7 --- 7 files changed, 39 insertions(+), 62 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index bb29a38fd..0b6aa2320 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -134,7 +134,7 @@ namespace FlaxEngine.Interop ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(unmanagedPtrFieldOffset, ref obj); fieldRef = unmanagedPtr; } - + if (idPtr != IntPtr.Zero) { ref Guid nativeId = ref Unsafe.AsRef(idPtr.ToPointer()); diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.h b/Source/Engine/Scripting/ManagedCLR/MCore.h index 5a2264047..6a3f24ba8 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.h +++ b/Source/Engine/Scripting/ManagedCLR/MCore.h @@ -196,7 +196,7 @@ public: /// struct FLAXENGINE_API ScriptingObject { - static void SetInternalValues(MObject* object, void* unmanagedPtr, const Guid* id); + static void SetInternalValues(MClass* klass, MObject* object, void* unmanagedPtr, const Guid* id); static MObject* CreateScriptingObject(MClass* klass, void* unmanagedPtr, const Guid* id); }; }; diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 9bc4fc89d..047423994 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -1737,7 +1737,7 @@ void* GetStaticMethodPointer(const String& methodName) return fun; } -void MCore::ScriptingObject::SetInternalValues(MObject* object, void* unmanagedPtr, const Guid* id) +void MCore::ScriptingObject::SetInternalValues(MClass* klass, MObject* object, void* unmanagedPtr, const Guid* id) { static void* ScriptingObjectSetInternalValuesPtr = GetStaticMethodPointer(TEXT("ScriptingObjectSetInternalValues")); CallStaticMethod(ScriptingObjectSetInternalValuesPtr, object, unmanagedPtr, id); diff --git a/Source/Engine/Scripting/Runtime/Mono.cpp b/Source/Engine/Scripting/Runtime/Mono.cpp index 8d186c661..676453c21 100644 --- a/Source/Engine/Scripting/Runtime/Mono.cpp +++ b/Source/Engine/Scripting/Runtime/Mono.cpp @@ -2127,13 +2127,42 @@ const Array& MProperty::GetAttributes() const return _attributes; } -void MCore::ScriptingObject::SetInternalValues(MObject* object, void* unmanagedPtr, const Guid* id) +void MCore::ScriptingObject::SetInternalValues(MClass* klass, MObject* object, void* unmanagedPtr, const Guid* id) { + // Set handle to unmanaged object + const MField* monoUnmanagedPtrField = klass->GetField("__unmanagedPtr"); + if (monoUnmanagedPtrField) + { + const void* param = unmanagedPtr; + monoUnmanagedPtrField->SetValue(managedInstance, ¶m); + } + if (id != nullptr) + { + // Set object id + const MField* monoIdField = klass->GetField("__internalId"); + if (monoIdField) + { + monoIdField->SetValue(managedInstance, (void*)id); + } + } } MObject* MCore::ScriptingObject::CreateScriptingObject(MClass* klass, void* unmanagedPtr, const Guid* id) { - return nullptr; + // Ensure to have managed domain attached (this can be called from custom native thread, eg. content loader) + MCore::Thread::Attach(); + + // Allocate managed instance + MObject* managedInstance = MCore::Object::New(klass); + if (managedInstance) + { + // Set unmanaged object handle and id + MCore::ScriptingObject::SetInternalValues(klass, managedInstance, unmanagedPtr, _id); + + // Initialize managed instance (calls constructor) + MCore::Object::Init(managedInstance); + } + return managedInstance; } #endif diff --git a/Source/Engine/Scripting/Runtime/None.cpp b/Source/Engine/Scripting/Runtime/None.cpp index 073b7ca19..a25d59c59 100644 --- a/Source/Engine/Scripting/Runtime/None.cpp +++ b/Source/Engine/Scripting/Runtime/None.cpp @@ -565,7 +565,7 @@ const Array& MProperty::GetAttributes() const return _attributes; } -void MCore::ScriptingObject::SetInternalValues(MObject* object, void* unmanagedPtr, const Guid* id) +void MCore::ScriptingObject::SetInternalValues(MClass* klass, MObject* object, void* unmanagedPtr, const Guid* id) { } diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index ddd0eb07d..b6d126bca 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -181,7 +181,6 @@ ScriptingObject* ScriptingObject::ToNative(MObject* obj) if (obj) { #if USE_MONO - // TODO: cache the field offset from object and read directly from object pointer const auto ptrField = MCore::Object::GetClass(obj)->GetField(ScriptingObject_unmanagedPtr); CHECK_RETURN(ptrField, nullptr); ptrField->GetValue(obj, &ptr); @@ -279,7 +278,7 @@ bool ScriptingObject::CreateManaged() if (const auto monoClass = GetClass()) { // Reset managed to unmanaged pointer - SetInternalValues(monoClass, managedInstance, nullptr, nullptr); + MCore::ScriptingObject::SetInternalValues(monoClass, managedInstance, nullptr, nullptr); } MCore::GCHandle::Free(handle); return true; @@ -295,32 +294,6 @@ bool ScriptingObject::CreateManaged() #if USE_CSHARP -void ScriptingObject::SetInternalValues(MClass* monoClass, MObject* managedInstance, void* unmanagedPtr, const Guid* id) -{ -#if USE_MONO - // Set handle to unmanaged object - const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr); - if (monoUnmanagedPtrField) - { - const void* param = unmanagedPtr; - monoUnmanagedPtrField->SetValue(managedInstance, ¶m); - } - - if (id != nullptr) - { - // Set object id - const MField* monoIdField = monoClass->GetField(ScriptingObject_id); - if (monoIdField) - { - monoIdField->SetValue(managedInstance, (void*)id); - } - } - -#else - MCore::ScriptingObject::SetInternalValues(managedInstance, unmanagedPtr, id); -#endif -} - MObject* ScriptingObject::CreateManagedInternal() { // Get class @@ -331,28 +304,11 @@ MObject* ScriptingObject::CreateManagedInternal() return nullptr; } - // Ensure to have managed domain attached (this can be called from custom native thread, eg. content loader) - MCore::Thread::Attach(); - -#if USE_MONO - // Allocate managed instance - MObject* managedInstance = MCore::Object::New(monoClass); - if (managedInstance == nullptr) - { - LOG(Warning, "Failed to create new instance of the object of type {0}", String(monoClass->GetFullName())); - } - - SetInternalValues(monoClass, managedInstance, this, &_id); - - // Initialize managed instance (calls constructor) - MCore::Object::Init(managedInstance); -#else MObject* managedInstance = MCore::ScriptingObject::CreateScriptingObject(monoClass, this, &_id); if (managedInstance == nullptr) { LOG(Warning, "Failed to create new instance of the object of type {0}", String(monoClass->GetFullName())); } -#endif return managedInstance; } @@ -370,7 +326,7 @@ void ScriptingObject::DestroyManaged() { if (const auto monoClass = GetClass()) { - SetInternalValues(monoClass, managedInstance, nullptr, nullptr); + MCore::ScriptingObject::SetInternalValues(monoClass, managedInstance, nullptr, nullptr); } } @@ -494,7 +450,7 @@ bool ManagedScriptingObject::CreateManaged() if (const auto monoClass = GetClass()) { // Reset managed to unmanaged pointer - SetInternalValues(monoClass, managedInstance, nullptr, nullptr); + MCore::ScriptingObject::SetInternalValues(monoClass, managedInstance, nullptr, nullptr); } MCore::GCHandle::Free(handle); return true; @@ -654,9 +610,8 @@ DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceCreated(MObject* manage } MClass* monoClass = obj->GetClass(); - const Guid id = obj->GetID(); - ScriptingObject::SetInternalValues(monoClass, managedInstance, obj, &id); + MCore::ScriptingObject::SetInternalValues(monoClass, managedInstance, obj, &id); // Register object if (!obj->IsRegistered()) diff --git a/Source/Engine/Scripting/ScriptingObject.h b/Source/Engine/Scripting/ScriptingObject.h index 0dfa80450..ad72a31c3 100644 --- a/Source/Engine/Scripting/ScriptingObject.h +++ b/Source/Engine/Scripting/ScriptingObject.h @@ -206,13 +206,6 @@ public: /// void UnregisterObject(); -#if USE_CSHARP - /// - /// Sets the internal values in managed object. - /// - static void SetInternalValues(MClass* monoClass, MObject* managedInstance, void* unmanagedPtr, const Guid* id); -#endif - protected: #if USE_CSHARP /// From e319b4dedc3448b966ed6131bed3289738fc9fba Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 28 Sep 2023 19:37:08 +0200 Subject: [PATCH 158/188] Minor optimization for PhysX events cleanup --- .../Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp index 4878750eb..42f9b7a59 100644 --- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp +++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp @@ -15,9 +15,11 @@ namespace { if (collection.IsEmpty()) return; + const auto c = collection.Get(); for (int32 i = 0; i < collection.Count(); i++) { - if (collection[i].First == collider || collection[i].Second == collider) + const SimulationEventCallback::CollidersPair cc = c[i]; + if (cc.First == collider || cc.Second == collider) { collection.RemoveAt(i--); if (collection.IsEmpty()) @@ -32,7 +34,8 @@ namespace return; for (auto i = collection.Begin(); i.IsNotEnd(); ++i) { - if (i->Key.First == collider || i->Key.Second == collider) + const SimulationEventCallback::CollidersPair cc = i->Key; + if (cc.First == collider || cc.Second == collider) { collection.Remove(i); if (collection.IsEmpty()) From 139e6d0d1fccdee183103c147beef0e5da76be3b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 28 Sep 2023 20:16:57 +0200 Subject: [PATCH 159/188] Improve #1514 to share code for tooltip position wrapping --- Source/Engine/UI/GUI/Tooltip.cs | 42 ++++++++++++--------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index d4825b7ca..e98c07cc5 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -68,26 +68,12 @@ namespace FlaxEngine.GUI var parentWin = target.Root; if (parentWin == null) return; - float dpiScale = target.RootWindow.DpiScale; + var dpiScale = target.RootWindow.DpiScale; var dpiSize = Size * dpiScale; var locationWS = target.PointToWindow(location); var locationSS = parentWin.PointToScreen(locationWS); - var monitorBounds = Platform.GetMonitorBounds(locationSS); - var rightBottomMonitorBounds = monitorBounds.BottomRight; - var rightBottomLocationSS = locationSS + dpiSize; - - // Prioritize tooltip placement within parent window, fall back to virtual desktop - if (rightBottomMonitorBounds.Y < rightBottomLocationSS.Y) - { - // Direction: up - locationSS.Y -= dpiSize.Y; - } - if (rightBottomMonitorBounds.X < rightBottomLocationSS.X) - { - // Direction: left - locationSS.X -= dpiSize.X; - } _showTarget = target; + WrapPosition(ref locationSS); // Create window var desc = CreateWindowSettings.Default; @@ -193,15 +179,11 @@ namespace FlaxEngine.GUI } } - /// - public override void Update(float deltaTime) + private void WrapPosition(ref Float2 locationSS, float flipOffset = 0.0f) { - var mousePos = Input.MouseScreenPosition; - // Calculate popup direction - float dpiScale = _showTarget.RootWindow.DpiScale; + var dpiScale = _showTarget.RootWindow.DpiScale; var dpiSize = Size * dpiScale; - var locationSS = mousePos; var monitorBounds = Platform.GetMonitorBounds(locationSS); var rightBottomMonitorBounds = monitorBounds.BottomRight; var rightBottomLocationSS = locationSS + dpiSize; @@ -210,17 +192,23 @@ namespace FlaxEngine.GUI if (rightBottomMonitorBounds.Y < rightBottomLocationSS.Y) { // Direction: up - locationSS.Y -= dpiSize.Y + 10; + locationSS.Y -= dpiSize.Y + flipOffset; } if (rightBottomMonitorBounds.X < rightBottomLocationSS.X) { // Direction: left - locationSS.X -= dpiSize.X + 20; + locationSS.X -= dpiSize.X + flipOffset * 2; } - + } + + /// + public override void Update(float deltaTime) + { // Move window with mouse location - _window.Position = locationSS + new Float2(15, 10); - + var mousePos = Input.MouseScreenPosition; + WrapPosition(ref mousePos, 10); + _window.Position = mousePos + new Float2(15, 10); + // Auto hide if mouse leaves control area var location = _showTarget.PointFromScreen(mousePos); if (!_showTarget.OnTestTooltipOverControl(ref location)) From ea201b617326949d26b310b61ab56872a77ecca7 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Mon, 25 Sep 2023 22:05:37 +0300 Subject: [PATCH 160/188] Fix null check in SceneAnimationWindow The null-conditional operator checks for reference equality of the Object, but doesn't check the unmanaged pointer validity. --- Source/Editor/Windows/Assets/SceneAnimationWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs index 824928743..0d7fa1c78 100644 --- a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs +++ b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs @@ -821,7 +821,7 @@ namespace FlaxEditor.Windows.Assets if (_timeline.IsModified) { var time = _timeline.CurrentTime; - var isPlaying = _previewPlayer?.IsPlaying ?? false; + var isPlaying = _previewPlayer != null ? _previewPlayer.IsPlaying : false; _timeline.Save(_asset); if (_previewButton.Checked && _previewPlayer != null) { From 58445f04c4aa3cd6d06e5212d895287c03e8bc1b Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Mon, 25 Sep 2023 23:06:14 +0300 Subject: [PATCH 161/188] Fix potential incorrect null checks in `FlaxEngine.Object`s The null-conditional operator checks for reference equality of the Object, but doesn't check the validity of the unmanaged pointer. This check is corrected in cases where the object was not immediately returned from the bindings layer and may have been destroyed earlier. --- .../CustomEditors/Dedicated/ScriptsEditor.cs | 4 ++-- Source/Editor/EditorAssets.cs | 4 +++- .../Editor/GUI/Timeline/SceneAnimationTimeline.cs | 4 ++-- Source/Editor/Tools/Foliage/FoliageTypesTab.cs | 2 +- Source/Editor/Tools/Foliage/PaintTab.cs | 2 +- Source/Editor/Tools/VertexPainting.cs | 2 +- Source/Editor/Undo/Actions/PasteActorsAction.cs | 2 +- .../Viewport/Previews/ParticleSystemPreview.cs | 4 ++-- Source/Editor/Viewport/Previews/TexturePreview.cs | 8 ++++---- .../Windows/Assets/MaterialInstanceWindow.cs | 14 +++++++++----- Source/Editor/Windows/Assets/ModelWindow.cs | 6 +++--- .../Editor/Windows/Assets/ParticleSystemWindow.cs | 2 +- .../Editor/Windows/Assets/SceneAnimationWindow.cs | 2 +- Source/Editor/Windows/Assets/SkinnedModelWindow.cs | 6 +++--- Source/Engine/UI/GUI/Brushes/GPUTextureBrush.cs | 2 +- Source/Engine/UI/GUI/Brushes/TextureBrush.cs | 2 +- Source/Engine/UI/UICanvas.cs | 3 ++- Source/Engine/UI/UIControl.cs | 8 ++++---- 18 files changed, 42 insertions(+), 35 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index bf82e19da..acd27d7ef 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -576,8 +576,8 @@ namespace FlaxEditor.CustomEditors.Dedicated return; for (int j = 0; j < e.Length; j++) { - var t1 = scripts[j]?.TypeName; - var t2 = e[j]?.TypeName; + var t1 = scripts[j] != null ? scripts[j].TypeName : null; + var t2 = e[j] != null ? e[j].TypeName : null; if (t1 != t2) return; } diff --git a/Source/Editor/EditorAssets.cs b/Source/Editor/EditorAssets.cs index 7fa920ca6..eb2f21356 100644 --- a/Source/Editor/EditorAssets.cs +++ b/Source/Editor/EditorAssets.cs @@ -36,7 +36,9 @@ namespace FlaxEditor public static void OnEditorOptionsChanged(Options.EditorOptions options) { - var param = _highlightMaterial?.GetParameter("Color"); + if (!_highlightMaterial) + return; + var param = _highlightMaterial.GetParameter("Color"); if (param != null) param.Value = options.Visual.HighlightColor; } diff --git a/Source/Editor/GUI/Timeline/SceneAnimationTimeline.cs b/Source/Editor/GUI/Timeline/SceneAnimationTimeline.cs index c5989cca5..76b6ca246 100644 --- a/Source/Editor/GUI/Timeline/SceneAnimationTimeline.cs +++ b/Source/Editor/GUI/Timeline/SceneAnimationTimeline.cs @@ -33,7 +33,7 @@ namespace FlaxEditor.GUI.Timeline /// public SceneAnimationPlayer Player { - get => _player; + get => _player != null ? _player : null; set { if (_player == value) @@ -268,7 +268,7 @@ namespace FlaxEditor.GUI.Timeline /// public override void OnSeek(int frame) { - if (_player?.Animation) + if (_player != null && _player.Animation) { _player.Animation.WaitForLoaded(); _player.Time = frame / _player.Animation.FramesPerSecond; diff --git a/Source/Editor/Tools/Foliage/FoliageTypesTab.cs b/Source/Editor/Tools/Foliage/FoliageTypesTab.cs index 2a51d972c..8934f5058 100644 --- a/Source/Editor/Tools/Foliage/FoliageTypesTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTypesTab.cs @@ -375,7 +375,7 @@ namespace FlaxEditor.Tools.Foliage private void OnModified() { - Editor.Instance.Scene.MarkSceneEdited(_proxy.Foliage?.Scene); + Editor.Instance.Scene.MarkSceneEdited(_proxy.Foliage != null ? _proxy.Foliage.Scene : null); } private void OnSelectedFoliageChanged() diff --git a/Source/Editor/Tools/Foliage/PaintTab.cs b/Source/Editor/Tools/Foliage/PaintTab.cs index e4f01661b..ba31ca18b 100644 --- a/Source/Editor/Tools/Foliage/PaintTab.cs +++ b/Source/Editor/Tools/Foliage/PaintTab.cs @@ -218,7 +218,7 @@ namespace FlaxEditor.Tools.Foliage private void OnModified() { - Editor.Instance.Scene.MarkSceneEdited(_proxy.Foliage?.Scene); + Editor.Instance.Scene.MarkSceneEdited(_proxy.Foliage != null ? _proxy.Foliage.Scene : null); } private void OnSelectedFoliageChanged() diff --git a/Source/Editor/Tools/VertexPainting.cs b/Source/Editor/Tools/VertexPainting.cs index 0863569c2..6934a754b 100644 --- a/Source/Editor/Tools/VertexPainting.cs +++ b/Source/Editor/Tools/VertexPainting.cs @@ -544,7 +544,7 @@ namespace FlaxEditor.Tools public override bool IsControllingMouse => IsPainting; /// - public override BoundingSphere FocusBounds => _selectedModel?.Sphere ?? base.FocusBounds; + public override BoundingSphere FocusBounds => _selectedModel != null ? _selectedModel.Sphere : base.FocusBounds; /// public override void Update(float dt) diff --git a/Source/Editor/Undo/Actions/PasteActorsAction.cs b/Source/Editor/Undo/Actions/PasteActorsAction.cs index 8b0d040d8..7aee40878 100644 --- a/Source/Editor/Undo/Actions/PasteActorsAction.cs +++ b/Source/Editor/Undo/Actions/PasteActorsAction.cs @@ -144,7 +144,7 @@ namespace FlaxEditor.Actions { var node = nodeParents[i]; var actor = node.Actor; - var parent = actor?.Parent; + var parent = actor != null ? actor.Parent : null; if (parent != null) { bool IsNameValid(string name) diff --git a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs index fc7fcdbe1..c6ce5a7b5 100644 --- a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs +++ b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs @@ -68,7 +68,7 @@ namespace FlaxEditor.Viewport.Previews /// public bool ShowBounds { - get => _boundsModel?.IsActive ?? false; + get => _boundsModel != null ? _boundsModel.IsActive : false; set { if (value == ShowBounds) @@ -110,7 +110,7 @@ namespace FlaxEditor.Viewport.Previews /// public bool ShowOrigin { - get => _originModel?.IsActive ?? false; + get => _originModel != null ? _originModel.IsActive : false; set { if (value == ShowOrigin) diff --git a/Source/Editor/Viewport/Previews/TexturePreview.cs b/Source/Editor/Viewport/Previews/TexturePreview.cs index a33a61f86..ec10bb019 100644 --- a/Source/Editor/Viewport/Previews/TexturePreview.cs +++ b/Source/Editor/Viewport/Previews/TexturePreview.cs @@ -500,7 +500,7 @@ namespace FlaxEditor.Viewport.Previews /// protected override void CalculateTextureRect(out Rectangle rect) { - CalculateTextureRect(_asset?.Size ?? new Float2(100), Size, out rect); + CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect); } /// @@ -549,7 +549,7 @@ namespace FlaxEditor.Viewport.Previews /// protected override void CalculateTextureRect(out Rectangle rect) { - CalculateTextureRect(_asset?.Size ?? new Float2(100), Size, out rect); + CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect); } /// @@ -604,7 +604,7 @@ namespace FlaxEditor.Viewport.Previews /// protected override void CalculateTextureRect(out Rectangle rect) { - CalculateTextureRect(_asset?.Size ?? new Float2(100), Size, out rect); + CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect); } /// @@ -659,7 +659,7 @@ namespace FlaxEditor.Viewport.Previews /// protected override void CalculateTextureRect(out Rectangle rect) { - CalculateTextureRect(_asset?.Size ?? new Float2(100), Size, out rect); + CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect); } /// diff --git a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs index d5ab2ae8a..5f1273999 100644 --- a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs @@ -69,7 +69,7 @@ namespace FlaxEditor.Windows.Assets [EditorDisplay("General"), Tooltip("The base material used to override it's properties")] public MaterialBase BaseMaterial { - get => Window?.Asset?.BaseMaterial; + get => Window?.Asset != null ? Window?.Asset.BaseMaterial : null; set { var asset = Window?.Asset; @@ -101,10 +101,12 @@ namespace FlaxEditor.Windows.Assets [HideInEditor] public object[] Values { - get => Window?.Asset?.Parameters.Select(x => x.Value).ToArray(); + get => Window?.Asset != null ? Window?.Asset.Parameters.Select(x => x.Value).ToArray() : null; set { - var parameters = Window?.Asset?.Parameters; + if (Window?.Asset == null) + return; + var parameters = Window?.Asset.Parameters; if (value != null && parameters != null) { if (value.Length != parameters.Length) @@ -131,9 +133,11 @@ namespace FlaxEditor.Windows.Assets [HideInEditor] public FlaxEngine.Object[] ValuesRef { - get => Window?.Asset?.Parameters.Select(x => x.Value as FlaxEngine.Object).ToArray(); + get => Window?.Asset != null ? Window?.Asset.Parameters.Select(x => x.Value as FlaxEngine.Object).ToArray() : null; set { + if (Window?.Asset == null) + return; var parameters = Window?.Asset?.Parameters; if (value != null && parameters != null) { @@ -293,7 +297,7 @@ namespace FlaxEditor.Windows.Assets var p = (MaterialParameter)e.Tag; // Try to get default value (from the base material) - var pBase = baseMaterial?.GetParameter(p.Name); + var pBase = baseMaterial != null ? baseMaterial.GetParameter(p.Name) : null; if (pBase != null && pBase.ParameterType == p.ParameterType) { valueContainer.SetDefaultValue(pBase.Value); diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index f70f6a3b5..c2764a5a6 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -134,7 +134,7 @@ namespace FlaxEditor.Windows.Assets if (Window._skipEffectsGuiEvents) return; - Window._isolateIndex = mesh?.MaterialSlotIndex ?? -1; + Window._isolateIndex = mesh != null ? mesh.MaterialSlotIndex : -1; Window.UpdateEffectsOnAsset(); UpdateEffectsOnUI(); } @@ -144,7 +144,7 @@ namespace FlaxEditor.Windows.Assets if (Window._skipEffectsGuiEvents) return; - Window._highlightIndex = mesh?.MaterialSlotIndex ?? -1; + Window._highlightIndex = mesh != null ? mesh.MaterialSlotIndex : -1; Window.UpdateEffectsOnAsset(); UpdateEffectsOnUI(); } @@ -326,7 +326,7 @@ namespace FlaxEditor.Windows.Assets [EditorOrder(10), EditorDisplay("Materials", EditorDisplayAttribute.InlineStyle)] public MaterialSlot[] MaterialSlots { - get => Asset?.MaterialSlots; + get => Asset != null ? Asset.MaterialSlots : null; set { if (Asset != null) diff --git a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs index 328369680..17eda1358 100644 --- a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs @@ -187,7 +187,7 @@ namespace FlaxEditor.Windows.Assets base.Initialize(layout); var emitterTrack = Values[0] as EmitterTrackProxy; - if (emitterTrack?._effect?.Parameters == null) + if (emitterTrack?._effect == null || emitterTrack?._effect.Parameters == null) return; var group = layout.Group("Parameters"); diff --git a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs index 0d7fa1c78..162944144 100644 --- a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs +++ b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs @@ -792,7 +792,7 @@ namespace FlaxEditor.Windows.Assets { if (_previewButton.Checked) return; - _previewPlayerPicker.Value = _timeline.Player; + _previewPlayerPicker.Value = _timeline.Player != null ? _timeline.Player : null; _cachedPlayerId = _timeline.Player?.ID ?? Guid.Empty; } diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index a7ee6e767..d360e5570 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -151,7 +151,7 @@ namespace FlaxEditor.Windows.Assets if (Window._skipEffectsGuiEvents) return; - Window._isolateIndex = mesh?.MaterialSlotIndex ?? -1; + Window._isolateIndex = mesh != null ? mesh.MaterialSlotIndex : -1; Window.UpdateEffectsOnAsset(); UpdateEffectsOnUI(); } @@ -165,7 +165,7 @@ namespace FlaxEditor.Windows.Assets if (Window._skipEffectsGuiEvents) return; - Window._highlightIndex = mesh?.MaterialSlotIndex ?? -1; + Window._highlightIndex = mesh != null ? mesh.MaterialSlotIndex : -1; Window.UpdateEffectsOnAsset(); UpdateEffectsOnUI(); } @@ -415,7 +415,7 @@ namespace FlaxEditor.Windows.Assets [EditorOrder(10), EditorDisplay("Materials", EditorDisplayAttribute.InlineStyle)] public MaterialSlot[] MaterialSlots { - get => Asset?.MaterialSlots; + get => Asset != null ? Asset.MaterialSlots : null; set { if (Asset != null) diff --git a/Source/Engine/UI/GUI/Brushes/GPUTextureBrush.cs b/Source/Engine/UI/GUI/Brushes/GPUTextureBrush.cs index 9ad9db1d3..d3469670a 100644 --- a/Source/Engine/UI/GUI/Brushes/GPUTextureBrush.cs +++ b/Source/Engine/UI/GUI/Brushes/GPUTextureBrush.cs @@ -37,7 +37,7 @@ namespace FlaxEngine.GUI } /// - public Float2 Size => Texture?.Size ?? Float2.Zero; + public Float2 Size => Texture != null ? Texture.Size : Float2.Zero; /// public void Draw(Rectangle rect, Color color) diff --git a/Source/Engine/UI/GUI/Brushes/TextureBrush.cs b/Source/Engine/UI/GUI/Brushes/TextureBrush.cs index d49e5519b..755f7527b 100644 --- a/Source/Engine/UI/GUI/Brushes/TextureBrush.cs +++ b/Source/Engine/UI/GUI/Brushes/TextureBrush.cs @@ -104,7 +104,7 @@ namespace FlaxEngine.GUI } /// - public Float2 Size => Texture?.Size ?? Float2.Zero; + public Float2 Size => Texture != null ? Texture.Size : Float2.Zero; /// public unsafe void Draw(Rectangle rect, Color color) diff --git a/Source/Engine/UI/UICanvas.cs b/Source/Engine/UI/UICanvas.cs index 8cb43af62..edbec7f7d 100644 --- a/Source/Engine/UI/UICanvas.cs +++ b/Source/Engine/UI/UICanvas.cs @@ -493,7 +493,8 @@ namespace FlaxEngine if (_renderer) { #if FLAX_EDITOR - _editorTask?.RemoveCustomPostFx(_renderer); + if (_editorTask != null) + _editorTask.RemoveCustomPostFx(_renderer); #endif SceneRenderTask.RemoveGlobalCustomPostFx(_renderer); _renderer.Canvas = null; diff --git a/Source/Engine/UI/UIControl.cs b/Source/Engine/UI/UIControl.cs index dc76122c0..219a4fa4e 100644 --- a/Source/Engine/UI/UIControl.cs +++ b/Source/Engine/UI/UIControl.cs @@ -204,7 +204,7 @@ namespace FlaxEngine up = value; Internal_SetNavTargets(__unmanagedPtr, GetUnmanagedPtr(up), GetUnmanagedPtr(down), GetUnmanagedPtr(left), GetUnmanagedPtr(right)); if (_control != null) - _control.NavTargetUp = value?.Control; + _control.NavTargetUp = value != null ? value.Control : null; } } @@ -228,7 +228,7 @@ namespace FlaxEngine down = value; Internal_SetNavTargets(__unmanagedPtr, GetUnmanagedPtr(up), GetUnmanagedPtr(down), GetUnmanagedPtr(left), GetUnmanagedPtr(right)); if (_control != null) - _control.NavTargetDown = value?.Control; + _control.NavTargetDown = value != null ? value.Control : null; } } @@ -252,7 +252,7 @@ namespace FlaxEngine left = value; Internal_SetNavTargets(__unmanagedPtr, GetUnmanagedPtr(up), GetUnmanagedPtr(down), GetUnmanagedPtr(left), GetUnmanagedPtr(right)); if (_control != null) - _control.NavTargetLeft = value?.Control; + _control.NavTargetLeft = value != null ? value.Control : null; } } @@ -276,7 +276,7 @@ namespace FlaxEngine right = value; Internal_SetNavTargets(__unmanagedPtr, GetUnmanagedPtr(up), GetUnmanagedPtr(down), GetUnmanagedPtr(left), GetUnmanagedPtr(right)); if (_control != null) - _control.NavTargetRight = value?.Control; + _control.NavTargetRight = value != null ? value.Control : null; } } From 96d880df6a9d8c95cbaf7de056f99f3c88fea0c7 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Thu, 28 Sep 2023 21:49:39 +0300 Subject: [PATCH 162/188] Fix crash in SceneAnimationPlayer --- .../Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp | 2 +- Source/Engine/Engine/NativeInterop.Unmanaged.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp index fa7999ce0..8fef8e6d3 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp @@ -905,7 +905,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3 MException ex(exception); ex.Log(LogType::Error, TEXT("Property")); } - else if (!MCore::Type::IsPointer(valueType)) + else if (!MCore::Type::IsPointer(valueType) && !MCore::Type::IsReference(valueType)) { if (boxed) Platform::MemoryCopy(value, MCore::Object::Unbox(boxed), valueSize); diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index a2647dd10..54c1c21ec 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -1230,7 +1230,7 @@ namespace FlaxEngine.Interop internal static bool GetTypeIsReference(ManagedHandle typeHandle) { Type type = Unsafe.As(typeHandle.Target); - return type.IsByRef; + return !type.IsValueType; // Maybe also type.IsByRef? } [UnmanagedCallersOnly] From 9a5d8e2c51cc0bff78af3388ca86fc8719033c59 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 28 Sep 2023 15:31:30 -0500 Subject: [PATCH 163/188] Fix tooltip crash --- Source/Engine/UI/GUI/Tooltip.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index e98c07cc5..734fb078f 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -181,6 +181,9 @@ namespace FlaxEngine.GUI private void WrapPosition(ref Float2 locationSS, float flipOffset = 0.0f) { + if (_showTarget?.RootWindow == null) + return; + // Calculate popup direction var dpiScale = _showTarget.RootWindow.DpiScale; var dpiSize = Size * dpiScale; @@ -207,7 +210,8 @@ namespace FlaxEngine.GUI // Move window with mouse location var mousePos = Input.MouseScreenPosition; WrapPosition(ref mousePos, 10); - _window.Position = mousePos + new Float2(15, 10); + if (_window) + _window.Position = mousePos + new Float2(15, 10); // Auto hide if mouse leaves control area var location = _showTarget.PointFromScreen(mousePos); From 8eed667d5a161c5a60aed7eeb245c367d6f43a7e Mon Sep 17 00:00:00 2001 From: Edu Garcia <28616+Arcnor@users.noreply.github.com> Date: Thu, 28 Sep 2023 22:41:24 +0100 Subject: [PATCH 164/188] Makes BaseLinesGapScale configurable for Label --- Source/Engine/UI/GUI/Common/Label.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index 2779dfdea..0e2d3c9cf 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -74,10 +74,16 @@ namespace FlaxEngine.GUI [EditorDisplay("Text Style"), EditorOrder(2022), Tooltip("The text wrapping within the control bounds.")] public TextWrapping Wrapping { get; set; } = TextWrapping.NoWrap; + /// + /// Gets or sets the text wrapping within the control bounds. + /// + [EditorDisplay("Text Style"), EditorOrder(2023), Tooltip("The gap between lines when wrapping and more than a single line is displayed."), Limit(0f)] + public float BaseLinesGapScale { get; set; } = 1.0f; + /// /// Gets or sets the font. /// - [EditorDisplay("Text Style"), EditorOrder(2023)] + [EditorDisplay("Text Style"), EditorOrder(2024)] public FontReference Font { get => _font; @@ -99,7 +105,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data. /// - [EditorDisplay("Text Style"), EditorOrder(2024)] + [EditorDisplay("Text Style"), EditorOrder(2025)] public MaterialBase Material { get; set; } /// @@ -227,7 +233,7 @@ namespace FlaxEngine.GUI } } - Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, 1.0f, scale); + Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); if (ClipText) Render2D.PopClip(); @@ -249,6 +255,7 @@ namespace FlaxEngine.GUI else if (_autoWidth && !_autoHeight) layout.Bounds.Size.Y = Height - Margin.Height; _textSize = font.MeasureText(_text, ref layout); + _textSize.Y *= BaseLinesGapScale; // Check if size is controlled via text if (_autoWidth || _autoHeight) From 6d3fdeec8a533ef4bec073dedc05ddef5a96a63d Mon Sep 17 00:00:00 2001 From: Edu Garcia <28616+Arcnor@users.noreply.github.com> Date: Thu, 28 Sep 2023 22:50:07 +0100 Subject: [PATCH 165/188] Rename new actor after creating it --- Source/Editor/Windows/SceneTreeWindow.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 609f98f83..fba052269 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -173,6 +173,9 @@ namespace FlaxEditor.Windows // Spawn it Editor.SceneEditing.Spawn(actor, parentActor); + + Editor.SceneEditing.Select(actor); + Rename(); } /// From 1d42988f399d217fd094571ba620d101c1ec9553 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 29 Sep 2023 00:49:51 +0300 Subject: [PATCH 166/188] Fix VisualScript method calls not working with value types --- Source/Engine/Scripting/BinaryModule.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 8f1bb27d0..7bdffd581 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -1237,8 +1237,12 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp return true; } +#if USE_NETCORE + mInstance = instanceObject; +#else // For value-types instance is the actual boxed object data, not te object itself mInstance = instanceObjectClass->IsValueType() ? MCore::Object::Unbox(instanceObject) : instanceObject; +#endif } // Marshal parameters From fd3f10864b8c09289971d8cb2cfd64090d0cd026 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 29 Sep 2023 00:00:21 +0200 Subject: [PATCH 167/188] Fixes for iOS #1312 --- Flax.sln.DotSettings | 2 + .../Engine/Engine/NativeInterop.Unmanaged.cs | 2 +- Source/Engine/Engine/NativeInterop.cs | 68 ++++++++++--------- Source/Engine/Scripting/Runtime/DotNet.cpp | 24 +++---- 4 files changed, 51 insertions(+), 45 deletions(-) diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index e8af1461c..6a22f211b 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -291,6 +291,8 @@ True True True + True + True True True True diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index a2647dd10..9cc5a1f1c 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -853,7 +853,7 @@ namespace FlaxEngine.Interop { object fieldOwner = fieldOwnerHandle.Target; FieldHolder field = Unsafe.As(fieldHandle.Target); - field.toNativeMarshaller(field.fieldOffset, fieldOwner, valuePtr, out int fieldSize); + field.toNativeMarshaller(field.field, field.fieldOffset, fieldOwner, valuePtr, out int fieldSize); } [UnmanagedCallersOnly] diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 0b6aa2320..500636290 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -363,7 +363,7 @@ namespace FlaxEngine.Interop internal delegate object MarshalToManagedDelegate(IntPtr nativePtr, bool byRef); internal delegate void MarshalToNativeDelegate(object managedObject, IntPtr nativePtr); - internal delegate void MarshalToNativeFieldDelegate(int fieldOffset, object fieldOwner, IntPtr nativePtr, out int fieldSize); + internal delegate void MarshalToNativeFieldDelegate(FieldInfo field, int fieldOffset, object fieldOwner, IntPtr nativePtr, out int fieldSize); internal static ConcurrentDictionary toManagedMarshallers = new ConcurrentDictionary(1, 3); internal static ConcurrentDictionary toNativeMarshallers = new ConcurrentDictionary(1, 3); @@ -471,7 +471,7 @@ namespace FlaxEngine.Interop { private delegate void MarshalToNativeTypedDelegate(ref T managedValue, IntPtr nativePtr); private delegate void MarshalToManagedTypedDelegate(ref T managedValue, IntPtr nativePtr, bool byRef); - internal delegate void MarshalFieldTypedDelegate(int managedFieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize); + internal delegate void MarshalFieldTypedDelegate(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize); internal delegate void* GetBasePointer(ref T fieldOwner); internal static FieldInfo[] marshallableFields; @@ -605,7 +605,6 @@ namespace FlaxEngine.Interop methodName = nameof(MarshalHelperValueType.ToManagedWithMarshallableFields); else methodName = nameof(MarshalHelperValueType.ToManaged); - toManagedMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); } else if (type.IsArray) @@ -716,7 +715,7 @@ namespace FlaxEngine.Interop toNativeTypedMarshaller(ref managedValue, nativePtr); } - internal static void ToNativeField(int fieldOffset, ref T fieldOwner, IntPtr nativePtr, out int fieldSize) + internal static void ToNativeField(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativePtr, out int fieldSize) { if (marshallableFields != null) { @@ -724,7 +723,7 @@ namespace FlaxEngine.Interop { if (marshallableFieldOffsets[i] == fieldOffset) { - toNativeFieldMarshallers[i](fieldOffset, ref fieldOwner, nativePtr, out fieldSize); + toNativeFieldMarshallers[i](field, fieldOffset, ref fieldOwner, nativePtr, out fieldSize); return; } } @@ -732,28 +731,28 @@ namespace FlaxEngine.Interop throw new NativeInteropException($"Invalid field with offset {fieldOffset} to marshal for type {typeof(T).Name}"); } - private static void ToManagedFieldPointerValueType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct + private static void ToManagedFieldPointerValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { ref IntPtr fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); fieldValueRef = Unsafe.Read(nativeFieldPtr.ToPointer()); fieldSize = IntPtr.Size; } - private static void ToManagedFieldPointerReferenceType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + private static void ToManagedFieldPointerReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { ref IntPtr fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); fieldValueRef = Unsafe.Read(nativeFieldPtr.ToPointer()); fieldSize = IntPtr.Size; } - private static void ToNativeFieldPointerValueType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct + private static void ToNativeFieldPointerValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { ref IntPtr fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValueRef); fieldSize = IntPtr.Size; } - private static void ToNativeFieldPointerReferenceType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + private static void ToNativeFieldPointerReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { ref IntPtr fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValueRef); @@ -788,7 +787,7 @@ namespace FlaxEngine.Interop fieldAlignment = GetTypeSize(fieldType); } - internal static void ToManagedFieldValueType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct + internal static void ToManagedFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { fieldSize = Unsafe.SizeOf(); if (fieldAlignment > 1) @@ -802,7 +801,7 @@ namespace FlaxEngine.Interop MarshalHelper.ToManaged(ref fieldValueRef, nativeFieldPtr, false); } - internal static void ToManagedFieldReferenceType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + internal static void ToManagedFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { fieldSize = Unsafe.SizeOf(); if (fieldAlignment > 1) @@ -816,7 +815,7 @@ namespace FlaxEngine.Interop MarshalHelper.ToManaged(ref fieldValueRef, nativeFieldPtr, false); } - internal static void ToManagedFieldArrayValueType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct + internal static void ToManagedFieldArrayValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { // Follows the same marshalling semantics with reference types fieldSize = Unsafe.SizeOf(); @@ -828,7 +827,7 @@ namespace FlaxEngine.Interop MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); } - internal static void ToManagedFieldArrayReferenceType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + internal static void ToManagedFieldArrayReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { // Follows the same marshalling semantics with reference types fieldSize = Unsafe.SizeOf(); @@ -840,7 +839,7 @@ namespace FlaxEngine.Interop MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); } - internal static void ToNativeFieldValueType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct + internal static void ToNativeFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { fieldSize = Unsafe.SizeOf(); if (fieldAlignment > 1) @@ -858,7 +857,7 @@ namespace FlaxEngine.Interop MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); } - internal static void ToNativeFieldReferenceType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + internal static void ToNativeFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { fieldSize = Unsafe.SizeOf(); if (fieldAlignment > 1) @@ -883,12 +882,12 @@ namespace FlaxEngine.Interop { } - internal static void ToManagedField(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) + internal static void ToManagedField(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) { fieldSize = 0; } - internal static void ToNativeField(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) + internal static void ToNativeField(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) { fieldSize = 0; } @@ -896,7 +895,7 @@ namespace FlaxEngine.Interop private static class ReferenceTypeField where TField : class { - internal static void ToManagedFieldValueType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct + internal static void ToManagedFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { fieldSize = Unsafe.SizeOf(); IntPtr fieldStartPtr = nativeFieldPtr; @@ -907,7 +906,7 @@ namespace FlaxEngine.Interop MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); } - internal static void ToManagedFieldReferenceType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + internal static void ToManagedFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { fieldSize = Unsafe.SizeOf(); IntPtr fieldStartPtr = nativeFieldPtr; @@ -918,7 +917,7 @@ namespace FlaxEngine.Interop MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); } - internal static void ToManagedFieldArrayValueType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct + internal static void ToManagedFieldArrayValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { fieldSize = Unsafe.SizeOf(); IntPtr fieldStartPtr = nativeFieldPtr; @@ -929,7 +928,7 @@ namespace FlaxEngine.Interop MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); } - internal static void ToManagedFieldArrayReferenceType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + internal static void ToManagedFieldArrayReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { fieldSize = Unsafe.SizeOf(); IntPtr fieldStartPtr = nativeFieldPtr; @@ -940,7 +939,7 @@ namespace FlaxEngine.Interop MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); } - internal static void ToNativeFieldValueType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct + internal static void ToNativeFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { fieldSize = Unsafe.SizeOf(); IntPtr fieldStartPtr = nativeFieldPtr; @@ -951,7 +950,7 @@ namespace FlaxEngine.Interop MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); } - internal static void ToNativeFieldReferenceType(int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + internal static void ToNativeFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { fieldSize = Unsafe.SizeOf(); IntPtr fieldStartPtr = nativeFieldPtr; @@ -971,9 +970,9 @@ namespace FlaxEngine.Interop MarshalHelper.ToNative(ref Unsafe.Unbox(managedObject), nativePtr); } - internal static void ToNativeFieldWrapper(int fieldOffset, object fieldOwner, IntPtr nativePtr, out int fieldSize) + internal static void ToNativeFieldWrapper(FieldInfo field, int fieldOffset, object fieldOwner, IntPtr nativePtr, out int fieldSize) { - MarshalHelper.ToNativeField(fieldOffset, ref Unsafe.Unbox(fieldOwner), nativePtr, out fieldSize); + MarshalHelper.ToNativeField(field, fieldOffset, ref Unsafe.Unbox(fieldOwner), nativePtr, out fieldSize); } internal static void ToManagedPointer(ref IntPtr managedValue, IntPtr nativePtr, bool byRef) @@ -982,7 +981,6 @@ namespace FlaxEngine.Interop byRef |= type.IsByRef; // Is this needed? if (type.IsByRef) Assert.IsTrue(type.GetElementType().IsValueType); - managedValue = byRef ? nativePtr : Unsafe.Read(nativePtr.ToPointer()); } @@ -994,9 +992,12 @@ namespace FlaxEngine.Interop internal static void ToManagedWithMarshallableFields(ref T managedValue, IntPtr nativePtr, bool byRef) { IntPtr fieldPtr = nativePtr; - for (int i = 0; i < MarshalHelper.marshallableFields.Length; i++) + var fields = MarshalHelper.marshallableFields; + var offsets = MarshalHelper.marshallableFieldOffsets; + var marshallers = MarshalHelper.toManagedFieldMarshallers; + for (int i = 0; i < fields.Length; i++) { - MarshalHelper.toManagedFieldMarshallers[i](MarshalHelper.marshallableFieldOffsets[i], ref managedValue, fieldPtr, out int fieldSize); + marshallers[i](fields[i], offsets[i], ref managedValue, fieldPtr, out int fieldSize); fieldPtr += fieldSize; } Assert.IsTrue((fieldPtr - nativePtr) <= Unsafe.SizeOf()); @@ -1038,9 +1039,12 @@ namespace FlaxEngine.Interop internal static void ToNativeWithMarshallableFields(ref T managedValue, IntPtr nativePtr) { IntPtr fieldPtr = nativePtr; + var fields = MarshalHelper.marshallableFields; + var offsets = MarshalHelper.marshallableFieldOffsets; + var marshallers = MarshalHelper.toNativeFieldMarshallers; for (int i = 0; i < MarshalHelper.marshallableFields.Length; i++) { - MarshalHelper.toNativeFieldMarshallers[i](MarshalHelper.marshallableFieldOffsets[i], ref managedValue, nativePtr, out int fieldSize); + marshallers[i](fields[i], offsets[i], ref managedValue, nativePtr, out int fieldSize); nativePtr += fieldSize; } Assert.IsTrue((nativePtr - fieldPtr) <= Unsafe.SizeOf()); @@ -1060,10 +1064,10 @@ namespace FlaxEngine.Interop MarshalHelper.ToNative(ref managedValue, nativePtr); } - internal static void ToNativeFieldWrapper(int fieldOffset, object managedObject, IntPtr nativePtr, out int fieldSize) + internal static void ToNativeFieldWrapper(FieldInfo field, int fieldOffset, object fieldOwner, IntPtr nativePtr, out int fieldSize) { - T managedValue = Unsafe.As(managedObject); - MarshalHelper.ToNativeField(fieldOffset, ref managedValue, nativePtr, out fieldSize); + T managedValue = Unsafe.As(fieldOwner); + MarshalHelper.ToNativeField(field, fieldOffset, ref managedValue, nativePtr, out fieldSize); } internal static void ToManagedString(ref string managedValue, IntPtr nativePtr, bool byRef) diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 047423994..1d01a8ea3 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -614,6 +614,18 @@ bool MCore::Type::IsReference(MType* type) return CallStaticMethod(GetTypeIsReferencePtr, type); } +void MCore::ScriptingObject::SetInternalValues(MClass* klass, MObject* object, void* unmanagedPtr, const Guid* id) +{ + static void* ScriptingObjectSetInternalValuesPtr = GetStaticMethodPointer(TEXT("ScriptingObjectSetInternalValues")); + CallStaticMethod(ScriptingObjectSetInternalValuesPtr, object, unmanagedPtr, id); +} + +MObject* MCore::ScriptingObject::CreateScriptingObject(MClass* klass, void* unmanagedPtr, const Guid* id) +{ + static void* ScriptingObjectSetInternalValuesPtr = GetStaticMethodPointer(TEXT("ScriptingObjectCreate")); + return CallStaticMethod(ScriptingObjectSetInternalValuesPtr, klass->_handle, unmanagedPtr, id); +} + const MAssembly::ClassesDictionary& MAssembly::GetClasses() const { if (_hasCachedClasses || !IsLoaded()) @@ -1737,18 +1749,6 @@ void* GetStaticMethodPointer(const String& methodName) return fun; } -void MCore::ScriptingObject::SetInternalValues(MClass* klass, MObject* object, void* unmanagedPtr, const Guid* id) -{ - static void* ScriptingObjectSetInternalValuesPtr = GetStaticMethodPointer(TEXT("ScriptingObjectSetInternalValues")); - CallStaticMethod(ScriptingObjectSetInternalValuesPtr, object, unmanagedPtr, id); -} - -MObject* MCore::ScriptingObject::CreateScriptingObject(MClass* klass, void* unmanagedPtr, const Guid* id) -{ - static void* ScriptingObjectSetInternalValuesPtr = GetStaticMethodPointer(TEXT("ScriptingObjectCreate")); - return CallStaticMethod(ScriptingObjectSetInternalValuesPtr, klass->_handle, unmanagedPtr, id); -} - #elif DOTNET_HOST_MONO #ifdef USE_MONO_AOT_MODULE From 9975bde78c27c5d7cee7ed517b469569a33cdaef Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 28 Sep 2023 19:09:02 -0500 Subject: [PATCH 168/188] Change output window search and scroll bars to be like other windows. --- Source/Editor/Windows/OutputLogWindow.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index 4ea31b35e..1d188a32c 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -160,7 +160,7 @@ namespace FlaxEditor.Windows Parent = this, }; _viewDropdown.Clicked += OnViewButtonClicked; - _searchBox = new SearchBox(false, _viewDropdown.Right + 2, 2, Width - _viewDropdown.Right - 2 - _scrollSize) + _searchBox = new SearchBox(false, _viewDropdown.Right + 2, 2, Width - _viewDropdown.Right - 4) { Parent = this, }; @@ -171,11 +171,12 @@ namespace FlaxEditor.Windows Maximum = 0, }; _hScroll.ValueChanged += OnHScrollValueChanged; - _vScroll = new VScrollBar(this, Width - _scrollSize, Height, _scrollSize) + _vScroll = new VScrollBar(this, Width - _scrollSize, Height - _viewDropdown.Height - 2, _scrollSize) { ThumbThickness = 10, Maximum = 0, }; + _vScroll.Y += _viewDropdown.Height + 2; _vScroll.ValueChanged += OnVScrollValueChanged; _output = new OutputTextBox { @@ -409,7 +410,7 @@ namespace FlaxEditor.Windows if (_output != null) { - _searchBox.Width = Width - _viewDropdown.Right - 2 - _scrollSize; + _searchBox.Width = Width - _viewDropdown.Right - 4; _output.Size = new Float2(_vScroll.X - 2, _hScroll.Y - 4 - _viewDropdown.Bottom); } } From bb54229760332b96e0d5a7745f7c5a7cac05ec89 Mon Sep 17 00:00:00 2001 From: Ruan Lucas <79365912+RuanLucasGD@users.noreply.github.com> Date: Thu, 28 Sep 2023 20:13:57 -0400 Subject: [PATCH 169/188] Simplify code --- Source/Editor/Windows/Assets/CollisionDataWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/Assets/CollisionDataWindow.cs b/Source/Editor/Windows/Assets/CollisionDataWindow.cs index 85d4bbecc..1b6ed914c 100644 --- a/Source/Editor/Windows/Assets/CollisionDataWindow.cs +++ b/Source/Editor/Windows/Assets/CollisionDataWindow.cs @@ -200,7 +200,7 @@ namespace FlaxEditor.Windows.Assets ViewportCamera = new FPSCamera(), Parent = _split.Panel1 }; - _preview.Task.ViewFlags = ViewFlags.Reflections | ViewFlags.DebugDraw | ViewFlags.AO | ViewFlags.DirectionalLights | ViewFlags.SkyLights | ViewFlags.Shadows | ViewFlags.SpecularLight | ViewFlags.AntiAliasing | ViewFlags.ToneMapping; + _preview.Task.ViewFlags &= ~ViewFlags.Sky & ~ViewFlags.Bloom; // Asset properties _propertiesPresenter = new CustomEditorPresenter(null); From 8f5af2e149bd0937244dd6b7a7cc5712ae0ee945 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 28 Sep 2023 19:49:59 -0500 Subject: [PATCH 170/188] Fix bool editor when null. --- Source/Editor/CustomEditors/Editors/BooleanEditor.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Editor/CustomEditors/Editors/BooleanEditor.cs b/Source/Editor/CustomEditors/Editors/BooleanEditor.cs index f5edafed8..803b7b4dd 100644 --- a/Source/Editor/CustomEditors/Editors/BooleanEditor.cs +++ b/Source/Editor/CustomEditors/Editors/BooleanEditor.cs @@ -34,7 +34,9 @@ namespace FlaxEditor.CustomEditors.Editors } else { - element.CheckBox.Checked = (bool)Values[0]; + var value = (bool?)Values[0]; + if (value != null) + element.CheckBox.Checked = value.Value; } } } From db8f721fa7adf88ee5861950bf03e2d12c049865 Mon Sep 17 00:00:00 2001 From: Zode Date: Fri, 29 Sep 2023 11:20:24 +0300 Subject: [PATCH 171/188] Fix build project generation under arch --- Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs index 20f64b92e..eff24dbf3 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs @@ -192,11 +192,9 @@ namespace Flax.Build case TargetPlatform.Linux: { rid = $"linux-{arch}"; - ridFallback = ""; + ridFallback = Utilities.ReadProcessOutput("dotnet", "--info").Split('\n').FirstOrDefault(x => x.StartsWith(" RID:"), "").Replace("RID:", "").Trim(); if (string.IsNullOrEmpty(dotnetPath)) dotnetPath ??= SearchForDotnetLocationLinux(); - if (dotnetPath == null) - ridFallback = Utilities.ReadProcessOutput("dotnet", "--info").Split('\n').FirstOrDefault(x => x.StartsWith(" RID:"), "").Replace("RID:", "").Trim(); break; } case TargetPlatform.Mac: From 18310c92647827d3ce4f6a01dc062ebb349b90f7 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 29 Sep 2023 22:06:55 +0300 Subject: [PATCH 172/188] Fix loading shared managed assemblies multiple times --- Source/Engine/Scripting/Scripting.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 6210063b8..28c9a4ed1 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -104,7 +104,7 @@ namespace MMethod* _method_LateFixedUpdate = nullptr; MMethod* _method_Draw = nullptr; MMethod* _method_Exit = nullptr; - Array> _nonNativeModules; + Dictionary> _nonNativeModules; #if USE_EDITOR bool LastBinariesLoadTriggeredCompilation = false; #endif @@ -334,6 +334,8 @@ bool Scripting::LoadBinaryModules(const String& path, const String& projectFolde // Check if that module has been already registered BinaryModule* module = BinaryModule::GetModule(nameAnsi); + if (!module) + _nonNativeModules.TryGet(nameAnsi, module); if (!module) { // C++ @@ -403,7 +405,7 @@ bool Scripting::LoadBinaryModules(const String& path, const String& projectFolde { // Create module if native library is not used module = New(nameAnsi); - _nonNativeModules.Add(module); + _nonNativeModules.Add(nameAnsi, module); } } From 811d6395736bd9a31d34a37cfb046070ddce3f2a Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 29 Sep 2023 22:07:45 +0300 Subject: [PATCH 173/188] Generate Visual Studio Code build tasks for C#-projects --- .../VisualStudioCodeProjectGenerator.cs | 222 +++++++++--------- 1 file changed, 109 insertions(+), 113 deletions(-) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs index fdf1df4e2..2fdff6fa4 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs @@ -155,122 +155,118 @@ namespace Flax.Build.Projects.VisualStudioCode { foreach (var project in solution.Projects) { - // C++ project - if (project.Type == TargetType.NativeCpp) + if (project.Name == "BuildScripts") + continue; + + bool defaultTask = project == solution.MainProject; + foreach (var configuration in project.Configurations) { - bool defaultTask = project == solution.MainProject; - foreach (var configuration in project.Configurations) + var target = configuration.Target; + var name = project.Name + '|' + configuration.Name; + + json.BeginObject(); + + json.AddField("label", name); + + bool isDefaultTask = defaultTask && configuration.Configuration == TargetConfiguration.Development && configuration.Platform == Platform.BuildPlatform.Target; + + json.BeginObject("group"); { - var target = configuration.Target; - var name = project.Name + '|' + configuration.Name; - - json.BeginObject(); - - json.AddField("label", name); - - if (defaultTask && configuration.Configuration == TargetConfiguration.Development && configuration.Platform == Platform.BuildPlatform.Target) - { - defaultTask = false; - json.BeginObject("group"); - { - json.AddField("kind", "build"); - json.AddField("isDefault", true); - } - json.EndObject(); - } - else - { - json.AddField("group", "build"); - } - - switch (Platform.BuildPlatform.Target) - { - case TargetPlatform.Windows: - { - json.AddField("command", buildToolPath); - json.BeginArray("args"); - { - json.AddUnnamedField("-build"); - json.AddUnnamedField("-log"); - json.AddUnnamedField("-mutex"); - json.AddUnnamedField(string.Format("\\\"-workspace={0}\\\"", buildToolWorkspace)); - json.AddUnnamedField(string.Format("-arch={0}", configuration.ArchitectureName)); - json.AddUnnamedField(string.Format("-configuration={0}", configuration.ConfigurationName)); - json.AddUnnamedField(string.Format("-platform={0}", configuration.PlatformName)); - json.AddUnnamedField(string.Format("-buildTargets={0}", target.Name)); - if (!string.IsNullOrEmpty(Configuration.Compiler)) - json.AddUnnamedField(string.Format("-compiler={0}", Configuration.Compiler)); - } - json.EndArray(); - - json.AddField("type", "shell"); - - json.BeginObject("options"); - { - json.AddField("cwd", buildToolWorkspace); - } - json.EndObject(); - break; - } - case TargetPlatform.Linux: - { - json.AddField("command", buildToolPath); - json.BeginArray("args"); - { - json.AddUnnamedField("--build"); - json.AddUnnamedField("--log"); - json.AddUnnamedField("--mutex"); - json.AddUnnamedField(string.Format("--workspace=\\\"{0}\\\"", buildToolWorkspace)); - json.AddUnnamedField(string.Format("--arch={0}", configuration.Architecture)); - json.AddUnnamedField(string.Format("--configuration={0}", configuration.ConfigurationName)); - json.AddUnnamedField(string.Format("--platform={0}", configuration.PlatformName)); - json.AddUnnamedField(string.Format("--buildTargets={0}", target.Name)); - if (!string.IsNullOrEmpty(Configuration.Compiler)) - json.AddUnnamedField(string.Format("--compiler={0}", Configuration.Compiler)); - } - json.EndArray(); - - json.AddField("type", "shell"); - - json.BeginObject("options"); - { - json.AddField("cwd", buildToolWorkspace); - } - json.EndObject(); - break; - } - case TargetPlatform.Mac: - { - json.AddField("command", buildToolPath); - json.BeginArray("args"); - { - json.AddUnnamedField("--build"); - json.AddUnnamedField("--log"); - json.AddUnnamedField("--mutex"); - json.AddUnnamedField(string.Format("--workspace=\\\"{0}\\\"", buildToolWorkspace)); - json.AddUnnamedField(string.Format("--arch={0}", configuration.Architecture)); - json.AddUnnamedField(string.Format("--configuration={0}", configuration.ConfigurationName)); - json.AddUnnamedField(string.Format("--platform={0}", configuration.PlatformName)); - json.AddUnnamedField(string.Format("--buildTargets={0}", target.Name)); - if (!string.IsNullOrEmpty(Configuration.Compiler)) - json.AddUnnamedField(string.Format("--compiler={0}", Configuration.Compiler)); - } - json.EndArray(); - - json.AddField("type", "shell"); - - json.BeginObject("options"); - { - json.AddField("cwd", buildToolWorkspace); - } - json.EndObject(); - break; - } - default: throw new Exception("Visual Code project generator does not support current platform."); - } - - json.EndObject(); + json.AddField("kind", "build"); + json.AddField("isDefault", isDefaultTask); } + json.EndObject(); + + if (isDefaultTask) + defaultTask = false; + + switch (Platform.BuildPlatform.Target) + { + case TargetPlatform.Windows: + { + json.AddField("command", buildToolPath); + json.BeginArray("args"); + { + json.AddUnnamedField("-build"); + json.AddUnnamedField("-log"); + json.AddUnnamedField("-mutex"); + json.AddUnnamedField(string.Format("\\\"-workspace={0}\\\"", buildToolWorkspace)); + json.AddUnnamedField(string.Format("-arch={0}", configuration.ArchitectureName)); + json.AddUnnamedField(string.Format("-configuration={0}", configuration.ConfigurationName)); + json.AddUnnamedField(string.Format("-platform={0}", configuration.PlatformName)); + json.AddUnnamedField(string.Format("-buildTargets={0}", target.Name)); + if (!string.IsNullOrEmpty(Configuration.Compiler)) + json.AddUnnamedField(string.Format("-compiler={0}", Configuration.Compiler)); + } + json.EndArray(); + + json.AddField("type", "shell"); + + json.BeginObject("options"); + { + json.AddField("cwd", buildToolWorkspace); + } + json.EndObject(); + break; + } + case TargetPlatform.Linux: + { + json.AddField("command", buildToolPath); + json.BeginArray("args"); + { + json.AddUnnamedField("--build"); + json.AddUnnamedField("--log"); + json.AddUnnamedField("--mutex"); + json.AddUnnamedField(string.Format("--workspace=\\\"{0}\\\"", buildToolWorkspace)); + json.AddUnnamedField(string.Format("--arch={0}", configuration.Architecture)); + json.AddUnnamedField(string.Format("--configuration={0}", configuration.ConfigurationName)); + json.AddUnnamedField(string.Format("--platform={0}", configuration.PlatformName)); + json.AddUnnamedField(string.Format("--buildTargets={0}", target.Name)); + if (!string.IsNullOrEmpty(Configuration.Compiler)) + json.AddUnnamedField(string.Format("--compiler={0}", Configuration.Compiler)); + } + json.EndArray(); + + json.AddField("type", "shell"); + + json.BeginObject("options"); + { + json.AddField("cwd", buildToolWorkspace); + } + json.EndObject(); + break; + } + case TargetPlatform.Mac: + { + json.AddField("command", buildToolPath); + json.BeginArray("args"); + { + json.AddUnnamedField("--build"); + json.AddUnnamedField("--log"); + json.AddUnnamedField("--mutex"); + json.AddUnnamedField(string.Format("--workspace=\\\"{0}\\\"", buildToolWorkspace)); + json.AddUnnamedField(string.Format("--arch={0}", configuration.Architecture)); + json.AddUnnamedField(string.Format("--configuration={0}", configuration.ConfigurationName)); + json.AddUnnamedField(string.Format("--platform={0}", configuration.PlatformName)); + json.AddUnnamedField(string.Format("--buildTargets={0}", target.Name)); + if (!string.IsNullOrEmpty(Configuration.Compiler)) + json.AddUnnamedField(string.Format("--compiler={0}", Configuration.Compiler)); + } + json.EndArray(); + + json.AddField("type", "shell"); + + json.BeginObject("options"); + { + json.AddField("cwd", buildToolWorkspace); + } + json.EndObject(); + break; + } + default: throw new Exception("Visual Code project generator does not support current platform."); + } + + json.EndObject(); } } } From 8a19b5ddd6c4754a6ca45be7b4a20a52cb31bc9a Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 29 Sep 2023 23:46:57 +0300 Subject: [PATCH 174/188] Skip adding VSCode task and launch profiles for plugins and dependencies --- .../VisualStudioCodeProjectGenerator.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs index 2fdff6fa4..743a643f3 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs @@ -158,6 +158,10 @@ namespace Flax.Build.Projects.VisualStudioCode if (project.Name == "BuildScripts") continue; + // Skip duplicate build tasks + if (project.Name == "FlaxEngine" || (solution.MainProject.Name != "Flax" && solution.MainProject != project)) + continue; + bool defaultTask = project == solution.MainProject; foreach (var configuration in project.Configurations) { @@ -288,9 +292,15 @@ namespace Flax.Build.Projects.VisualStudioCode { foreach (var project in solution.Projects) { + if (project.Name == "BuildScripts") + continue; // C++ project if (project.Type == TargetType.NativeCpp) { + // Skip generating launch profiles for plugins and dependencies + if (solution.MainProject.Name != "Flax" && project.Name != "Flax.Build" && solution.MainProject.WorkspaceRootPath != project.WorkspaceRootPath) + continue; + foreach (var configuration in project.Configurations) { var name = project.Name + '|' + configuration.Name; From bf2c10b036a484705add7e3b8b0a7409eb67df69 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 29 Sep 2023 23:50:15 +0300 Subject: [PATCH 175/188] Generate one VSCode debugger attach launch profile per debugger type --- .../VisualStudioCodeProjectGenerator.cs | 238 +++++++++--------- 1 file changed, 115 insertions(+), 123 deletions(-) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs index 743a643f3..f1f7e415e 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs @@ -281,6 +281,9 @@ namespace Flax.Build.Projects.VisualStudioCode json.Save(Path.Combine(vsCodeFolder, "tasks.json")); } + bool hasMonoProjects = solution.Projects.Any(x => x.Type == TargetType.DotNet); + bool hasDotnetProjects = solution.Projects.Any(x => x.Type == TargetType.DotNetCore); + // Create launch file using (var json = new JsonWriter()) { @@ -320,115 +323,49 @@ namespace Flax.Build.Projects.VisualStudioCode json.AddField("preLaunchTask", name); json.AddField("cwd", buildToolWorkspace); - switch (Platform.BuildPlatform.Target) + WriteNativePlatformLaunchSettings(json, configuration.Platform); + + if (outputType != TargetOutputType.Executable && configuration.Name.StartsWith("Editor.")) { - case TargetPlatform.Windows: - if (configuration.Platform == TargetPlatform.Windows && outputType != TargetOutputType.Executable && configuration.Name.StartsWith("Editor.")) + if (configuration.Platform == TargetPlatform.Windows) { var editorFolder = configuration.Architecture == TargetArchitecture.x64 ? "Win64" : "Win32"; json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", editorFolder, configuration.ConfigurationName, "FlaxEditor.exe")); - json.BeginArray("args"); + } + else if (configuration.Platform == TargetPlatform.Linux) + json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", "Linux", configuration.ConfigurationName, "FlaxEditor")); + else if (configuration.Platform == TargetPlatform.Mac) + json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", "Mac", configuration.ConfigurationName, "FlaxEditor")); + + json.BeginArray("args"); + { + json.AddUnnamedField("-project"); + json.AddUnnamedField(buildToolWorkspace); + json.AddUnnamedField("-skipCompile"); + if (hasMonoProjects) { - json.AddUnnamedField("-project"); - json.AddUnnamedField(buildToolWorkspace); - json.AddUnnamedField("-skipCompile"); json.AddUnnamedField("-debug"); json.AddUnnamedField("127.0.0.1:55555"); } - json.EndArray(); } - else - { - json.AddField("program", outputTargetFilePath); - } - break; - case TargetPlatform.Linux: - if (configuration.Platform == TargetPlatform.Linux && (outputType != TargetOutputType.Executable || project.BaseName == "Flax") && configuration.Name.StartsWith("Editor.")) - { - json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", "Linux", configuration.ConfigurationName, "FlaxEditor")); - } - else - { - json.AddField("program", outputTargetFilePath); - } - if (configuration.Platform == TargetPlatform.Linux) - { - json.AddField("MIMode", "gdb"); - json.BeginArray("setupCommands"); - { - json.BeginObject(); - json.AddField("description", "Enable pretty-printing for gdb"); - json.AddField("text", "-enable-pretty-printing"); - json.AddField("ignoreFailures", true); - json.EndObject(); - - // Ignore signals used by C# runtime - json.BeginObject(); - json.AddField("description", "ignore SIG34 signal"); - json.AddField("text", "handle SIG34 nostop noprint pass"); - json.EndObject(); - json.BeginObject(); - json.AddField("description", "ignore SIG35 signal"); - json.AddField("text", "handle SIG35 nostop noprint pass"); - json.EndObject(); - json.BeginObject(); - json.AddField("description", "ignore SIG36 signal"); - json.AddField("text", "handle SIG36 nostop noprint pass"); - json.EndObject(); - json.BeginObject(); - json.AddField("description", "ignore SIG357 signal"); - json.AddField("text", "handle SIG37 nostop noprint pass"); - json.EndObject(); - } - json.EndArray(); - json.BeginArray("args"); - { - json.AddUnnamedField("--std"); - if (outputType != TargetOutputType.Executable && configuration.Name.StartsWith("Editor.")) - { - json.AddUnnamedField("--project"); - json.AddUnnamedField(buildToolWorkspace); - json.AddUnnamedField("--skipCompile"); - } - } - json.EndArray(); - } - break; - case TargetPlatform.Mac: - if (configuration.Platform == TargetPlatform.Mac && (outputType != TargetOutputType.Executable || project.BaseName == "Flax") && configuration.Name.StartsWith("Editor.")) - { - json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", "Mac", configuration.ConfigurationName, "FlaxEditor")); - } - else - { - json.AddField("program", outputTargetFilePath); - } - if (configuration.Platform == TargetPlatform.Mac) - { - json.AddField("MIMode", "lldb"); - json.BeginArray("args"); - { - json.AddUnnamedField("--std"); - if (outputType != TargetOutputType.Executable && configuration.Name.StartsWith("Editor.")) - { - json.AddUnnamedField("--project"); - json.AddUnnamedField(buildToolWorkspace); - json.AddUnnamedField("--skipCompile"); - } - } - json.EndArray(); - } - break; + json.EndArray(); } - switch (configuration.Platform) + else { - case TargetPlatform.Windows: - json.AddField("stopAtEntry", false); - json.AddField("externalConsole", true); - break; - case TargetPlatform.Linux: - break; + json.AddField("program", outputTargetFilePath); + json.BeginArray("args"); + { + if (configuration.Platform == TargetPlatform.Linux || configuration.Platform == TargetPlatform.Mac) + json.AddUnnamedField("--std"); + if (hasMonoProjects) + { + json.AddUnnamedField("-debug"); + json.AddUnnamedField("127.0.0.1:55555"); + } + } + json.EndArray(); } + json.AddField("visualizerFile", Path.Combine(Globals.EngineRoot, "Source", "flax.natvis")); } json.EndObject(); @@ -437,16 +374,9 @@ namespace Flax.Build.Projects.VisualStudioCode // C# project else if (project.Type == TargetType.DotNetCore) { - // TODO: Skip generating launch profiles for plugins and dependencies - - json.BeginObject(); - { - json.AddField("type", "coreclr"); - json.AddField("name", project.Name + " (C# attach Editor)"); - json.AddField("request", "attach"); - json.AddField("processName", "FlaxEditor"); - } - json.EndObject(); + // Skip generating launch profiles for plugins and dependencies + if (solution.MainProject.WorkspaceRootPath != project.WorkspaceRootPath) + continue; foreach (var configuration in project.Configurations) { @@ -490,24 +420,33 @@ namespace Flax.Build.Projects.VisualStudioCode json.EndObject(); } } - // Mono C# project - else if (project.Type == TargetType.DotNet) - { - foreach (var configuration in project.Configurations) - { - json.BeginObject(); - { - json.AddField("type", "mono"); - json.AddField("name", project.Name + " (C# attach)" + '|' + configuration.Name); - json.AddField("request", "attach"); - json.AddField("address", "localhost"); - json.AddField("port", 55555); - } - json.EndObject(); - } - } } } + + if (hasDotnetProjects) + { + json.BeginObject(); + { + json.AddField("type", "coreclr"); + json.AddField("name", solution.Name + " (C# Attach Editor)"); + json.AddField("request", "attach"); + json.AddField("processName", "FlaxEditor"); + } + json.EndObject(); + } + if (hasMonoProjects) + { + json.BeginObject(); + { + json.AddField("type", "mono"); + json.AddField("name", solution.Name + " (C# Attach)"); + json.AddField("request", "attach"); + json.AddField("address", "localhost"); + json.AddField("port", 55555); + } + json.EndObject(); + } + json.EndArray(); } json.EndRootObject(); @@ -515,6 +454,59 @@ namespace Flax.Build.Projects.VisualStudioCode json.Save(Path.Combine(vsCodeFolder, "launch.json")); } + static void WriteNativePlatformLaunchSettings(JsonWriter json, TargetPlatform platform) + { + switch (Platform.BuildPlatform.Target) + { + case TargetPlatform.Linux: + if (platform == TargetPlatform.Linux) + { + json.AddField("MIMode", "gdb"); + json.BeginArray("setupCommands"); + { + json.BeginObject(); + json.AddField("description", "Enable pretty-printing for gdb"); + json.AddField("text", "-enable-pretty-printing"); + json.AddField("ignoreFailures", true); + json.EndObject(); + + // Ignore signals used by C# runtime + json.BeginObject(); + json.AddField("description", "ignore SIG34 signal"); + json.AddField("text", "handle SIG34 nostop noprint pass"); + json.EndObject(); + json.BeginObject(); + json.AddField("description", "ignore SIG35 signal"); + json.AddField("text", "handle SIG35 nostop noprint pass"); + json.EndObject(); + json.BeginObject(); + json.AddField("description", "ignore SIG36 signal"); + json.AddField("text", "handle SIG36 nostop noprint pass"); + json.EndObject(); + json.BeginObject(); + json.AddField("description", "ignore SIG357 signal"); + json.AddField("text", "handle SIG37 nostop noprint pass"); + json.EndObject(); + } + json.EndArray(); + } + break; + case TargetPlatform.Mac: + if (platform == TargetPlatform.Mac) + { + json.AddField("MIMode", "lldb"); + } + break; + } + switch (platform) + { + case TargetPlatform.Windows: + json.AddField("stopAtEntry", false); + json.AddField("externalConsole", true); + break; + } + } + // Create C++ properties file using (var json = new JsonWriter()) { From 8b8970e4b96527d4b363feef89fc0cc441b36cb2 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 29 Sep 2023 23:50:50 +0300 Subject: [PATCH 176/188] Generate VSCode attach profile for native debugging --- .../VisualStudioCodeProjectGenerator.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs index f1f7e415e..887197258 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs @@ -281,6 +281,7 @@ namespace Flax.Build.Projects.VisualStudioCode json.Save(Path.Combine(vsCodeFolder, "tasks.json")); } + bool hasNativeProjects = solution.Projects.Any(x => x.Type == TargetType.NativeCpp); bool hasMonoProjects = solution.Projects.Any(x => x.Type == TargetType.DotNet); bool hasDotnetProjects = solution.Projects.Any(x => x.Type == TargetType.DotNetCore); @@ -423,6 +424,27 @@ namespace Flax.Build.Projects.VisualStudioCode } } + if (hasNativeProjects) + { + foreach (var platform in solution.Projects.SelectMany(x => x.Configurations).Select(x => x.Platform).Distinct()) + { + json.BeginObject(); + { + if (platform == TargetPlatform.Windows) + json.AddField("type", "cppvsdbg"); + else + json.AddField("type", "cppdbg"); + json.AddField("name", solution.Name + " (Attach Editor)"); + json.AddField("request", "attach"); + json.AddField("processId", "${command:pickProcess}"); // Does not seem to be possible to attach by process name? + + WriteNativePlatformLaunchSettings(json, platform); + + json.AddField("visualizerFile", Path.Combine(Globals.EngineRoot, "Source", "flax.natvis")); + } + json.EndObject(); + } + } if (hasDotnetProjects) { json.BeginObject(); From c9324004eb818a55c9e14a8371989fad123e1e7e Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 30 Sep 2023 01:22:02 +0300 Subject: [PATCH 177/188] Build C# bindings after generating engine project files --- GenerateProjectFiles.bat | 15 +++++++++++---- GenerateProjectFiles.command | 5 +++++ GenerateProjectFiles.sh | 5 +++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/GenerateProjectFiles.bat b/GenerateProjectFiles.bat index 1b766457f..622939c34 100644 --- a/GenerateProjectFiles.bat +++ b/GenerateProjectFiles.bat @@ -1,15 +1,22 @@ @echo off - -rem Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +:: Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. setlocal pushd + echo Generating Flax Engine project files... -rem Run Flax.Build to generate Visual Studio solution and project files (also pass the arguments) -call "Development\Scripts\Windows\CallBuildTool.bat" -genproject %* +:: Change the path to the script root +cd /D "%~dp0" + +:: Run Flax.Build to generate Visual Studio solution and project files (also pass the arguments) +call "Development\Scripts\Windows\CallBuildTool.bat" -genproject %* if errorlevel 1 goto BuildToolFailed +:: Build bindings for all editor configurations +echo Building C# bindings... +Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor,FlaxGame + popd echo Done! exit /B 0 diff --git a/GenerateProjectFiles.command b/GenerateProjectFiles.command index 6554886bc..5ee5c0783 100755 --- a/GenerateProjectFiles.command +++ b/GenerateProjectFiles.command @@ -10,3 +10,8 @@ cd "`dirname "$0"`" # Run Flax.Build to generate project files (also pass the arguments) bash ./Development/Scripts/Mac/CallBuildTool.sh --genproject "$@" + +# Build bindings for all editor configurations +echo Building C# bindings... +# TODO: Detect the correct architecture here +Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor,FlaxGame diff --git a/GenerateProjectFiles.sh b/GenerateProjectFiles.sh index fcda1acc1..dceb8abe8 100755 --- a/GenerateProjectFiles.sh +++ b/GenerateProjectFiles.sh @@ -10,3 +10,8 @@ cd "`dirname "$0"`" # Run Flax.Build to generate project files (also pass the arguments) bash ./Development/Scripts/Linux/CallBuildTool.sh --genproject "$@" + +# Build bindings for all editor configurations +echo Building C# bindings... +# TODO: Detect the correct architecture here +Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=x64 -platform=Linux --buildTargets=FlaxEditor,FlaxGame From cc318dddd7dd1bade4a269bfe39a402c335a0441 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 30 Sep 2023 02:36:05 +0300 Subject: [PATCH 178/188] Remap non-native Editor VS build configurations to native configurations --- .../VisualStudioProjectGenerator.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index 6dcc12a4f..debf827a6 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -479,7 +479,7 @@ namespace Flax.Build.Projects.VisualStudio { SolutionConfiguration projectConfiguration; bool build = false; - int firstFullMatch = -1, firstPlatformMatch = -1; + int firstFullMatch = -1, firstPlatformMatch = -1, firstEditorMatch = -1; for (int i = 0; i < project.Configurations.Count; i++) { var e = new SolutionConfiguration(project.Configurations[i]); @@ -492,18 +492,31 @@ namespace Flax.Build.Projects.VisualStudio { firstPlatformMatch = i; } + if (firstEditorMatch == -1 && e.Configuration == configuration.Configuration) + { + firstEditorMatch = i; + } } if (firstFullMatch != -1) { projectConfiguration = configuration; build = solution.MainProject == project || (solution.MainProject == null && project.Name == solution.Name); } - else if (firstPlatformMatch != -1) + else if (firstPlatformMatch != -1 && !configuration.Name.StartsWith("Editor.")) { + // No exact match, pick the first configuration for matching platform projectConfiguration = new SolutionConfiguration(project.Configurations[firstPlatformMatch]); } + else if (firstEditorMatch != -1 && configuration.Name.StartsWith("Editor.")) + { + // No exact match, pick the matching editor configuration for different platform. + // As an example, Editor configuration for Android projects should be remapped + // to desktop platform in order to provide working Intellisense information. + projectConfiguration = new SolutionConfiguration(project.Configurations[firstEditorMatch]); + } else { + // No match projectConfiguration = new SolutionConfiguration(project.Configurations[0]); } From 6483f95450b2feb76c8e32facd5751898dbbf3dc Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 30 Sep 2023 03:25:58 +0300 Subject: [PATCH 179/188] Fix Visual Studio project folder GUIDs randomization during regeneration --- .../VisualStudio/VisualStudioProjectGenerator.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index 6dcc12a4f..21db89d60 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -166,12 +166,14 @@ namespace Flax.Build.Projects.VisualStudio { try { - Regex projectRegex = new Regex(@"Project\(.*\) = \""(\S+)\"", \""(\S+)\"", \""{(\S+)}\"""); + Regex projectRegex = new Regex(@"Project\(""{(\S+)}""\) = \""(\S+)\"", \""(\S+)\"", \""{(\S+)}\"""); MatchCollection matches = projectRegex.Matches(File.ReadAllText(path)); for (int i = 0; i < matches.Count; i++) { - if (matches[i].Groups[1].Value == projectName) - return Guid.ParseExact(matches[i].Groups[3].Value, "D"); + if (matches[i].Groups[1].Value.Equals("2150E333-8FDC-42A3-9474-1A3956D46DE8", StringComparison.OrdinalIgnoreCase)) + continue; + if (matches[i].Groups[2].Value == projectName) + return Guid.ParseExact(matches[i].Groups[4].Value, "D"); } } catch @@ -376,7 +378,8 @@ namespace Flax.Build.Projects.VisualStudio { if (!folderIds.TryGetValue(folderPath, out project.FolderGuid)) { - project.FolderGuid = Guid.NewGuid(); + if (!folderIds.TryGetValue(folderParents[i], out project.FolderGuid)) + project.FolderGuid = Guid.NewGuid(); folderIds.Add(folderPath, project.FolderGuid); } folderNames.Add(folderPath); From 85a985cd7fa6592ec09d34b858ed6c5a7ce16f3b Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 30 Sep 2023 13:52:20 -0500 Subject: [PATCH 180/188] Expose Main Window to c# through Screen class. --- Source/Engine/Engine/Screen.cpp | 9 +++++++++ Source/Engine/Engine/Screen.h | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp index 2bc6e1f58..207d6a87c 100644 --- a/Source/Engine/Engine/Screen.cpp +++ b/Source/Engine/Engine/Screen.cpp @@ -181,6 +181,15 @@ void Screen::SetGameWindowMode(GameWindowMode windowMode) #endif } +Window* Screen::GetMainWindow() +{ + Window* win = nullptr; +#if !USE_EDITOR + win = Engine::MainWindow; +#endif + return win; +} + void ScreenService::Update() { #if USE_EDITOR diff --git a/Source/Engine/Engine/Screen.h b/Source/Engine/Engine/Screen.h index 42be20a38..7785ee7b3 100644 --- a/Source/Engine/Engine/Screen.h +++ b/Source/Engine/Engine/Screen.h @@ -96,4 +96,10 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Screen); /// /// The window mode. API_PROPERTY() static void SetGameWindowMode(GameWindowMode windowMode); + + /// + /// Gets the main window. Only during built game. + /// + /// The current window. Will be null if fails. + API_PROPERTY() static Window* GetMainWindow(); }; From 1366c2850f748ccb7613b076d86fdf7cdaac8214 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 30 Sep 2023 17:21:48 -0500 Subject: [PATCH 181/188] Fix `Actor.RotateAround` to rotate the actors orientation. --- Source/Engine/Level/Actor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index 661b350cb..ff3c7c4fa 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -370,7 +370,7 @@ namespace FlaxEngine var q = Quaternion.RotationAxis(axis, angle * Mathf.DegreesToRadians); var dif = (transform.Translation - point) * q; transform.Translation = point + dif; - transform.Orientation = q; + transform.Orientation *= q; Transform = transform; } From bae1cfbc8a073221a58e96d99a63015487e74a9d Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 30 Sep 2023 17:35:42 -0500 Subject: [PATCH 182/188] Change base editor window size to 75% of desktop size when restored for first time. --- Source/Editor/Modules/WindowsModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index 086772ca2..d0c8b9747 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -721,7 +721,7 @@ namespace FlaxEditor.Modules // Create main window var settings = CreateWindowSettings.Default; settings.Title = "Flax Editor"; - //settings.Size = Platform.DesktopSize; + settings.Size = Platform.DesktopSize * 0.75f; settings.StartPosition = WindowStartPosition.CenterScreen; settings.ShowAfterFirstPaint = true; From 260c48e41de9ac6e939635820450c37157d70f47 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 30 Sep 2023 17:44:42 -0500 Subject: [PATCH 183/188] Change blur panel to draw self. Fix issue with blur strength slider. --- Source/Engine/UI/GUI/Panels/BlurPanel.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Source/Engine/UI/GUI/Panels/BlurPanel.cs b/Source/Engine/UI/GUI/Panels/BlurPanel.cs index 0fe506e44..055535aa8 100644 --- a/Source/Engine/UI/GUI/Panels/BlurPanel.cs +++ b/Source/Engine/UI/GUI/Panels/BlurPanel.cs @@ -11,7 +11,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets the blur strength. Defines how blurry the background is. Larger numbers increase blur, resulting in a larger runtime cost on the GPU. /// - [EditorOrder(0), Limit(0, 100, 0.0f)] + [EditorOrder(0), Limit(0, 100, 0.1f)] public float BlurStrength { get; set; } /// @@ -29,10 +29,9 @@ namespace FlaxEngine.GUI } /// - public override void Draw() + public override void DrawSelf() { - base.Draw(); - + base.DrawSelf(); var size = Size; var strength = BlurStrength; if (BlurScaleWithSize) From cc681de30edf9e331777d5afb0cd6a6f347f571a Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 30 Sep 2023 21:33:27 -0500 Subject: [PATCH 184/188] Fix more and add bool check to orient the actor or not. --- Source/Engine/Level/Actor.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index ff3c7c4fa..bcb91a3bc 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -364,13 +364,20 @@ namespace FlaxEngine /// The point (world-space). /// The axis (normalized). /// The angle (in degrees). - public void RotateAround(Vector3 point, Vector3 axis, float angle) + /// /// Whether to orient the actor the same amount as rotation. + public void RotateAround(Vector3 point, Vector3 axis, float angle, bool orientActor = true) { var transform = Transform; var q = Quaternion.RotationAxis(axis, angle * Mathf.DegreesToRadians); var dif = (transform.Translation - point) * q; - transform.Translation = point + dif; - transform.Orientation *= q; + if (Vector3.NearEqual(point, transform.Translation)) + transform.Orientation *= q; + else + { + transform.Translation = point + dif; + if (orientActor) + transform.Orientation *= q; + } Transform = transform; } From 8edd50c293b0dfbcbd5b9c9ba5e10ee0337564d5 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 30 Sep 2023 21:34:40 -0500 Subject: [PATCH 185/188] Simplify --- Source/Engine/Level/Actor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index bcb91a3bc..79e3d4716 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -369,11 +369,11 @@ namespace FlaxEngine { var transform = Transform; var q = Quaternion.RotationAxis(axis, angle * Mathf.DegreesToRadians); - var dif = (transform.Translation - point) * q; if (Vector3.NearEqual(point, transform.Translation)) transform.Orientation *= q; else { + var dif = (transform.Translation - point) * q; transform.Translation = point + dif; if (orientActor) transform.Orientation *= q; From 113307ddf9e52128added38793c74bd9784f0895 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 30 Sep 2023 21:36:41 -0500 Subject: [PATCH 186/188] Simplify to return main window even in editor. --- Source/Engine/Engine/Screen.cpp | 6 +----- Source/Engine/Engine/Screen.h | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp index 207d6a87c..fc7e8a022 100644 --- a/Source/Engine/Engine/Screen.cpp +++ b/Source/Engine/Engine/Screen.cpp @@ -183,11 +183,7 @@ void Screen::SetGameWindowMode(GameWindowMode windowMode) Window* Screen::GetMainWindow() { - Window* win = nullptr; -#if !USE_EDITOR - win = Engine::MainWindow; -#endif - return win; + return Engine::MainWindow; } void ScreenService::Update() diff --git a/Source/Engine/Engine/Screen.h b/Source/Engine/Engine/Screen.h index 7785ee7b3..b7bd89c38 100644 --- a/Source/Engine/Engine/Screen.h +++ b/Source/Engine/Engine/Screen.h @@ -98,7 +98,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Screen); API_PROPERTY() static void SetGameWindowMode(GameWindowMode windowMode); /// - /// Gets the main window. Only during built game. + /// Gets the main window. /// /// The current window. Will be null if fails. API_PROPERTY() static Window* GetMainWindow(); From b943c7ca3ebfd9a163d342a6bb0021bb2b715279 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 30 Sep 2023 22:19:09 -0500 Subject: [PATCH 187/188] Add extra check for stationary rotation. --- Source/Engine/Level/Actor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index 79e3d4716..dbe8a89b5 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -369,7 +369,7 @@ namespace FlaxEngine { var transform = Transform; var q = Quaternion.RotationAxis(axis, angle * Mathf.DegreesToRadians); - if (Vector3.NearEqual(point, transform.Translation)) + if (Vector3.NearEqual(point, transform.Translation) && orientActor) transform.Orientation *= q; else { From 0eda67bd738dbb061a9214700bb1d5a0626ea362 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 1 Oct 2023 11:56:54 +0200 Subject: [PATCH 188/188] Fix nested profiler events usage #https://github.com/FlaxEngine/FlaxEngine/issues/1380 --- Source/Engine/Profiler/ProfilerCPU.cpp | 11 ++++-- Source/Engine/Profiler/ProfilerCPU.h | 48 ++++++++++++++++++-------- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/Source/Engine/Profiler/ProfilerCPU.cpp b/Source/Engine/Profiler/ProfilerCPU.cpp index 26f9e49c2..7b9e2c6e3 100644 --- a/Source/Engine/Profiler/ProfilerCPU.cpp +++ b/Source/Engine/Profiler/ProfilerCPU.cpp @@ -129,8 +129,15 @@ void ProfilerCPU::Thread::EndEvent() { const double time = Platform::GetTimeSeconds() * 1000.0; _depth--; - Event& e = (Buffer.Last()--).Event(); - e.End = time; + for (auto i = Buffer.Last(); i != Buffer.Begin(); --i) + { + Event& e = i.Event(); + if (e.End <= 0) + { + e.End = time; + break; + } + } } bool ProfilerCPU::IsProfilingCurrentThread() diff --git a/Source/Engine/Profiler/ProfilerCPU.h b/Source/Engine/Profiler/ProfilerCPU.h index 124082c9b..ccfeb8ac4 100644 --- a/Source/Engine/Profiler/ProfilerCPU.h +++ b/Source/Engine/Profiler/ProfilerCPU.h @@ -121,15 +121,39 @@ public: EventBuffer* _buffer; int32 _index; - Iterator(EventBuffer* buffer, const int32 index) + FORCE_INLINE Iterator(EventBuffer* buffer, const int32 index) : _buffer(buffer) , _index(index) { } - Iterator(const Iterator& i) = default; - public: + FORCE_INLINE Iterator(const Iterator& other) + : _buffer(other._buffer) + , _index(other._index) + { + } + + FORCE_INLINE Iterator(Iterator&& other) noexcept + : _buffer(other._buffer) + , _index(other._index) + { + } + + FORCE_INLINE Iterator& operator=(Iterator&& other) + { + _buffer = other._buffer; + _index = other._index; + return *this; + } + + FORCE_INLINE Iterator& operator=(const Iterator& other) + { + _buffer = other._buffer; + _index = other._index; + return *this; + } + FORCE_INLINE int32 Index() const { return _index; @@ -141,15 +165,13 @@ public: return _buffer->Get(_index); } - bool IsEnd() const + FORCE_INLINE bool IsEnd() const { - ASSERT_LOW_LAYER(_buffer); return _index == _buffer->_head; } - bool IsNotEnd() const + FORCE_INLINE bool IsNotEnd() const { - ASSERT_LOW_LAYER(_buffer); return _index != _buffer->_head; } @@ -164,31 +186,27 @@ public: } public: - Iterator& operator++() + FORCE_INLINE Iterator& operator++() { - ASSERT(_buffer); _index = (_index + 1) & _buffer->_capacityMask; return *this; } - Iterator operator++(int) + FORCE_INLINE Iterator operator++(int) { - ASSERT(_buffer); Iterator temp = *this; _index = (_index + 1) & _buffer->_capacityMask; return temp; } - Iterator& operator--() + FORCE_INLINE Iterator& operator--() { - ASSERT(_buffer); _index = (_index - 1) & _buffer->_capacityMask; return *this; } - Iterator operator--(int) + FORCE_INLINE Iterator operator--(int) { - ASSERT(_buffer); Iterator temp = *this; _index = (_index - 1) & _buffer->_capacityMask; return temp;