From d4d404ac0b054573ee2eaf5d3c4f8140658fd27a Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 5 Aug 2023 12:48:42 +0300 Subject: [PATCH 01/24] 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 02/24] 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 03/24] 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 ccb3f40de00dde8030982eb08428872a6b98b29b Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 20 Sep 2023 17:58:08 -0500 Subject: [PATCH 04/24] 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 05/24] 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 bd864c42fae9ae227721cf4a4a3cdc3847227711 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 22 Sep 2023 08:40:38 -0500 Subject: [PATCH 06/24] 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 642766d9cc1bcf1395549b0434c9f64c4863847d Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 26 Aug 2023 12:19:37 +0300 Subject: [PATCH 07/24] 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 08/24] 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 09/24] 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 10/24] 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 11/24] 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 12/24] 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 13/24] 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 14/24] 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 15/24] 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 16/24] 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 17/24] 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 18/24] 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 19/24] 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 87ef35cd4fe9bf40351eba2dba45b8c0ee9fc0bd Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 24 Sep 2023 15:14:28 -0500 Subject: [PATCH 20/24] 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 21/24] 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 82b2f46b480583150722c15fef381baa5d4c938e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 28 Sep 2023 19:21:06 +0200 Subject: [PATCH 22/24] 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 23/24] 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 24/24] 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))