21 Commits

Author SHA1 Message Date
3008d8037d _unbox
Some checks failed
Build Android / Game (Android, Release ARM64) (push) Has been cancelled
Build iOS / Game (iOS, Release ARM64) (push) Has been cancelled
Build Linux / Editor (Linux, Development x64) (push) Has been cancelled
Build Linux / Game (Linux, Release x64) (push) Has been cancelled
Build macOS / Editor (Mac, Development ARM64) (push) Has been cancelled
Build macOS / Game (Mac, Release ARM64) (push) Has been cancelled
Build Windows / Editor (Windows, Development x64) (push) Has been cancelled
Build Windows / Game (Windows, Release x64) (push) Has been cancelled
Cooker / Cook (Mac) (push) Has been cancelled
Tests / Tests (Linux) (push) Has been cancelled
Tests / Tests (Windows) (push) Has been cancelled
2025-12-21 20:17:24 +02:00
0973363c64 wip 2 no leaks 2025-12-21 17:45:46 +02:00
5d45b9ea1c Fix managed boolean array conversion to native array 2025-12-20 01:51:37 +02:00
c40f7c12f2 Fix incorrect class namespace in bindings class name lookups 2025-12-19 23:57:31 +02:00
1b2d6372b2 Fix warnings 2025-12-19 13:16:35 +02:00
0782ea889c Fix command-line parsing for SDL CreateProcess arguments 2025-12-19 13:16:35 +02:00
ef89501111 Add function for parsing command-line arguments into argument list 2025-12-19 13:16:35 +02:00
608353b996 _managed handle pool start 2025-12-19 13:14:11 +02:00
221325ef09 _freemanaged dllexport used 2025-12-19 13:14:10 +02:00
8f57c91a9e _track disabled 2025-12-19 13:14:10 +02:00
968de34cae _wip 2025-12-19 13:14:10 +02:00
6586a98f8d Ensure managed converter functions are exported with optimizations 2025-12-19 13:14:10 +02:00
b3510b0e44 Add USED attribute for forcing compiler to emit the symbol 2025-12-19 13:14:10 +02:00
d5a92c1942 Fix missing exports for managed converter structures 2025-12-19 13:14:10 +02:00
417f82826f Expose RenderContext to scripting API 2025-12-19 13:14:10 +02:00
63bed0986a Fix clang bindings code generation for non-const ref parameters 2025-12-19 13:14:10 +02:00
c40eefc79d Fix compilation errors with /permissive- standard conformance mode 2025-12-19 13:14:10 +02:00
d68631dd20 Fix Editor project file generation to use custom code editor arguments 2025-12-19 13:14:10 +02:00
e77b772010 Get code editor name through CodeEditingManager 2025-12-19 13:14:10 +02:00
e41e956386 Fix default code editor not using Visual Studio as fallback editor 2025-12-19 13:14:10 +02:00
33e47c646b Fix invalid code editor when selected editor is not detected 2025-12-19 13:14:10 +02:00
46 changed files with 2050 additions and 373 deletions

View File

@@ -15,7 +15,7 @@ if errorlevel 1 goto BuildToolFailed
:: Build bindings for all editor configurations
echo Building C# bindings...
Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor
Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor,FlaxGame
popd
echo Done!

View File

@@ -334,7 +334,7 @@ Window* ManagedEditor::GetMainWindow()
ASSERT(HasManagedInstance());
const auto method = GetClass()->GetMethod("GetMainWindowPtr");
ASSERT(method);
return (Window*)MUtils::Unbox<void*>(method->Invoke(GetManagedInstance(), nullptr, nullptr));
return (Window*)MUtils::Unbox<void*>(method->Invoke(GetManagedInstance(), nullptr, nullptr), true);
}
bool ManagedEditor::CanReloadScripts()
@@ -346,7 +346,7 @@ bool ManagedEditor::CanReloadScripts()
Internal_CanReloadScripts = GetClass()->GetMethod("Internal_CanReloadScripts");
ASSERT(Internal_CanReloadScripts);
}
return MUtils::Unbox<bool>(Internal_CanReloadScripts->Invoke(GetManagedInstance(), nullptr, nullptr));
return MUtils::Unbox<bool>(Internal_CanReloadScripts->Invoke(GetManagedInstance(), nullptr, nullptr), true);
}
bool ManagedEditor::CanAutoBuildCSG()
@@ -365,7 +365,7 @@ bool ManagedEditor::CanAutoBuildCSG()
Internal_CanAutoBuildCSG = GetClass()->GetMethod("Internal_CanAutoBuildCSG");
ASSERT(Internal_CanAutoBuildCSG);
}
return MUtils::Unbox<bool>(Internal_CanAutoBuildCSG->Invoke(GetManagedInstance(), nullptr, nullptr));
return MUtils::Unbox<bool>(Internal_CanAutoBuildCSG->Invoke(GetManagedInstance(), nullptr, nullptr), true);
}
bool ManagedEditor::CanAutoBuildNavMesh()
@@ -384,7 +384,7 @@ bool ManagedEditor::CanAutoBuildNavMesh()
Internal_CanAutoBuildNavMesh = GetClass()->GetMethod("Internal_CanAutoBuildNavMesh");
ASSERT(Internal_CanAutoBuildNavMesh);
}
return MUtils::Unbox<bool>(Internal_CanAutoBuildNavMesh->Invoke(GetManagedInstance(), nullptr, nullptr));
return MUtils::Unbox<bool>(Internal_CanAutoBuildNavMesh->Invoke(GetManagedInstance(), nullptr, nullptr), true);
}
bool ManagedEditor::HasGameViewportFocus() const
@@ -397,7 +397,8 @@ bool ManagedEditor::HasGameViewportFocus() const
Internal_HasGameViewportFocus = GetClass()->GetMethod("Internal_HasGameViewportFocus");
ASSERT(Internal_HasGameViewportFocus);
}
result = MUtils::Unbox<bool>(Internal_HasGameViewportFocus->Invoke(GetManagedInstance(), nullptr, nullptr));
auto invk = Internal_HasGameViewportFocus->Invoke(GetManagedInstance(), nullptr, nullptr);
result = MUtils::Unbox<bool>(invk, true);
}
return result;
}
@@ -495,7 +496,7 @@ bool ManagedEditor::OnAppExit()
Internal_OnAppExit = GetClass()->GetMethod("Internal_OnAppExit");
ASSERT(Internal_OnAppExit);
}
return MUtils::Unbox<bool>(Internal_OnAppExit->Invoke(GetManagedInstance(), nullptr, nullptr));
return MUtils::Unbox<bool>(Internal_OnAppExit->Invoke(GetManagedInstance(), nullptr, nullptr), true);
}
void ManagedEditor::RequestStartPlayOnEditMode()

View File

@@ -334,6 +334,9 @@ namespace FlaxEditor.Modules.SourceCodeEditing
}
}
}
if (editor == null)
editor = Editor.Instance.CodeEditing.Editors[0];
Editor.Instance.CodeEditing.SelectedEditor = editor;
}

View File

@@ -41,9 +41,9 @@ namespace FlaxEditor.Modules.SourceCodeEditing
var vsCode = codeEditing.GetInBuildEditor(CodeEditorTypes.VSCode);
var rider = codeEditing.GetInBuildEditor(CodeEditorTypes.Rider);
#if PLATFORM_WINDOW
#if PLATFORM_WINDOWS
// Favor the newest Visual Studio
for (int i = (int)CodeEditorTypes.VS2019; i >= (int)CodeEditorTypes.VS2008; i--)
for (int i = (int)CodeEditorTypes.VS2026; i >= (int)CodeEditorTypes.VS2008; i--)
{
var visualStudio = codeEditing.GetInBuildEditor((CodeEditorTypes)i);
if (visualStudio != null)
@@ -74,7 +74,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing
public string Name => "Default";
/// <inheritdoc />
public string GenerateProjectCustomArgs => null;
public string GenerateProjectCustomArgs => _currentEditor?.GenerateProjectCustomArgs;
/// <inheritdoc />
public void OpenSolution()

View File

@@ -22,71 +22,14 @@ namespace FlaxEditor.Modules.SourceCodeEditing
public InBuildSourceCodeEditor(CodeEditorTypes type)
{
Type = type;
switch (type)
{
case CodeEditorTypes.Custom:
Name = "Custom";
break;
case CodeEditorTypes.SystemDefault:
Name = "System Default";
break;
case CodeEditorTypes.VS2008:
Name = "Visual Studio 2008";
break;
case CodeEditorTypes.VS2010:
Name = "Visual Studio 2010";
break;
case CodeEditorTypes.VS2012:
Name = "Visual Studio 2012";
break;
case CodeEditorTypes.VS2013:
Name = "Visual Studio 2013";
break;
case CodeEditorTypes.VS2015:
Name = "Visual Studio 2015";
break;
case CodeEditorTypes.VS2017:
Name = "Visual Studio 2017";
break;
case CodeEditorTypes.VS2019:
Name = "Visual Studio 2019";
break;
case CodeEditorTypes.VS2022:
Name = "Visual Studio 2022";
break;
case CodeEditorTypes.VS2026:
Name = "Visual Studio 2026";
break;
case CodeEditorTypes.VSCode:
Name = "Visual Studio Code";
break;
case CodeEditorTypes.VSCodeInsiders:
Name = "Visual Studio Code - Insiders";
break;
case CodeEditorTypes.Rider:
Name = "Rider";
break;
default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
Name = CodeEditingManager.GetName(type);
}
/// <inheritdoc />
public string Name { get; set; }
/// <inheritdoc />
public string GenerateProjectCustomArgs
{
get
{
switch (Type)
{
case CodeEditorTypes.VSCodeInsiders:
case CodeEditorTypes.VSCode: return "-vscode -vs2022";
case CodeEditorTypes.Rider: return "-vs2022";
default: return null;
}
}
}
public string GenerateProjectCustomArgs => CodeEditingManager.GetGenerateProjectCustomArgs(Type);
/// <inheritdoc />
public void OpenSolution()

View File

@@ -139,6 +139,34 @@ CodeEditor* CodeEditingManager::GetCodeEditor(CodeEditorTypes editorType)
return nullptr;
}
String CodeEditingManager::GetName(CodeEditorTypes editorType)
{
const auto editor = GetCodeEditor(editorType);
if (editor)
{
return editor->GetName();
}
else
{
LOG(Warning, "Missing code editor type {0}", (int32)editorType);
return String::Empty;
}
}
String CodeEditingManager::GetGenerateProjectCustomArgs(CodeEditorTypes editorType)
{
const auto editor = GetCodeEditor(editorType);
if (editor)
{
return editor->GetGenerateProjectCustomArgs();
}
else
{
LOG(Warning, "Missing code editor type {0}", (int32)editorType);
return String::Empty;
}
}
void CodeEditingManager::OpenFile(CodeEditorTypes editorType, const String& path, int32 line)
{
const auto editor = GetCodeEditor(editorType);

View File

@@ -109,9 +109,18 @@ public:
/// <summary>
/// Gets the name of the editor.
/// </summary>
/// <returns>The name</returns>
/// <returns>The name.</returns>
virtual String GetName() const = 0;
/// <summary>
/// Gets the custom arguments for the Flax.Build tool to add when generating project files for this code editor.
/// </summary>
/// <returns>The custom arguments to generate project files.</returns>
virtual String GetGenerateProjectCustomArgs() const
{
return String::Empty;
}
/// <summary>
/// Opens the file.
/// </summary>
@@ -169,6 +178,20 @@ public:
/// <returns>The editor object or null if not found.</returns>
static CodeEditor* GetCodeEditor(CodeEditorTypes editorType);
/// <summary>
/// Gets the name of the editor.
/// </summary>
/// <param name="editorType">The code editor type.</param>
/// <returns>The name.</returns>
API_FUNCTION() static String GetName(CodeEditorTypes editorType);
/// <summary>
/// Gets the custom arguments for the Flax.Build tool to add when generating project files for this code editor.
/// </summary>
/// <param name="editorType">The code editor type.</param>
/// <returns>The custom arguments to generate project files.</returns>
API_FUNCTION() static String GetGenerateProjectCustomArgs(CodeEditorTypes editorType);
/// <summary>
/// Opens the file. Handles async opening.
/// </summary>

View File

@@ -257,12 +257,17 @@ String RiderCodeEditor::GetName() const
return TEXT("Rider");
}
String RiderCodeEditor::GetGenerateProjectCustomArgs() const
{
return TEXT("-vs2022");
}
void RiderCodeEditor::OpenFile(const String& path, int32 line)
{
// Generate project files if solution is missing
if (!FileSystem::FileExists(_solutionPath))
{
ScriptsBuilder::GenerateProject(TEXT("-vs2022"));
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
}
// Open file
@@ -290,7 +295,7 @@ void RiderCodeEditor::OpenSolution()
// Generate project files if solution is missing
if (!FileSystem::FileExists(_solutionPath))
{
ScriptsBuilder::GenerateProject(TEXT("-vs2022"));
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
}
// Open solution
@@ -312,5 +317,5 @@ void RiderCodeEditor::OpenSolution()
void RiderCodeEditor::OnFileAdded(const String& path)
{
ScriptsBuilder::GenerateProject();
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
}

View File

@@ -35,6 +35,7 @@ public:
// [CodeEditor]
CodeEditorTypes GetType() const override;
String GetName() const override;
String GetGenerateProjectCustomArgs() const override;
void OpenFile(const String& path, int32 line) override;
void OpenSolution() override;
void OnFileAdded(const String& path) override;

View File

@@ -148,12 +148,17 @@ String VisualStudioEditor::GetName() const
return String(ToString(_version));
}
String VisualStudioEditor::GetGenerateProjectCustomArgs() const
{
return String::Format(TEXT("-{0}"), String(ToString(_version)).ToLower());
}
void VisualStudioEditor::OpenFile(const String& path, int32 line)
{
// Generate project files if solution is missing
if (!FileSystem::FileExists(_solutionPath))
{
ScriptsBuilder::GenerateProject();
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
}
// Open file
@@ -172,7 +177,7 @@ void VisualStudioEditor::OpenSolution()
// Generate project files if solution is missing
if (!FileSystem::FileExists(_solutionPath))
{
ScriptsBuilder::GenerateProject();
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
}
// Open solution
@@ -187,7 +192,7 @@ void VisualStudioEditor::OpenSolution()
void VisualStudioEditor::OnFileAdded(const String& path)
{
// TODO: finish dynamic files adding to the project - for now just regenerate it
ScriptsBuilder::GenerateProject();
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
return;
if (!FileSystem::FileExists(_solutionPath))
{

View File

@@ -56,6 +56,7 @@ public:
// [CodeEditor]
CodeEditorTypes GetType() const override;
String GetName() const override;
String GetGenerateProjectCustomArgs() const override;
void OpenFile(const String& path, int32 line) override;
void OpenSolution() override;
void OnFileAdded(const String& path) override;

View File

@@ -128,6 +128,11 @@ String VisualStudioCodeEditor::GetName() const
return _isInsiders ? TEXT("Visual Studio Code - Insiders") : TEXT("Visual Studio Code");
}
String VisualStudioCodeEditor::GetGenerateProjectCustomArgs() const
{
return TEXT("-vs2022 -vscode");
}
void VisualStudioCodeEditor::OpenFile(const String& path, int32 line)
{
// Generate VS solution files for intellisense

View File

@@ -37,6 +37,7 @@ public:
// [CodeEditor]
CodeEditorTypes GetType() const override;
String GetName() const override;
String GetGenerateProjectCustomArgs() const override;
void OpenFile(const String& path, int32 line) override;
void OpenSolution() override;
bool UseAsyncForOpen() const override;

View File

@@ -12,6 +12,7 @@ using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Tabs;
using FlaxEngine;
using FlaxEngine.Assertions;
using FlaxEngine.GUI;
using FlaxEngine.Json;
@@ -972,6 +973,9 @@ namespace FlaxEditor.Windows
_cloneProjectButton.Clicked -= OnCloneProjectButtonClicked;
PluginManager.PluginsChanged -= OnPluginsChanged;
Assert.IsTrue(!_entries.Any());
_entries.Clear();
base.OnDestroy();
}
}

View File

@@ -71,7 +71,7 @@ bool AccessVariant(Variant& instance, const StringAnsiView& member, Variant& val
if (set)
mField->SetValue(instanceObject, MUtils::VariantToManagedArgPtr(value, mField->GetType(), failed));
else
value = MUtils::UnboxVariant(mField->GetValueBoxed(instanceObject));
value = MUtils::UnboxVariant(mField->GetValueBoxed(instanceObject), true);
return !failed;
}
else if (const auto mProperty = mClass->GetProperty(member.Get()))
@@ -79,7 +79,7 @@ bool AccessVariant(Variant& instance, const StringAnsiView& member, Variant& val
if (set)
mProperty->SetValue(instanceObject, MUtils::VariantToManagedArgPtr(value, mProperty->GetType(), failed), nullptr);
else
value = MUtils::UnboxVariant(mProperty->GetValue(instanceObject, nullptr));
value = MUtils::UnboxVariant(mProperty->GetValue(instanceObject, nullptr), true);
return !failed;
}
}

View File

@@ -134,7 +134,7 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value&
}
// Extract result
value = MUtils::UnboxVariant(result);
value = MUtils::UnboxVariant(result, true);
context.ValueCache.Add(boxBase, value);
#endif
}

View File

@@ -962,7 +962,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
else if (!MCore::Type::IsPointer(valueType) && !MCore::Type::IsReference(valueType))
{
if (boxed)
Platform::MemoryCopy(value, MCore::Object::Unbox(boxed), valueSize);
MCore::Object::Unbox(boxed, value);
else
Platform::MemoryClear(value, valueSize);
}

View File

@@ -4,8 +4,9 @@
#if defined(__clang__)
#define DLLEXPORT __attribute__ ((__visibility__ ("default")))
#define DLLEXPORT __attribute__((__visibility__("default")))
#define DLLIMPORT
#define USED __attribute__((used))
#define THREADLOCAL __thread
#define STDCALL __attribute__((stdcall))
#define CDECL __attribute__((cdecl))
@@ -19,7 +20,7 @@
#define PACK_BEGIN()
#define PACK_END() __attribute__((__packed__))
#define ALIGN_BEGIN(_align)
#define ALIGN_END(_align) __attribute__( (aligned(_align) ) )
#define ALIGN_END(_align) __attribute__((aligned(_align)))
#define OFFSET_OF(X, Y) __builtin_offsetof(X, Y)
#define PRAGMA_DISABLE_DEPRECATION_WARNINGS \
_Pragma("clang diagnostic push") \
@@ -37,8 +38,9 @@
#elif defined(__GNUC__)
#define DLLEXPORT __attribute__ ((__visibility__ ("default")))
#define DLLEXPORT __attribute__((__visibility__("default")))
#define DLLIMPORT
#define USED __attribute__((used))
#define THREADLOCAL __thread
#define STDCALL __attribute__((stdcall))
#define CDECL __attribute__((cdecl))
@@ -52,7 +54,7 @@
#define PACK_BEGIN()
#define PACK_END() __attribute__((__packed__))
#define ALIGN_BEGIN(_align)
#define ALIGN_END(_align) __attribute__( (aligned(_align) ) )
#define ALIGN_END(_align) __attribute__((aligned(_align)))
#define OFFSET_OF(X, Y) __builtin_offsetof(X, Y)
#define PRAGMA_DISABLE_DEPRECATION_WARNINGS
#define PRAGMA_ENABLE_DEPRECATION_WARNINGS
@@ -67,6 +69,7 @@
#define DLLEXPORT __declspec(dllexport)
#define DLLIMPORT __declspec(dllimport)
#define USED
#define THREADLOCAL __declspec(thread)
#define STDCALL __stdcall
#define CDECL __cdecl

View File

@@ -4301,8 +4301,12 @@ void Variant::CopyStructure(void* src)
if (MANAGED_GC_HANDLE && mclass->IsValueType())
{
MObject* instance = MCore::GCHandle::GetTarget(MANAGED_GC_HANDLE);
void* data = MCore::Object::Unbox(instance);
Platform::MemoryCopy(data, src, mclass->GetInstanceSize());
PLATFORM_DEBUG_BREAK; // FIXME
MCore::GCHandle::Free(MANAGED_GC_HANDLE);
instance = MCore::Object::Box(src, mclass);
MANAGED_GC_HANDLE = *(MGCHandle*)&instance;
//void* data = MCore::Object::Unbox(instance);
//Platform::MemoryCopy(data, src, mclass->GetInstanceSize());
}
}
#endif

View File

@@ -3,6 +3,7 @@
#include "CommandLine.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Utilities.h"
#include "Engine/Core/Types/StringView.h"
#include <iostream>
CommandLine::OptionsData CommandLine::Options;
@@ -170,3 +171,63 @@ bool CommandLine::Parse(const Char* cmdLine)
return false;
}
bool CommandLine::ParseArguments(const StringView& cmdLine, Array<StringAnsi>& arguments)
{
int32 start = 0;
int32 quotesStart = -1;
int32 length = cmdLine.Length();
for (int32 i = 0; i < length; i++)
{
if (cmdLine[i] == ' ' && quotesStart == -1)
{
int32 count = i - start;
if (count > 0)
arguments.Add(StringAnsi(cmdLine.Substring(start, count)));
start = i + 1;
}
else if (cmdLine[i] == '\"')
{
if (quotesStart >= 0)
{
if (i + 1 < length && cmdLine[i + 1] != ' ')
{
// End quotes are in the middle of the current word,
// continue until the end of the current word.
}
else
{
int32 offset = 1;
if (quotesStart == start && cmdLine[start] == '\"')
{
// Word starts and ends with quotes, only include the quoted content.
quotesStart++;
offset--;
}
else if (quotesStart != start)
{
// Start quotes in the middle of the word, include the whole word.
quotesStart = start;
}
int32 count = i - quotesStart + offset;
if (count > 0)
arguments.Add(StringAnsi(cmdLine.Substring(quotesStart, count)));
start = i + 1;
}
quotesStart = -1;
}
else
{
quotesStart = i;
}
}
}
const int32 count = length - start;
if (count > 0)
arguments.Add(StringAnsi(cmdLine.Substring(start, count)));
if (quotesStart >= 0)
return true; // Missing last closing quote
return false;
}

View File

@@ -4,6 +4,7 @@
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/Nullable.h"
#include "Engine/Core/Collections/Array.h"
/// <summary>
/// Command line options helper.
@@ -219,4 +220,12 @@ public:
/// <param name="cmdLine">The command line.</param>
/// <returns>True if failed, otherwise false.</returns>
static bool Parse(const Char* cmdLine);
/// <summary>
/// Parses the command line arguments string into string list of arguments.
/// </summary>
/// <param name="cmdLine">The command line.</param>
/// <param name="arguments">The parsed arguments</param>
/// <returns>True if failed, otherwise false.</returns>
static bool ParseArguments(const StringView& cmdLine, Array<StringAnsi>& arguments);
};

View File

@@ -74,7 +74,7 @@ namespace FlaxEngine.Interop
internal static IntPtr MarshalReturnValueString(ref string returnValue)
{
return returnValue != null ? ManagedString.ToNativeWeak(returnValue) : IntPtr.Zero;
return returnValue != null ? ManagedString.ToNative/*Weak*/(returnValue) : IntPtr.Zero;
}
internal static IntPtr MarshalReturnValueManagedHandle(ref ManagedHandle returnValue)
@@ -157,7 +157,7 @@ namespace FlaxEngine.Interop
if (returnObject == null)
return IntPtr.Zero;
if (returnType == typeof(string))
return ManagedString.ToNativeWeak(Unsafe.As<string>(returnObject));
return ManagedString.ToNative/*Weak*/(Unsafe.As<string>(returnObject));
if (returnType == typeof(ManagedHandle))
return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnObject);
if (returnType == typeof(bool))
@@ -168,7 +168,7 @@ namespace FlaxEngine.Interop
return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As<Array>(returnObject)), GCHandleType.Weak);
if (returnType.IsArray)
return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As<Array>(returnObject)), GCHandleType.Weak);
return ManagedHandle.ToIntPtr(returnObject, GCHandleType.Weak);
return ManagedHandle.ToIntPtr(returnObject/*, GCHandleType.Weak*/);
}
internal static IntPtr MarshalReturnValueThunk<TRet>(ref TRet returnValue)
@@ -181,7 +181,7 @@ namespace FlaxEngine.Interop
if (returnObject == null)
return IntPtr.Zero;
if (returnType == typeof(string))
return ManagedString.ToNativeWeak(Unsafe.As<string>(returnObject));
return ManagedString.ToNative/*Weak*/(Unsafe.As<string>(returnObject));
if (returnType == typeof(IntPtr))
return (IntPtr)(object)returnObject;
if (returnType == typeof(ManagedHandle))
@@ -210,7 +210,7 @@ namespace FlaxEngine.Interop
return (IntPtr)new UIntPtr((ulong)(System.UInt32)(object)returnObject);
if (returnType == typeof(System.UInt64))
return (IntPtr)new UIntPtr((ulong)(System.UInt64)(object)returnObject);
return ManagedHandle.ToIntPtr(returnObject, GCHandleType.Weak);
return ManagedHandle.ToIntPtr(returnObject/*, GCHandleType.Weak*/);
}
#if !USE_AOT

View File

@@ -1,5 +1,10 @@
// Copyright (c) Wojciech Figat. All rights reserved.
#define USE_CONCURRENT_DICT
#define USE_GCHANDLE
//#define TRACK_HANDLES
#if USE_NETCORE
using System;
using System.Collections.Generic;
@@ -8,6 +13,7 @@ using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Threading;
using FlaxEngine.Assertions;
using System.Collections.Concurrent;
#pragma warning disable 1591
@@ -346,6 +352,14 @@ namespace FlaxEngine.Interop
return;
handle.Free();
}
[System.Diagnostics.DebuggerStepThrough]
public static void Free(ManagedHandle handle)
{
if (handle == EmptyStringHandle)
return;
handle.Free();
}
}
/// <summary>
@@ -356,18 +370,106 @@ namespace FlaxEngine.Interop
#endif
public struct ManagedHandle
{
#if USE_GCHANDLE
private GCHandle handle;
#if TRACK_HANDLES
private static HashSet<IntPtr> _weakHandles = new HashSet<nint>();
private static ConcurrentDictionary<IntPtr, string> _handles = new();
private static ConcurrentDictionary<IntPtr, string> _handles2 = new();
private static long _allocations = 0;
private static long _deallocations = 0;
public static long Allocations => Interlocked.Read(ref _allocations);
public static long Deallocations => Interlocked.Read(ref _deallocations);
#else
public static long Allocations => 0;
public static long Deallocations => 0;
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ManagedHandle(IntPtr handle) => this.handle = GCHandle.FromIntPtr(handle);
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ManagedHandle(object value, GCHandleType type) //=> handle = GCHandle.Alloc(value, type);
{
#if TRACK_HANDLES
/*if (type == GCHandleType.Weak || type == GCHandleType.WeakTrackResurrection)
{
type = GCHandleType.Normal;
}*/
#endif
handle = GCHandle.Alloc(value, type);
#if TRACK_HANDLES
/*if (type == GCHandleType.Weak || type == GCHandleType.WeakTrackResurrection)
_weakHandles.Add((IntPtr)handle);
else if (_handles.Count < 14000)
_handles.TryAdd((IntPtr)handle, value?.GetType().FullName ?? "");
else
{
if (_handles2.Count > 12)
type = type;
if (value?.GetType() == typeof(string))
type = type;
_handles2.TryAdd((IntPtr)handle, value?.GetType().FullName ?? "");
}*/
Interlocked.Increment(ref _allocations);
#endif
}
#else
private IntPtr handle;
private ManagedHandle(IntPtr handle) => this.handle = handle;
private ManagedHandle(object value, GCHandleType type) => handle = ManagedHandlePool.AllocateHandle(value, type);
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ManagedHandle Alloc(object value) => new ManagedHandle(value, GCHandleType.Normal);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ManagedHandle Alloc(object value, GCHandleType type) => new ManagedHandle(value, type);
#if USE_GCHANDLE
public void Free()// => handle.Free();
{
if ((IntPtr)handle == IntPtr.Zero)
return;
#if TRACK_HANDLES
/*if (_weakHandles.Remove((IntPtr)handle))
{
if (!handle.IsAllocated)
handle = handle;
handle = handle;
}
else if (_handles.Remove((IntPtr)handle, out _))
{
handle = handle;
}
else if (_handles2.Remove((IntPtr)handle, out _))
{
handle = handle;
}*/
Interlocked.Increment(ref _deallocations);
#endif
handle.Free();
}
public object Target
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => handle.Target;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => handle.Target = value;
}
public bool IsAllocated
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => handle.IsAllocated;
}
#else
public void Free()
{
if (handle == IntPtr.Zero)
@@ -383,6 +485,7 @@ namespace FlaxEngine.Interop
}
public bool IsAllocated => handle != IntPtr.Zero;
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator ManagedHandle(IntPtr value) => FromIntPtr(value);
@@ -393,6 +496,16 @@ namespace FlaxEngine.Interop
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator IntPtr(ManagedHandle value) => ToIntPtr(value);
#if USE_GCHANDLE
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr ToIntPtr(object value) => (IntPtr)Alloc(value, GCHandleType.Normal).handle;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr ToIntPtr(object value, GCHandleType type) => (IntPtr)Alloc(value, type).handle;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr ToIntPtr(ManagedHandle value) => (IntPtr)value.handle;
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr ToIntPtr(object value) => ManagedHandlePool.AllocateHandle(value, GCHandleType.Normal);
@@ -401,17 +514,69 @@ namespace FlaxEngine.Interop
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr ToIntPtr(ManagedHandle value) => value.handle;
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => handle.GetHashCode();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj) => obj is ManagedHandle other && handle == other.handle;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ManagedHandle other) => handle == other.handle;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(ManagedHandle a, ManagedHandle b) => a.handle == b.handle;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(ManagedHandle a, ManagedHandle b) => a.handle != b.handle;
#if USE_GCHANDLE
/// <summary>
/// A pool of shared managed handles used with short-lived temporary GCHandle allocations during interop.
/// </summary>
internal static class ManagedHandlePool
{
[ThreadStatic]
private static List<(bool InUse, ManagedHandle Handle)> _pool;
internal static void TryCollectWeakHandles(bool force = false)
{
}
internal static ManagedHandle Rent()
{
foreach (ref var tuple in CollectionsMarshal.AsSpan(_pool))
{
if (tuple.InUse)
continue;
tuple.InUse = true;
return tuple.Handle;
}
var newTuple = (InUse: true, Array: new ManagedHandle());
_pool.Add(newTuple);
return newTuple.Array;
}
internal static void Return(ManagedHandle handle)
{
foreach (ref var tuple in CollectionsMarshal.AsSpan(_pool))
{
if (tuple.Handle != handle)
continue;
tuple.InUse = false;
handle.Target = null;
return;
}
throw new Exception("Tried to return non-pooled ManagedHandle");
}
}
#else
internal static class ManagedHandlePool
{
private const int WeakPoolCollectionSizeThreshold = 10000000;
@@ -425,17 +590,31 @@ namespace FlaxEngine.Interop
// Dictionaries for storing the valid handles.
// Note: Using locks seems to be generally the fastest when adding or fetching from the dictionary.
// Concurrent dictionaries could also be considered, but they perform much slower when adding to the dictionary.
#if USE_CONCURRENT_DICT
private static ConcurrentDictionary<IntPtr, object> persistentPool = new();
private static ConcurrentDictionary<IntPtr, GCHandle> pinnedPool = new();
#else
private static Dictionary<IntPtr, object> persistentPool = new();
private static Dictionary<IntPtr, GCHandle> pinnedPool = new();
private static Lock persistentPoolLock = new();
private static Lock pinnedPoolLock = new();
#endif
// TODO: Performance of pinned handles are poor at the moment due to GCHandle wrapping.
// TODO: .NET8: Experiment with pinned arrays for faster pinning: https://github.com/dotnet/runtime/pull/89293
// Manage double-buffered pool for weak handles in order to avoid collecting in-flight handles.
// Periodically when the pools are being accessed and conditions are met, the other pool is cleared and swapped.
private static Dictionary<IntPtr, object> weakPool = new();
private static Dictionary<IntPtr, object> weakPoolOther = new();
private static object weakPoolLock = new object();
private static int weakPoolSize = 1;
private static int weakPoolOtherSize = 1;
#if USE_CONCURRENT_DICT
private static ConcurrentDictionary<IntPtr, object> weakPool = new(-1, weakPoolSize);
private static ConcurrentDictionary<IntPtr, object> weakPoolOther = new(-1, weakPoolOtherSize);
#else
private static Dictionary<IntPtr, object> weakPool = new(weakPoolSize);
private static Dictionary<IntPtr, object> weakPoolOther = new(weakPoolOtherSize);
#endif
private static Lock weakPoolLock = new();
private static ulong nextWeakPoolCollection;
private static int nextWeakPoolGCCollection;
private static long lastWeakPoolCollectionTime;
@@ -466,6 +645,19 @@ namespace FlaxEngine.Interop
// Swap the pools and release the oldest pool for GC
(weakPool, weakPoolOther) = (weakPoolOther, weakPool);
(weakPoolSize, weakPoolOtherSize) = (weakPoolOtherSize, weakPoolSize);
#if USE_CONCURRENT_DICT
if (weakPool.Count > weakPoolSize)
{
var newMax = Math.Max(Math.Max(weakPoolSize, weakPoolOtherSize), weakPool.Count);
//FlaxEditor.Editor.Log($"growth from {weakPoolSize} to {weakPool.Count}, max {newMax}");
weakPoolSize = newMax;
weakPool = new(-1, weakPoolSize);
}
//else if (weakPool.Count > 0)
// weakPool.Clear();
//else
#endif
weakPool.Clear();
}
@@ -485,14 +677,43 @@ namespace FlaxEngine.Interop
internal static IntPtr AllocateHandle(object value, GCHandleType type)
{
IntPtr handle = NewHandle(type);
#if USE_CONCURRENT_DICT
switch (type)
{
case GCHandleType.Normal:
lock (persistentPool)
persistentPool.Add(handle, value);
//lock (persistentPoolLock)
persistentPool.TryAdd(handle, value);
//if (value?.GetType().Name.Contains("RenderContext") ?? false)
// value = value;
break;
case GCHandleType.Pinned:
lock (pinnedPool)
//lock (pinnedPoolLock)
pinnedPool.TryAdd(handle, GCHandle.Alloc(value, GCHandleType.Pinned));
break;
case GCHandleType.Weak:
case GCHandleType.WeakTrackResurrection:
lock (weakPoolLock)
{
TryCollectWeakHandles();
//weakPool.TryAdd(handle, value);
weakPool[handle] = value;
//if (value?.GetType().Name.Contains("RenderContext") ?? false)
// value = value;
}
break;
}
#else
switch (type)
{
case GCHandleType.Normal:
lock (persistentPoolLock)
persistentPool.Add(handle, value);
//if (value?.GetType().Name.Contains("RenderContext") ?? false)
// value = value;
break;
case GCHandleType.Pinned:
lock (pinnedPoolLock)
pinnedPool.Add(handle, GCHandle.Alloc(value, GCHandleType.Pinned));
break;
case GCHandleType.Weak:
@@ -501,25 +722,36 @@ namespace FlaxEngine.Interop
{
TryCollectWeakHandles();
weakPool.Add(handle, value);
//weakPool[handle] = value;
//if (value?.GetType().Name.Contains("RenderContext") ?? false)
// value = value;
}
break;
}
#endif
return handle;
}
internal static object GetObject(IntPtr handle)
{
switch (GetHandleType(handle))
GCHandleType type = GetHandleType(handle);
switch (type)
{
case GCHandleType.Normal:
lock (persistentPool)
#if !USE_CONCURRENT_DICT
lock (persistentPoolLock)
#endif
{
if (persistentPool.TryGetValue(handle, out object value))
return value;
}
break;
case GCHandleType.Pinned:
lock (pinnedPool)
#if !USE_CONCURRENT_DICT
lock (pinnedPoolLock)
#endif
{
if (pinnedPool.TryGetValue(handle, out GCHandle gcHandle))
return gcHandle.Target;
@@ -537,15 +769,98 @@ namespace FlaxEngine.Interop
}
break;
}
throw new NativeInteropException("Invalid ManagedHandle");
throw new NativeInteropException($"Invalid ManagedHandle of type '{type}'");
}
internal static void SetObject(IntPtr handle, object value)
{
switch (GetHandleType(handle))
GCHandleType type = GetHandleType(handle);
#if USE_CONCURRENT_DICT
switch (type)
{
case GCHandleType.Normal:
lock (persistentPool)
//lock (persistentPoolLock)
{
//ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(persistentPool, handle);
if (persistentPool.TryGetValue(handle, out var oldValue))
{
if (persistentPool.TryUpdate(handle, value, oldValue))
//if (!Unsafe.IsNullRef(ref obj))
{
//obj = value;
return;
}
}
}
break;
case GCHandleType.Pinned:
//lock (pinnedPoolLock)
{
/*ref GCHandle gcHandle = ref CollectionsMarshal.GetValueRefOrNullRef(pinnedPool, handle);
if (!Unsafe.IsNullRef(ref gcHandle))
{
gcHandle.Target = value;
return;
}*/
if (pinnedPool.TryGetValue(handle, out var gcHandle))
{
gcHandle.Target = value;
//if (pinnedPool.TryUpdate(handle, value, oldValue))
//if (!Unsafe.IsNullRef(ref obj))
//{
//obj = value;
return;
}
//}
}
break;
case GCHandleType.Weak:
case GCHandleType.WeakTrackResurrection:
lock (weakPoolLock)
{
TryCollectWeakHandles();
if (weakPool.TryGetValue(handle, out var oldValue))
{
if (weakPool.TryUpdate(handle, value, oldValue))
//if (!Unsafe.IsNullRef(ref obj))
{
//obj = value;
return;
}
}
if (weakPoolOther.TryGetValue(handle, out oldValue))
{
if (weakPoolOther.TryUpdate(handle, value, oldValue))
//if (!Unsafe.IsNullRef(ref obj))
{
//obj = value;
return;
}
}
/*{
ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPool, handle);
if (!Unsafe.IsNullRef(ref obj))
{
obj = value;
return;
}
}
{
ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPoolOther, handle);
if (!Unsafe.IsNullRef(ref obj))
{
obj = value;
return;
}
}*/
}
break;
}
#else
switch (type)
{
case GCHandleType.Normal:
lock (persistentPoolLock)
{
ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(persistentPool, handle);
if (!Unsafe.IsNullRef(ref obj))
@@ -556,7 +871,7 @@ namespace FlaxEngine.Interop
}
break;
case GCHandleType.Pinned:
lock (pinnedPool)
lock (pinnedPoolLock)
{
ref GCHandle gcHandle = ref CollectionsMarshal.GetValueRefOrNullRef(pinnedPool, handle);
if (!Unsafe.IsNullRef(ref gcHandle))
@@ -590,22 +905,32 @@ namespace FlaxEngine.Interop
}
break;
}
throw new NativeInteropException("Invalid ManagedHandle");
#endif
throw new NativeInteropException($"Invalid ManagedHandle of type '{type}'");
}
internal static void FreeHandle(IntPtr handle)
{
switch (GetHandleType(handle))
GCHandleType type = GetHandleType(handle);
switch (type)
{
case GCHandleType.Normal:
lock (persistentPool)
#if !USE_CONCURRENT_DICT
lock (persistentPoolLock)
#endif
{
if (persistentPool.Remove(handle))
if (persistentPool.Remove(handle, out _))
{
//if (value?.GetType().Name.Contains("RenderContext") ?? false)
// value = value;
return;
}
}
break;
case GCHandleType.Pinned:
lock (pinnedPool)
#if !USE_CONCURRENT_DICT
lock (pinnedPoolLock)
#endif
{
if (pinnedPool.Remove(handle, out GCHandle gcHandle))
{
@@ -617,12 +942,19 @@ namespace FlaxEngine.Interop
case GCHandleType.Weak:
case GCHandleType.WeakTrackResurrection:
lock (weakPoolLock)
{
TryCollectWeakHandles();
return;
if (weakPool.Remove(handle, out _))
return;
else if (weakPoolOther.Remove(handle, out _))
return;
return;
}
}
throw new NativeInteropException("Invalid ManagedHandle");
throw new NativeInteropException($"Invalid ManagedHandle of type '{type}'");
}
}
#endif
}
}

View File

@@ -59,8 +59,9 @@ namespace FlaxEngine.Interop
#endif
public struct ManagedToNativeState
{
ManagedArray managedArray;
IntPtr handle;
ManagedArray _pooledManagedArray;
ManagedArray _managedArray;
ManagedHandle handle;
public void FromManaged(object managed)
{
@@ -73,29 +74,30 @@ namespace FlaxEngine.Interop
if (NativeInterop.ArrayFactory.GetMarshalledType(elementType) == elementType)
{
// Use pooled managed array wrapper to be passed around as handle to it
(ManagedHandle tmp, managedArray) = ManagedArray.WrapPooledArray(arr);
handle = ManagedHandle.ToIntPtr(tmp);
(handle, _pooledManagedArray) = ManagedArray.WrapPooledArray(arr);
}
else
{
// Convert array contents to be properly accessed by the native code (as GCHandles array)
managedArray = NativeInterop.ManagedArrayToGCHandleWrappedArray(arr);
handle = ManagedHandle.ToIntPtr(ManagedHandle.Alloc(managedArray));
managedArray = null; // It's not pooled
_managedArray = NativeInterop.ManagedArrayToGCHandleWrappedArray(arr);
handle = ManagedHandle.Alloc(_pooledManagedArray);
}
}
else
handle = ManagedHandle.ToIntPtr(managed, GCHandleType.Weak);
handle = ManagedHandle.Alloc(managed/*, GCHandleType.Weak*/);
}
public IntPtr ToUnmanaged()
{
return handle;
}
public IntPtr ToUnmanaged() => ManagedHandle.ToIntPtr(handle);
public void Free()
{
managedArray?.FreePooled();
if (_pooledManagedArray != null)
_pooledManagedArray.FreePooled();
else
{
handle.Free();
_managedArray?.Free();
}
}
}
@@ -109,6 +111,7 @@ namespace FlaxEngine.Interop
public static void Free(IntPtr unmanaged)
{
ManagedHandle.FromIntPtr(unmanaged).Free();
}
}
@@ -409,7 +412,7 @@ namespace FlaxEngine.Interop
public static void Free(IntPtr unmanaged)
{
//DictionaryMarshaller<T, U>.Free(unmanaged); // No need to free weak handles
DictionaryMarshaller<T, U>.Free(unmanaged); // No need to free weak handles
}
}
@@ -678,7 +681,7 @@ namespace FlaxEngine.Interop
public static class NativeToManaged
{
public static string ConvertToManaged(IntPtr unmanaged) => ManagedString.ToManaged(unmanaged);
public static unsafe IntPtr ConvertToUnmanaged(string managed) => managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed, GCHandleType.Weak);
public static unsafe IntPtr ConvertToUnmanaged(string managed) => managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed/*, GCHandleType.Weak*/);
public static void Free(IntPtr unmanaged) => ManagedString.Free(unmanaged);
}
@@ -688,11 +691,11 @@ namespace FlaxEngine.Interop
public static class ManagedToNative
{
public static string ConvertToManaged(IntPtr unmanaged) => ManagedString.ToManaged(unmanaged);
public static unsafe IntPtr ConvertToUnmanaged(string managed) => managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed, GCHandleType.Weak);
public static unsafe IntPtr ConvertToUnmanaged(string managed) => managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed/*, GCHandleType.Weak*/);
public static void Free(IntPtr unmanaged)
{
//ManagedString.Free(unmanaged); // No need to free weak handles
ManagedString.Free(unmanaged); // No need to free weak handles
}
}

View File

@@ -9,6 +9,8 @@ using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Threading;
using FlaxEngine.Assertions;
using FlaxEngine.Utilities;
#pragma warning disable 1591
@@ -543,6 +545,18 @@ namespace FlaxEngine.Interop
}
}
[UnmanagedCallersOnly]
internal static void FreeArray(ManagedHandle handle)
{
if (!handle.IsAllocated)
return;
ManagedArray managedArray = Unsafe.As<ManagedArray>(handle.Target);
if (managedArray.ElementType.IsValueType)
managedArray.Free();
else
managedArray.FreePooled();
}
[UnmanagedCallersOnly]
internal static ManagedHandle GetArrayTypeFromElementType(ManagedHandle elementTypeHandle)
{
@@ -602,13 +616,19 @@ namespace FlaxEngine.Interop
[UnmanagedCallersOnly]
internal static IntPtr NewStringUTF16(char* text, int length)
{
return ManagedString.ToNativeWeak(new string(new ReadOnlySpan<char>(text, length)));
return ManagedString.ToNative/*Weak*/(new string(new ReadOnlySpan<char>(text, length)));
}
[UnmanagedCallersOnly]
internal static IntPtr NewStringUTF8(sbyte* text, int length)
{
return ManagedString.ToNativeWeak(new string(text, 0, length, System.Text.Encoding.UTF8));
return ManagedString.ToNative/*Weak*/(new string(text, 0, length, System.Text.Encoding.UTF8));
}
[UnmanagedCallersOnly]
internal static void FreeString(ManagedHandle handle)
{
ManagedString.Free(handle);
}
[UnmanagedCallersOnly]
@@ -654,27 +674,30 @@ namespace FlaxEngine.Interop
}
/// <summary>
/// Creates a managed copy of the value, and stores it in a boxed reference.
/// Creates a managed converted copy of the unmanaged value, and boxes it in a managed handle.
/// </summary>
/// <param name="typeHandle">A handle to class type.</param>
/// <param name="valuePtr">A pointer to unmanaged value.</param>
[UnmanagedCallersOnly]
internal static ManagedHandle BoxValue(ManagedHandle typeHandle, IntPtr valuePtr)
{
Type type = Unsafe.As<TypeHolder>(typeHandle.Target);
object value = MarshalToManaged(valuePtr, type);
return ManagedHandle.Alloc(value, GCHandleType.Weak);
return ManagedHandle.Alloc(value/*, GCHandleType.Weak*/);
}
/// <summary>
/// Returns the address of the boxed value type.
/// Writes the unboxed converted value to given address.
/// </summary>
/// <param name="objectHandle">A handle to boxed object.</param>
/// <param name="destinationPtr">The destination address for unboxed value.</param>
[UnmanagedCallersOnly]
internal static IntPtr UnboxValue(ManagedHandle handle)
internal static void UnboxValue(ManagedHandle objectHandle, IntPtr destinationPtr)
{
object value = handle.Target;
object value = objectHandle.Target;
Assert.IsNotNull(value, "Boxed value type can't be null.");
Type type = value.GetType();
if (!type.IsValueType)
return ManagedHandle.ToIntPtr(handle);
return ValueTypeUnboxer.GetPointer(value, type);
MarshalToNative(objectHandle.Target, destinationPtr, type);
}
[UnmanagedCallersOnly]
@@ -714,7 +737,7 @@ namespace FlaxEngine.Interop
internal static IntPtr InvokeMethod(ManagedHandle instanceHandle, ManagedHandle methodHandle, IntPtr paramPtr, IntPtr exceptionPtr)
{
MethodHolder methodHolder = Unsafe.As<MethodHolder>(methodHandle.Target);
#if !USE_AOT
#if false//#if !USE_AOT
if (methodHolder.TryGetDelegate(out var methodDelegate, out var methodDelegateContext))
{
// Fast path, invoke the method with minimal allocations
@@ -737,7 +760,7 @@ namespace FlaxEngine.Interop
// Slow path, method parameters needs to be stored in heap
object returnObject;
int numParams = methodHolder.parameterTypes.Length;
object[] methodParameters = new object[numParams];
object[] methodParameters = new object[numParams];//ObjectArrayPool.Rent(numParams);//new object[numParams];
for (int i = 0; i < numParams; i++)
{
@@ -748,6 +771,17 @@ namespace FlaxEngine.Interop
try
{
returnObject = methodHolder.method.Invoke(instanceHandle.IsAllocated ? instanceHandle.Target : null, methodParameters);
// Marshal reference parameters back to original unmanaged references
for (int i = 0; i < numParams; i++)
{
Type parameterType = methodHolder.parameterTypes[i];
if (parameterType.IsByRef)
{
IntPtr nativePtr = Unsafe.Read<IntPtr>((IntPtr.Add(paramPtr, sizeof(IntPtr) * i)).ToPointer());
MarshalToNative(methodParameters[i], nativePtr, parameterType.GetElementType());
}
}
}
catch (Exception exception)
{
@@ -762,19 +796,11 @@ namespace FlaxEngine.Interop
throw realException;
return IntPtr.Zero;
}
// Marshal reference parameters back to original unmanaged references
for (int i = 0; i < numParams; i++)
finally
{
Type parameterType = methodHolder.parameterTypes[i];
if (parameterType.IsByRef)
{
IntPtr nativePtr = Unsafe.Read<IntPtr>((IntPtr.Add(paramPtr, sizeof(IntPtr) * i)).ToPointer());
MarshalToNative(methodParameters[i], nativePtr, parameterType.GetElementType());
}
//ObjectArrayPool.Return(methodParameters);
}
// Return value
return Invoker.MarshalReturnValueGeneric(methodHolder.returnType, returnObject);
}
}
@@ -908,7 +934,7 @@ namespace FlaxEngine.Interop
if (File.Exists(pdbPath))
{
// Load including debug symbols
using FileStream pdbStream = new FileStream(Path.ChangeExtension(assemblyPath, "pdb"), FileMode.Open);
using FileStream pdbStream = new FileStream(Path.ChangeExtension(assemblyPath, "pdb"), FileMode.Open, FileAccess.Read);
assembly = scriptingAssemblyLoadContext.LoadFromStream(stream, pdbStream);
}
else
@@ -998,6 +1024,8 @@ namespace FlaxEngine.Interop
Debug.Logger.LogHandler.LogWrite(LogType.Warning, "Scripting AssemblyLoadContext was not unloaded.");
weakRef.Free();
Assert.IsFalse(AssemblyLoadContext.All.Any(x => x.Name == "Flax"));
static bool IsHandleAlive(GCHandle weakRef)
{
// Checking the target in scope somehow holds a reference to it...?
@@ -1026,6 +1054,7 @@ namespace FlaxEngine.Interop
// Clear all caches which might hold references to assemblies in collectible ALC
cachedDelegatesCollectible.Clear();
cachedDelegatesCollectible = new();
foreach (var pair in managedTypesCollectible)
pair.Value.handle.Free();
managedTypesCollectible.Clear();
@@ -1115,6 +1144,9 @@ namespace FlaxEngine.Interop
}
}
GC.Collect();
GC.WaitForPendingFinalizers();
// Unload the ALC
scriptingAssemblyLoadContext.Unload();
scriptingAssemblyLoadContext.Resolving -= OnScriptingAssemblyLoadContextResolving;

View File

@@ -18,6 +18,7 @@ using System.Collections.Concurrent;
using System.IO;
using System.Text;
using System.Threading;
using System.Buffers;
namespace FlaxEngine.Interop
{
@@ -1244,7 +1245,7 @@ namespace FlaxEngine.Interop
internal static void ToNativeString(ref string managedValue, IntPtr nativePtr)
{
Unsafe.Write<IntPtr>(nativePtr.ToPointer(), ManagedString.ToNativeWeak(managedValue));
Unsafe.Write<IntPtr>(nativePtr.ToPointer(), ManagedString.ToNative/*Weak*/(managedValue));
}
internal static void ToNativeType(ref Type managedValue, IntPtr nativePtr)
@@ -1291,14 +1292,14 @@ namespace FlaxEngine.Interop
}
else
managedArray = ManagedArrayToGCHandleWrappedArray(arr);
managedPtr = ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak);
managedPtr = ManagedHandle.ToIntPtr(managedArray/*, GCHandleType.Weak*/);
}
Unsafe.Write<IntPtr>(nativePtr.ToPointer(), managedPtr);
}
internal static void ToNative(ref T managedValue, IntPtr nativePtr)
{
Unsafe.Write<IntPtr>(nativePtr.ToPointer(), managedValue != null ? ManagedHandle.ToIntPtr(managedValue, GCHandleType.Weak) : IntPtr.Zero);
Unsafe.Write<IntPtr>(nativePtr.ToPointer(), managedValue != null ? ManagedHandle.ToIntPtr(managedValue/*, GCHandleType.Weak*/) : IntPtr.Zero);
}
}
@@ -1529,11 +1530,12 @@ namespace FlaxEngine.Interop
private static GCHandle[] pinnedBoxedValues = new GCHandle[256];
private static uint pinnedBoxedValuesPointer = 0;
private static (IntPtr ptr, int size)[] pinnedAllocations = new (IntPtr ptr, int size)[256];
private static Action[] pinnedNativeFreeDelegates = new Action[256];
private static uint pinnedAllocationsPointer = 0;
private delegate IntPtr UnboxerDelegate(object value, object converter);
private delegate IntPtr UnboxerDelegate(object value, object toNativeConverter, object nativeFree);
private static ConcurrentDictionary<Type, (UnboxerDelegate deleg, object toNativeDeleg)> unboxers = new(1, 3);
private static ConcurrentDictionary<Type, (UnboxerDelegate deleg, object/*Delegate*/ toNativeConverter, object nativeFree)> unboxers = new(1, 3);
private static MethodInfo unboxerMethod = typeof(ValueTypeUnboxer).GetMethod(nameof(ValueTypeUnboxer.UnboxPointer), BindingFlags.Static | BindingFlags.NonPublic);
private static MethodInfo unboxerToNativeMethod = typeof(ValueTypeUnboxer).GetMethod(nameof(ValueTypeUnboxer.UnboxPointerWithConverter), BindingFlags.Static | BindingFlags.NonPublic);
@@ -1546,8 +1548,12 @@ namespace FlaxEngine.Interop
var toNativeMethod = attr?.NativeType.GetMethod("ToNative", BindingFlags.Static | BindingFlags.NonPublic);
if (toNativeMethod != null)
{
tuple.deleg = unboxerToNativeMethod.MakeGenericMethod(type, toNativeMethod.ReturnType).CreateDelegate<UnboxerDelegate>();
tuple.toNativeDeleg = toNativeMethod.CreateDelegate(typeof(ToNativeDelegate<,>).MakeGenericType(type, toNativeMethod.ReturnType));
Type internalType = toNativeMethod.ReturnType;
tuple.deleg = unboxerToNativeMethod.MakeGenericMethod(type, internalType).CreateDelegate<UnboxerDelegate>();
tuple.toNativeConverter = toNativeMethod.CreateDelegate(typeof(ToNativeDelegate<,>).MakeGenericType(type, internalType));
MethodInfo freeNativeMethod = attr.NativeType.GetMethod("Free", BindingFlags.Static | BindingFlags.NonPublic);
tuple.nativeFree = freeNativeMethod?.CreateDelegate(typeof(Action<>).MakeGenericType(internalType));
}
else
{
@@ -1555,7 +1561,7 @@ namespace FlaxEngine.Interop
}
tuple = unboxers.GetOrAdd(type, tuple);
}
return tuple.deleg(value, tuple.toNativeDeleg);
return tuple.deleg(value, tuple.toNativeConverter, tuple.nativeFree);
}
private static void PinValue(object value)
@@ -1569,12 +1575,15 @@ namespace FlaxEngine.Interop
handle = GCHandle.Alloc(value, GCHandleType.Pinned);
}
private static IntPtr PinValue<T>(T value) where T : struct
private static IntPtr PinValue<T>(T value, out uint index) where T : struct
{
// Store the converted value in unmanaged memory so it will not be relocated by the garbage collector.
int size = TypeHelpers<T>.MarshalSize;
uint index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length;
index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length;
ref (IntPtr ptr, int size) alloc = ref pinnedAllocations[index];
Action freeNativeDelegate = pinnedNativeFreeDelegates[index];
if (freeNativeDelegate != null)
freeNativeDelegate();
if (alloc.size < size)
{
if (alloc.ptr != IntPtr.Zero)
@@ -1586,7 +1595,7 @@ namespace FlaxEngine.Interop
return alloc.ptr;
}
private static IntPtr UnboxPointer<T>(object value, object converter) where T : struct
private static IntPtr UnboxPointer<T>(object value, object toNativeConverter, object nativeFreeDelegate) where T : struct
{
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()) // Cannot pin structure with references
return IntPtr.Zero;
@@ -1594,11 +1603,16 @@ namespace FlaxEngine.Interop
return new IntPtr(Unsafe.AsPointer(ref Unsafe.Unbox<T>(value)));
}
private static IntPtr UnboxPointerWithConverter<T, TInternal>(object value, object converter) where T : struct
where TInternal : struct
private static IntPtr UnboxPointerWithConverter<T, TInternal>(
object value, object nativeConverterDelegate, object nativeFreeDelegate) where T : struct
where TInternal : struct
{
ToNativeDelegate<T, TInternal> toNative = Unsafe.As<ToNativeDelegate<T, TInternal>>(converter);
return PinValue<TInternal>(toNative(Unsafe.Unbox<T>(value)));
ToNativeDelegate<T, TInternal> toNative = Unsafe.As<ToNativeDelegate<T, TInternal>>(nativeConverterDelegate);
TInternal nativeValue = toNative(Unsafe.Unbox<T>(value));
IntPtr pinnedPtr = PinValue<TInternal>(nativeValue, out uint index);
Action<TInternal> freeNative = Unsafe.As<Action<TInternal>>(nativeFreeDelegate);
//pinnedNativeFreeDelegates[index] = () => freeNative(nativeValue);
return pinnedPtr;
}
}
@@ -1743,25 +1757,37 @@ namespace FlaxEngine.Interop
internal static void InitMethods()
{
return;
MakeNewCustomDelegateFunc =
typeof(Expression).Assembly.GetType("System.Linq.Expressions.Compiler.DelegateHelpers")
.GetMethod("MakeNewCustomDelegate", BindingFlags.NonPublic | BindingFlags.Static).CreateDelegate<Func<Type[], Type>>();
MakeNewCustomDelegateFunc(new[] { typeof(void) });
#if FLAX_EDITOR
// Load System.Linq.Expressions assembly to collectible ALC.
// The dynamic assembly where delegates are stored is cached in the DelegateHelpers class, so we should
// use the DelegateHelpers in collectible ALC to make sure the delegates are also stored in the same ALC.
Assembly assembly = scriptingAssemblyLoadContext.LoadFromAssemblyPath(typeof(Expression).Assembly.Location);
//using var _ = scriptingAssemblyLoadContext.EnterContextualReflection();
//Debug.Logger.LogHandler.LogWrite(LogType.Warning, "InitMethods.");
var asdfa = AssemblyLoadContext.All.ToArray();
var asma = typeof(Expression).Assembly;
var loc = asma.Location;
using FileStream stream = new FileStream(loc, FileMode.Open, FileAccess.Read);
Assembly assembly = scriptingAssemblyLoadContext.LoadFromAssemblyPath(loc);
MakeNewCustomDelegateFuncCollectible =
assembly.GetType("System.Linq.Expressions.Compiler.DelegateHelpers")
.GetMethod("MakeNewCustomDelegate", BindingFlags.NonPublic | BindingFlags.Static).CreateDelegate<Func<Type[], Type>>();
// Create dummy delegates to force the dynamic Snippets assembly to be loaded in correcet ALCs
MakeNewCustomDelegateFunc(new[] { typeof(void) });
Debug.Logger.LogHandler.LogWrite(LogType.Warning, "InitMethods ok .");
{
// Ensure the new delegate is placed in the collectible ALC
// Ensure any future delegates is placed in the collectible ALC
using var ctx = scriptingAssemblyLoadContext.EnterContextualReflection();
MakeNewCustomDelegateFuncCollectible(new[] { typeof(void) });
var ret = MakeNewCustomDelegateFuncCollectible(new[] { typeof(void) });
Assert.IsTrue(ret.IsCollectible);
}
#endif
}
@@ -1785,6 +1811,102 @@ namespace FlaxEngine.Interop
#endif
}
internal static class ObjectArrayPool
{
[ThreadStatic]
private static List<(bool InUse, object[] Array)> _pool;
[ThreadStatic]
private static List<(bool InUse, object[] Array)> _pool2;
/// <summary>
/// Rents an array from the pool.
/// </summary>
/// <param name="size">Exact size of the array.</param>
internal static object[] Rent(int size)
{
if (size == 0)
return Array.Empty<object>();
if (_pool == null)
_pool = new(16);
foreach (ref var tuple in CollectionsMarshal.AsSpan(_pool))
{
if (tuple.InUse)
continue;
if (tuple.Array.Length != size)
continue;
tuple.InUse = true;
return tuple.Array;
}
var newTuple = (InUse: true, Array: new object[size]);
_pool.Add(newTuple);
return newTuple.Array;
}
internal static object[] Rent2()
{
if (_pool2 == null)
_pool2 = new(16);
foreach (ref var tuple in CollectionsMarshal.AsSpan(_pool2))
{
if (tuple.InUse)
continue;
tuple.InUse = true;
return tuple.Array;
}
var newTuple = (InUse: true, Array: new object[8]);
_pool2.Add(newTuple);
return newTuple.Array;
}
/// <summary>
/// Returns the rented object array back to the pool.
/// </summary>
/// <param name="array">The array rented from the pool.</param>
internal static void Return(object[] array)
{
if (array.Length == 0)
return;
foreach (ref var tuple in CollectionsMarshal.AsSpan(_pool))
{
if (tuple.Array != array)
continue;
tuple.InUse = false;
for (int i = 0; i < array.Length; i++)
array[i] = null;
return;
}
throw new NativeInteropException("Tried to free non-pooled object array as pooled ManagedArray");
}
internal static void Return2(object[] array)
{
foreach (ref var tuple in CollectionsMarshal.AsSpan(_pool2))
{
if (tuple.Array != array)
continue;
tuple.InUse = false;
for (int i = 0; i < array.Length; i++)
array[i] = null;
return;
}
throw new NativeInteropException("Tried to free non-pooled object array as pooled ManagedArray");
}
}
#if !USE_AOT
/// <summary>
/// Wrapper class for invoking function pointers from unmanaged code.
@@ -1792,13 +1914,18 @@ namespace FlaxEngine.Interop
internal class ThunkContext
{
internal MethodInfo method;
internal MethodInvoker invoker;
internal Type[] parameterTypes;
internal Invoker.InvokeThunkDelegate methodDelegate;
internal object methodDelegateContext;
internal static object[] objectPool = new object[128];
internal static int objectPoolIndex = 0;
internal ThunkContext(MethodInfo method)
{
this.method = method;
invoker = MethodInvoker.Create(method);
parameterTypes = method.GetParameterTypes();
// Thunk delegates don't support IsByRef parameters (use generic invocation that handles 'out' and 'ref' prams)
@@ -1826,6 +1953,7 @@ namespace FlaxEngine.Interop
genericParamTypes.Add(type);
}
#if false
string invokerTypeName = $"{typeof(Invoker).FullName}+Invoker{(method.IsStatic ? "Static" : "")}{(method.ReturnType != typeof(void) ? "Ret" : "NoRet")}{parameterTypes.Length}{(genericParamTypes.Count > 0 ? "`" + genericParamTypes.Count : "")}";
Type invokerType = Type.GetType(invokerTypeName);
if (invokerType != null)
@@ -1838,11 +1966,13 @@ namespace FlaxEngine.Interop
if (methodDelegate != null)
Assert.IsTrue(methodDelegateContext != null);
#endif
}
public IntPtr InvokeThunk(ManagedHandle instanceHandle, IntPtr param1, IntPtr param2, IntPtr param3, IntPtr param4, IntPtr param5, IntPtr param6, IntPtr param7)
{
IntPtr* nativePtrs = stackalloc IntPtr[] { param1, param2, param3, param4, param5, param6, param7 };
#if false
if (methodDelegate != null)
{
IntPtr returnValue;
@@ -1861,36 +1991,69 @@ namespace FlaxEngine.Interop
return returnValue;
}
else
#endif
{
// The parameters are wrapped (boxed) in GCHandles
object returnObject;
int numParams = parameterTypes.Length;
object[] methodParameters = new object[numParams];
//object[] methodParameters = ObjectArrayPool.Rent(numParams);//new object[numParams]; // TODO: object array pool
if (objectPoolIndex + numParams > 128)
objectPoolIndex = 0;
var paramSpan = objectPool.AsSpan(objectPoolIndex, numParams);
objectPoolIndex += numParams;
for (int i = 0; i < numParams; i++)
{
IntPtr nativePtr = nativePtrs[i];
object managed = null;
if (nativePtr != IntPtr.Zero)
{
object managed = null;
Type type = parameterTypes[i];
Type elementType = type.GetElementType();
if (type.IsByRef)
{
// References use indirection to support value returning
nativePtr = Unsafe.Read<IntPtr>(nativePtr.ToPointer());
type = elementType;
type = type.GetElementType();
}
if (type.IsArray)
managed = MarshalToManaged(nativePtr, type); // Array might be in internal format of custom structs so unbox if need to
else if (type == typeof(Type))
managed = Unsafe.As<TypeHolder>(ManagedHandle.FromIntPtr(nativePtr).Target).type;
else
managed = ManagedHandle.FromIntPtr(nativePtr).Target;
paramSpan[i] = managed;
}
methodParameters[i] = managed;
}
try
{
returnObject = method.Invoke(instanceHandle.IsAllocated ? instanceHandle.Target : null, methodParameters);
returnObject = invoker.Invoke(instanceHandle.IsAllocated ? instanceHandle.Target : null, paramSpan);
//returnObject = method.Invoke(instanceHandle.IsAllocated ? instanceHandle.Target : null, methodParameters);
// Marshal reference parameters back to original unmanaged references
for (int i = 0; i < numParams; i++)
{
IntPtr nativePtr = nativePtrs[i];
Type type = parameterTypes[i];
if (nativePtr != IntPtr.Zero && type.IsByRef)
{
object managed = paramSpan[i];
type = type.GetElementType();
if (managed == null)
Unsafe.Write<IntPtr>(nativePtr.ToPointer(), IntPtr.Zero);
else if (type.IsArray)
MarshalToNative(managed, nativePtr, type);
else
{
var ptr = nativePtr.ToPointer();
if (type == typeof(Type))
managed = GetTypeHolder(Unsafe.As<Type>(managed));
var handle = ManagedHandle.Alloc(managed/*, GCHandleType.Weak*/);
var handleptr = ManagedHandle.ToIntPtr(handle);
Unsafe.Write<IntPtr>(ptr, handleptr);
//Unsafe.Write<IntPtr>(nativePtr.ToPointer(), ManagedHandle.ToIntPtr(ManagedHandle.Alloc(managed, GCHandleType.Weak)));
}
}
}
}
catch (Exception exception)
{
@@ -1900,32 +2063,19 @@ namespace FlaxEngine.Interop
Unsafe.Write<IntPtr>(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(exception, GCHandleType.Weak));
return IntPtr.Zero;
}
// Marshal reference parameters back to original unmanaged references
for (int i = 0; i < numParams; i++)
finally
{
IntPtr nativePtr = nativePtrs[i];
Type type = parameterTypes[i];
object managed = methodParameters[i];
if (nativePtr != IntPtr.Zero && type.IsByRef)
{
type = type.GetElementType();
if (managed == null)
Unsafe.Write<IntPtr>(nativePtr.ToPointer(), IntPtr.Zero);
else if (type.IsArray)
MarshalToNative(managed, nativePtr, type);
else
Unsafe.Write<IntPtr>(nativePtr.ToPointer(), ManagedHandle.ToIntPtr(ManagedHandle.Alloc(managed, GCHandleType.Weak)));
}
//ObjectArrayPool.Return(methodParameters);
for (int i = 0; i < numParams; i++)
paramSpan[i] = null;
}
// Return value
return Invoker.MarshalReturnValueThunkGeneric(method.ReturnType, returnObject);
}
}
}
#endif
}
}
internal class NativeInteropException : Exception
{

View File

@@ -450,7 +450,7 @@ public:
/// <summary>
/// The high-level renderer context. Used to collect the draw calls for the scene rendering. Can be used to perform a custom rendering.
/// </summary>
API_STRUCT(NoDefault) struct RenderContext
API_STRUCT(NoDefault) struct FLAXENGINE_API RenderContext
{
DECLARE_SCRIPTING_TYPE_MINIMAL(RenderContext);
@@ -491,7 +491,7 @@ API_STRUCT(NoDefault) struct RenderContext
/// <summary>
/// The high-level renderer context batch that encapsulates multiple rendering requests within a single task (eg. optimize main view scene rendering and shadow projections at once).
/// </summary>
API_STRUCT(NoDefault) struct RenderContextBatch
API_STRUCT(NoDefault) struct FLAXENGINE_API RenderContextBatch
{
DECLARE_SCRIPTING_TYPE_MINIMAL(RenderContextBatch);

View File

@@ -553,7 +553,7 @@ void GPUTextureDX11::initHandles()
if (useDSV && useSRV && PixelFormatExtensions::HasStencil(format))
{
PixelFormat stencilFormat;
switch (_dxgiFormatDSV)
switch (static_cast<PixelFormat>(_dxgiFormatDSV))
{
case PixelFormat::D24_UNorm_S8_UInt:
srDesc.Format = DXGI_FORMAT_X24_TYPELESS_G8_UINT;

View File

@@ -210,7 +210,7 @@ public:
/// <param name="device">The graphics device.</param>
/// <param name="name">The resource name.</param>
GPUResourceDX12(GPUDeviceDX12* device, const StringView& name)
: GPUResourceBase(device, name)
: GPUResourceBase<GPUDeviceDX12, BaseType>(device, name)
{
}
};

View File

@@ -732,7 +732,7 @@ void GPUTextureDX12::initHandles()
if (useDSV && useSRV && PixelFormatExtensions::HasStencil(format))
{
PixelFormat stencilFormat;
switch (_dxgiFormatDSV)
switch (static_cast<PixelFormat>(_dxgiFormatDSV))
{
case PixelFormat::D24_UNorm_S8_UInt:
srDesc.Format = DXGI_FORMAT_X24_TYPELESS_G8_UINT;

View File

@@ -194,7 +194,8 @@ CultureInfo MUtils::ToNative(void* value)
if (lcidProperty && lcidProperty->GetGetMethod())
{
MObject* lcidObj = lcidProperty->GetGetMethod()->Invoke(value, nullptr, nullptr);
lcid = *(int32*)MCore::Object::Unbox(lcidObj);
PLATFORM_DEBUG_BREAK;
MCore::Object::Unbox(lcidObj, &lcid);
}
}
#endif

View File

@@ -74,7 +74,7 @@
MObject* exception = nullptr; \
auto resultObj = _method_##name->Invoke(GetManagedInstance(), params, &exception); \
if (resultObj) \
result = (DragDropEffect)MUtils::Unbox<int32>(resultObj); \
result = (DragDropEffect)MUtils::Unbox<int32>(resultObj, true); \
END_INVOKE_EVENT(name)
#else
#define INVOKE_EVENT(name, paramsCount, param0, param1, param2)

View File

@@ -11,6 +11,7 @@
#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Platform/WindowsManager.h"
#include "Engine/Platform/SDL/SDLInput.h"
#include "Engine/Engine/CommandLine.h"
#include <SDL3/SDL_hints.h>
#include <SDL3/SDL_init.h>
@@ -317,11 +318,11 @@ Window* SDLPlatform::CreateWindow(const CreateWindowSettings& settings)
return New<SDLWindow>(settings);
}
bool ReadStream(SDL_IOStream*& stream, char* buffer, int32 bufferLength, int32& bufferPosition, LogType logType, CreateProcessSettings& settings)
bool ReadStream(SDL_IOStream*& stream, char* buffer, int64 bufferLength, int64& bufferPosition, LogType logType, CreateProcessSettings& settings)
{
bool flushBuffer = false;
bool success = true;
int32 read = SDL_ReadIO(stream, buffer + bufferPosition, bufferLength - bufferPosition - 1);
auto read = SDL_ReadIO(stream, buffer + bufferPosition, bufferLength - bufferPosition - 1);
if (read == 0)
{
SDL_IOStatus status = SDL_GetIOStatus(stream);
@@ -335,8 +336,8 @@ bool ReadStream(SDL_IOStream*& stream, char* buffer, int32 bufferLength, int32&
}
else
{
int32 startPosition = bufferPosition;
bufferPosition += read;
int64 startPosition = bufferPosition;
bufferPosition += (int64)read;
if (bufferPosition == bufferLength - 1)
{
flushBuffer = true;
@@ -344,7 +345,7 @@ bool ReadStream(SDL_IOStream*& stream, char* buffer, int32 bufferLength, int32&
}
else
{
for (int i = startPosition; i < bufferPosition; ++i)
for (int64 i = startPosition; i < bufferPosition; ++i)
{
if (buffer[i] == '\n')
{
@@ -357,13 +358,13 @@ bool ReadStream(SDL_IOStream*& stream, char* buffer, int32 bufferLength, int32&
if (flushBuffer)
{
int32 start = 0;
for (int i = 0; i < bufferPosition; ++i)
int64 start = 0;
for (int64 i = 0; i < bufferPosition; ++i)
{
if (buffer[i] != '\n')
continue;
String str(&buffer[start], i - start + 1);
String str(&buffer[start], (int32)(i - start + 1));
#if LOG_ENABLE
if (settings.LogOutput)
Log::Logger::Write(logType, StringView(str.Get(), str.Length() - 1));
@@ -372,7 +373,7 @@ bool ReadStream(SDL_IOStream*& stream, char* buffer, int32 bufferLength, int32&
settings.Output.Add(str.Get(), str.Length());
start = i + 1;
}
int32 length = bufferPosition - start;
int64 length = bufferPosition - start;
if (length > 0)
{
// TODO: Use memmove here? Overlapped memory regions with memcpy is undefined behaviour
@@ -396,13 +397,7 @@ int32 SDLPlatform::CreateProcess(CreateProcessSettings& settings)
int32 result = 0;
const bool captureStdOut = settings.LogOutput || settings.SaveOutput;
const StringAnsi cmdLine = StringAnsi::Format("\"{0}\" {1}", StringAnsi(settings.FileName), StringAnsi(settings.Arguments));
const char* cmd[] = { "sh", "-c", cmdLine.Get(), (char *)0 };
StringAnsi workingDirectory(settings.WorkingDirectory);
#if PLATFORM_WINDOWS
bool background = !settings.WaitForEnd || settings.HiddenWindow; // This also hides the window on Windows
#else
bool background = !settings.WaitForEnd;
#endif
// Populate environment with current values from parent environment.
// SDL does not populate the environment with the latest values but with a snapshot captured during initialization.
@@ -414,8 +409,27 @@ int32 SDLPlatform::CreateProcess(CreateProcessSettings& settings)
for (auto iter = settings.Environment.Begin(); iter != settings.Environment.End(); ++iter)
SDL_SetEnvironmentVariable(env, StringAnsi(iter->Key).Get(), StringAnsi(iter->Value).Get(), true);
// Parse argument list with possible quotes included
Array<StringAnsi> arguments;
arguments.Add(StringAnsi(settings.FileName));
if (CommandLine::ParseArguments(settings.Arguments, arguments))
{
LOG(Error, "Failed to parse arguments for process {}: '{}'", settings.FileName.Get(), settings.Arguments.Get());
return -1;
}
Array<const char*> cmd;
for (const StringAnsi& str : arguments)
cmd.Add(str.Get());
cmd.Add((const char*)0);
#if PLATFORM_WINDOWS
bool background = !settings.WaitForEnd || settings.HiddenWindow; // This also hides the window on Windows
#else
bool background = !settings.WaitForEnd;
#endif
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, cmd);
SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, cmd.Get());
SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, env);
SDL_SetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_BACKGROUND_BOOLEAN, background);
if (workingDirectory.HasChars())
@@ -430,12 +444,12 @@ int32 SDLPlatform::CreateProcess(CreateProcessSettings& settings)
SDL_DestroyEnvironment(env);
if (process == nullptr)
{
LOG(Error, "Failed to run process {}: {}", String(settings.FileName).Get(), String(SDL_GetError()));
LOG(Error, "Failed to run process {}: {}", settings.FileName.Get(), String(SDL_GetError()));
return -1;
}
props = SDL_GetProcessProperties(process);
int32 pid = SDL_GetNumberProperty(props, SDL_PROP_PROCESS_PID_NUMBER, 0);
int64 pid = SDL_GetNumberProperty(props, SDL_PROP_PROCESS_PID_NUMBER, 0);
SDL_IOStream* stdoutStream = nullptr;
SDL_IOStream* stderrStream = nullptr;
if (captureStdOut)
@@ -446,9 +460,9 @@ int32 SDLPlatform::CreateProcess(CreateProcessSettings& settings)
// Handle process output in realtime
char stdoutBuffer[2049];
int32 stdoutPosition = 0;
int64 stdoutPosition = 0;
char stderrBuffer[2049];
int32 stderrPosition = 0;
int64 stderrPosition = 0;
while (stdoutStream != nullptr && stderrStream != nullptr)
{
if (stdoutStream != nullptr && !ReadStream(stdoutStream, stdoutBuffer, sizeof(stdoutBuffer), stdoutPosition, LogType::Info, settings))

View File

@@ -1332,7 +1332,7 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp
}
// Unbox result
result = MUtils::UnboxVariant(resultObject);
result = MUtils::UnboxVariant(resultObject, true);
#if 0
// Helper method invocations logging
@@ -1366,7 +1366,7 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp
paramValue.SetString(MUtils::ToString((MString*)param));
break;
case VariantType::Object:
paramValue = MUtils::UnboxVariant((MObject*)param);
paramValue = MUtils::UnboxVariant((MObject*)param, true);
break;
case VariantType::Structure:
{
@@ -1383,7 +1383,7 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp
{
MType* paramType = mMethod->GetParameterType(paramIdx);
if (MCore::Type::IsReference(paramType) && MCore::Type::GetType(paramType) == MTypes::Object)
paramValue = MUtils::UnboxVariant((MObject*)outParams[paramIdx]);
paramValue = MUtils::UnboxVariant((MObject*)outParams[paramIdx], true);
break;
}
}
@@ -1527,7 +1527,7 @@ bool ManagedBinaryModule::GetFieldValue(void* field, const Variant& instance, Va
const auto mField = (MField*)field;
resultObject = mField->GetValueBoxed(instanceObject);
}
result = MUtils::UnboxVariant(resultObject);
result = MUtils::UnboxVariant(resultObject, true);
return false;
#else
return true;

View File

@@ -69,6 +69,7 @@ public:
{
static MObject* Box(void* value, const MClass* klass);
static void* Unbox(MObject* obj);
static void Unbox(MObject* obj, void* dest);
static MObject* New(const MClass* klass);
static void Init(MObject* obj);
static MClass* GetClass(MObject* obj);
@@ -84,6 +85,7 @@ public:
static MString* GetEmpty(MDomain* domain = nullptr);
static MString* New(const char* str, int32 length, MDomain* domain = nullptr);
static MString* New(const Char* str, int32 length, MDomain* domain = nullptr);
static void Free(MString* obj);
static StringView GetChars(MString* obj);
};
@@ -93,6 +95,7 @@ public:
struct FLAXENGINE_API Array
{
static MArray* New(const MClass* elementKlass, int32 length);
static void Free(const MArray* array);
static MClass* GetClass(MClass* elementKlass);
static MClass* GetArrayClass(const MArray* obj);
static int32 GetLength(const MArray* obj);

View File

@@ -304,7 +304,7 @@ MTypeObject* MUtils::BoxVariantType(const VariantType& value)
return INTERNAL_TYPE_GET_OBJECT(mType);
}
Variant MUtils::UnboxVariant(MObject* value)
Variant MUtils::UnboxVariant(MObject* value, bool releaseHandle)
{
if (value == nullptr)
return Variant::Null;
@@ -313,7 +313,10 @@ Variant MUtils::UnboxVariant(MObject* value)
MType* mType = klass->GetType();
const MTypes mTypes = MCore::Type::GetType(mType);
void* unboxed = MCore::Object::Unbox(value);
Variant storage;
MCore::Object::Unbox(value, &storage.AsData);
void* unboxed = &storage.AsData;
// Fast type detection for in-built types
switch (mTypes)
@@ -345,7 +348,11 @@ Variant MUtils::UnboxVariant(MObject* value)
case MTypes::R8:
return *static_cast<double*>(unboxed);
case MTypes::String:
{
if (releaseHandle)
MUtils::FreeManaged<String>(value);
return Variant(MUtils::ToString((MString*)value));
}
case MTypes::Ptr:
return *static_cast<void**>(unboxed);
case MTypes::ValueType:
@@ -400,6 +407,8 @@ Variant MUtils::UnboxVariant(MObject* value)
{
Variant v;
v.SetBlob(ptr, MCore::Array::GetLength((MArray*)value));
if (releaseHandle)
MUtils::FreeManaged<Array<byte>>(value);
return v;
}
const StringAnsiView fullname = arrayClass->GetFullName();
@@ -486,13 +495,16 @@ Variant MUtils::UnboxVariant(MObject* value)
// TODO: optimize this for large arrays to prevent multiple AllocStructure calls in Variant::SetType by using computed struct type
for (int32 i = 0; i < array.Count(); i++)
{
PLATFORM_DEBUG_BREAK; // FIXME
auto& a = array[i];
a.SetType(elementType);
void* managed = (byte*)ptr + elementSize * i;
// TODO: optimize structures unboxing to not require MObject* but raw managed value data to prevent additional boxing here
MObject* boxed = MCore::Object::New(elementClass);
Platform::MemoryCopy(MCore::Object::Unbox(boxed), managed, elementSize);
type.Struct.Unbox(a.AsBlob.Data, boxed);
//MObject* boxed = MCore::Object::New(elementClass);
//Variant storage;
MCore::Object::Unbox((MObject*)managed, &a.AsBlob.Data);
//Platform::MemoryCopy(MCore::Object::Unbox(boxed), managed, elementSize);
//type.Struct.Unbox(a.AsBlob.Data, boxed);
}
break;
}
@@ -508,7 +520,10 @@ Variant MUtils::UnboxVariant(MObject* value)
{
// Array of Objects
for (int32 i = 0; i < array.Count(); i++)
array[i] = UnboxVariant(((MObject**)ptr)[i]);
array[i] = UnboxVariant(((MObject**)ptr)[i], releaseHandle);
if (releaseHandle)
MUtils::FreeManaged<Array<byte>>(value);
}
return v;
}
@@ -527,10 +542,13 @@ Variant MUtils::UnboxVariant(MObject* value)
{
MObject* keyManaged = managedKeysPtr[i];
MObject* valueManaged = managed.GetValue(keyManaged);
native.Add(UnboxVariant(keyManaged), UnboxVariant(valueManaged));
native.Add(UnboxVariant(keyManaged, releaseHandle), UnboxVariant(valueManaged, releaseHandle));
}
Variant v(MoveTemp(native));
v.Type.SetTypeName(klass->GetFullName());
if (releaseHandle)
MCore::GCHandle::Free(*(MGCHandle*)&value);
//MUtils::FreeManaged<Dictionary<byte, byte>>(value);
return v;
}
break;
@@ -547,7 +565,10 @@ Variant MUtils::UnboxVariant(MObject* value)
Variant v;
v.Type = MoveTemp(VariantType(VariantType::Enum, fullname));
// TODO: what about 64-bit enum? use enum size with memcpy
v.AsUint64 = *static_cast<uint32*>(MCore::Object::Unbox(value));
PLATFORM_DEBUG_BREAK; // FIXME
MCore::Object::Unbox(value, &v.AsUint64);
if (releaseHandle)
MUtils::FreeManaged<byte>(value);
return v;
}
if (klass->IsValueType())
@@ -565,10 +586,12 @@ Variant MUtils::UnboxVariant(MObject* value)
type.Struct.Unbox(v.AsBlob.Data, value);
return v;
}
return Variant(value);
}
return Variant(value);
auto variant = Variant(value);
if (releaseHandle)
MUtils::FreeManaged<byte>(value);
return variant;
}
MObject* MUtils::BoxVariant(const Variant& value)
@@ -727,9 +750,10 @@ MObject* MUtils::BoxVariant(const Variant& value)
ASSERT(type.Type == ScriptingTypes::Structure);
for (int32 i = 0; i < array.Count(); i++)
{
PLATFORM_DEBUG_BREAK; // FIXME
// TODO: optimize structures boxing to not return MObject* but use raw managed object to prevent additional boxing here
MObject* boxed = type.Struct.Box(array[i].AsBlob.Data);
Platform::MemoryCopy(managedPtr + elementSize * i, MCore::Object::Unbox(boxed), elementSize);
MCore::Object::Unbox(boxed, managedPtr + elementSize * i);
}
break;
}
@@ -1160,7 +1184,9 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, MType* type, bool& failed)
auto& valueType = typeHandle.GetType();
if (valueType.ManagedClass == MCore::Type::GetClass(type))
{
return MCore::Object::Unbox(valueType.Struct.Box(value.AsBlob.Data));
PLATFORM_DEBUG_BREAK; // FIXME
MCore::Object::Unbox(valueType.Struct.Box(value.AsBlob.Data), &value.AsBlob.Data);
return &value.AsBlob.Data;
}
LOG(Error, "Cannot marshal argument of type {0} as {1}", String(valueType.Fullname), MCore::Type::ToString(type));
}
@@ -1171,9 +1197,11 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, MType* type, bool& failed)
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(fullname);
if (typeHandle)
{
PLATFORM_DEBUG_BREAK; // FIXME
auto& valueType = typeHandle.GetType();
value.SetType(VariantType(VariantType::Structure, fullname));
return MCore::Object::Unbox(valueType.Struct.Box(value.AsBlob.Data));
MCore::Object::Unbox(valueType.Struct.Box(value.AsBlob.Data), &value.AsBlob.Data);
return &value.AsBlob.Data;
}
}
}

View File

@@ -39,7 +39,7 @@ namespace MUtils
extern FLAXENGINE_API MTypeObject* BoxScriptingTypeHandle(const ScriptingTypeHandle& value);
extern FLAXENGINE_API VariantType UnboxVariantType(MType* type);
extern FLAXENGINE_API MTypeObject* BoxVariantType(const VariantType& value);
extern FLAXENGINE_API Variant UnboxVariant(MObject* value);
extern FLAXENGINE_API Variant UnboxVariant(MObject* value, bool releaseHandle = false);
extern FLAXENGINE_API MObject* BoxVariant(const Variant& value);
}
@@ -49,10 +49,58 @@ struct MConverter
{
MObject* Box(const T& data, const MClass* klass);
void Unbox(T& result, MObject* data);
void FreeManaged(MObject* data);
void ToManagedArray(MArray* result, const Span<T>& data);
void ToNativeArray(Span<T>& result, const MArray* data);
void FreeManagedArray(MArray* array);
};
// Simple array used to pass arrays of blittable data between managed and unmanaged side.
template<typename T>
struct NativeArray
{
T* data;
int32 length;
};
#if USE_NETCORE
// Special case for boolean values, the handles are fixed and should not be removed
template<>
struct MConverter<bool>
{
MObject* Box(const bool& data, const MClass* klass)
{
return MCore::Object::Box((void*)&data, klass);
}
void Unbox(bool& result, MObject* data)
{
if (data)
MCore::Object::Unbox(data, &result);
}
void FreeManaged(MObject* data)
{
}
void ToManagedArray(MArray* result, const Span<bool>& data)
{
Platform::MemoryCopy(MCore::Array::GetAddress(result), data.Get(), data.Length() * sizeof(bool));
}
void ToNativeArray(Span<bool>& result, const MArray* data)
{
Platform::MemoryCopy(result.Get(), MCore::Array::GetAddress(data), result.Length() * sizeof(bool));
}
void FreeManagedArray(MArray* array)
{
MCore::Array::Free(array);
MCore::GCHandle::Free(*(MGCHandle*)&array);
}
};
#endif
// Converter for POD types (that can use raw memory copy).
template<typename T>
struct MConverter<T, typename TEnableIf<TAnd<TIsPODType<T>, TNot<TIsBaseOf<class ScriptingObject, typename TRemovePointer<T>::Type>>>::Value>::Type>
@@ -65,7 +113,7 @@ struct MConverter<T, typename TEnableIf<TAnd<TIsPODType<T>, TNot<TIsBaseOf<class
void Unbox(T& result, MObject* data)
{
if (data)
Platform::MemoryCopy(&result, MCore::Object::Unbox(data), sizeof(T));
MCore::Object::Unbox(data, &result);
}
void ToManagedArray(MArray* result, const Span<T>& data)
@@ -77,6 +125,17 @@ struct MConverter<T, typename TEnableIf<TAnd<TIsPODType<T>, TNot<TIsBaseOf<class
{
Platform::MemoryCopy(result.Get(), MCore::Array::GetAddress(data), result.Length() * sizeof(T));
}
void FreeManaged(MObject* data)
{
MCore::GCHandle::Free(*(MGCHandle*)&data);
}
void FreeManagedArray(MArray* array)
{
MCore::Array::Free(array);
MCore::GCHandle::Free(*(MGCHandle*)&array);
}
};
// Converter for String.
@@ -85,22 +144,22 @@ struct MConverter<String>
{
MObject* Box(const String& data, const MClass* klass)
{
#if USE_NETCORE
/*#if USE_NETCORE
MString* str = MUtils::ToString(data);
return MCore::Object::Box(str, klass);
#else
#else*/
return (MObject*)MUtils::ToString(data);
#endif
//#endif
}
void Unbox(String& result, MObject* data)
{
#if USE_NETCORE
/*#if USE_NETCORE
MString* str = (MString*)MCore::Object::Unbox(data);
result = MUtils::ToString(str);
#else
#else*/
result = MUtils::ToString((MString*)data);
#endif
//#endif
}
void ToManagedArray(MArray* result, const Span<String>& data)
@@ -120,6 +179,23 @@ struct MConverter<String>
for (int32 i = 0; i < result.Length(); i++)
MUtils::ToString(dataPtr[i], result.Get()[i]);
}
void FreeManaged(MObject* data)
{
//MString* str = (MString*)MCore::Object::Unbox(data);
//MCore::GCHandle::Free(*(MGCHandle*)&str);
MCore::String::Free((MString*)data);
}
void FreeManagedArray(MArray* array)
{
MString** dataPtr = MCore::Array::GetAddress<MString*>(array);
auto length = MCore::Array::GetLength(array);
for (int32 i = 0; i < length; i++)
MCore::String::Free(dataPtr[i]);
MCore::Array::Free(array);
MCore::GCHandle::Free(*(MGCHandle*)&array);
}
};
// Converter for StringAnsi.
@@ -153,6 +229,21 @@ struct MConverter<StringAnsi>
for (int32 i = 0; i < result.Length(); i++)
MUtils::ToString(dataPtr[i], result.Get()[i]);
}
void FreeManaged(MObject* data)
{
MCore::String::Free((MString*)data);
}
void FreeManagedArray(MArray* array)
{
MString** dataPtr = MCore::Array::GetAddress<MString*>(array);
auto length = MCore::Array::GetLength(array);
for (int32 i = 0; i < length; i++)
MCore::String::Free(dataPtr[i]);
MCore::Array::Free(array);
MCore::GCHandle::Free(*(MGCHandle*)&array);
}
};
// Converter for StringView.
@@ -161,16 +252,19 @@ struct MConverter<StringView>
{
MObject* Box(const StringView& data, const MClass* klass)
{
PLATFORM_DEBUG_BREAK; // FIXME
return (MObject*)MUtils::ToString(data);
}
void Unbox(StringView& result, MObject* data)
{
PLATFORM_DEBUG_BREAK; // FIXME
result = MUtils::ToString((MString*)data);
}
void ToManagedArray(MArray* result, const Span<StringView>& data)
{
PLATFORM_DEBUG_BREAK; // FIXME
if (data.Length() == 0)
return;
MObject** objects = (MObject**)Allocator::Allocate(data.Length() * sizeof(MObject*));
@@ -182,10 +276,28 @@ struct MConverter<StringView>
void ToNativeArray(Span<StringView>& result, const MArray* data)
{
PLATFORM_DEBUG_BREAK; // FIXME
MString** dataPtr = MCore::Array::GetAddress<MString*>(data);
for (int32 i = 0; i < result.Length(); i++)
MUtils::ToString(dataPtr[i], result.Get()[i]);
}
void FreeManaged(MObject* data)
{
PLATFORM_DEBUG_BREAK; // FIXME
MCore::String::Free((MString*)data);
}
void FreeManagedArray(MArray* array)
{
PLATFORM_DEBUG_BREAK; // FIXME
MString** dataPtr = MCore::Array::GetAddress<MString*>(array);
auto length = MCore::Array::GetLength(array);
for (int32 i = 0; i < length; i++)
MCore::String::Free(dataPtr[i]);
MCore::Array::Free(array);
MCore::GCHandle::Free(*(MGCHandle*)&array);
}
};
// Converter for Variant.
@@ -194,16 +306,19 @@ struct MConverter<Variant>
{
MObject* Box(const Variant& data, const MClass* klass)
{
PLATFORM_DEBUG_BREAK; // FIXME
return MUtils::BoxVariant(data);
}
void Unbox(Variant& result, MObject* data)
{
PLATFORM_DEBUG_BREAK; // FIXME
result = MUtils::UnboxVariant(data);
}
void ToManagedArray(MArray* result, const Span<Variant>& data)
{
PLATFORM_DEBUG_BREAK; // FIXME
if (data.Length() == 0)
return;
MObject** objects = (MObject**)Allocator::Allocate(data.Length() * sizeof(MObject*));
@@ -215,10 +330,23 @@ struct MConverter<Variant>
void ToNativeArray(Span<Variant>& result, const MArray* data)
{
PLATFORM_DEBUG_BREAK; // FIXME
MObject** dataPtr = MCore::Array::GetAddress<MObject*>(data);
for (int32 i = 0; i < result.Length(); i++)
result.Get()[i] = MUtils::UnboxVariant(dataPtr[i]);
}
void FreeManaged(MObject* data)
{
PLATFORM_DEBUG_BREAK; // FIXME
}
void FreeManagedArray(MArray* array)
{
PLATFORM_DEBUG_BREAK; // FIXME
MCore::Array::Free(array);
MCore::GCHandle::Free(*(MGCHandle*)&array);
}
};
// Converter for Scripting Objects (collection of pointers).
@@ -227,11 +355,13 @@ struct MConverter<T*, typename TEnableIf<TIsBaseOf<class ScriptingObject, T>::Va
{
MObject* Box(T* data, const MClass* klass)
{
PLATFORM_DEBUG_BREAK; // FIXME
return data ? data->GetOrCreateManagedInstance() : nullptr;
}
void Unbox(T*& result, MObject* data)
{
PLATFORM_DEBUG_BREAK; // FIXME
result = (T*)ScriptingObject::ToNative(data);
}
@@ -252,6 +382,17 @@ struct MConverter<T*, typename TEnableIf<TIsBaseOf<class ScriptingObject, T>::Va
for (int32 i = 0; i < result.Length(); i++)
result.Get()[i] = (T*)ScriptingObject::ToNative(dataPtr[i]);
}
void FreeManaged(MObject* data)
{
PLATFORM_DEBUG_BREAK; // FIXME
}
void FreeManagedArray(MArray* array)
{
MCore::Array::Free(array);
MCore::GCHandle::Free(*(MGCHandle*)&array);
}
};
// Converter for Scripting Objects (collection of values).
@@ -260,11 +401,13 @@ struct MConverter<T, typename TEnableIf<TIsBaseOf<class ScriptingObject, T>::Val
{
MObject* Box(const T& data, const MClass* klass)
{
PLATFORM_DEBUG_BREAK; // FIXME
return data.GetOrCreateManagedInstance();
}
void ToManagedArray(MArray* result, const Span<T>& data)
{
PLATFORM_DEBUG_BREAK; // FIXME
if (data.Length() == 0)
return;
MObject** objects = (MObject**)Allocator::Allocate(data.Length() * sizeof(MObject*));
@@ -273,6 +416,18 @@ struct MConverter<T, typename TEnableIf<TIsBaseOf<class ScriptingObject, T>::Val
MCore::GC::WriteArrayRef(result, Span<MObject*>(objects, data.Length()));
Allocator::Free(objects);
}
void FreeManaged(MObject* data)
{
PLATFORM_DEBUG_BREAK; // FIXME
}
void FreeManagedArray(MArray* array)
{
PLATFORM_DEBUG_BREAK; // FIXME
MCore::Array::Free(array);
MCore::GCHandle::Free(*(MGCHandle*)&array);
}
};
// Converter for ScriptingObject References.
@@ -284,16 +439,19 @@ struct MConverter<ScriptingObjectReference<T>>
{
MObject* Box(const ScriptingObjectReference<T>& data, const MClass* klass)
{
PLATFORM_DEBUG_BREAK; // FIXME
return data.GetManagedInstance();
}
void Unbox(ScriptingObjectReference<T>& result, MObject* data)
{
PLATFORM_DEBUG_BREAK; // FIXME
result = (T*)ScriptingObject::ToNative(data);
}
void ToManagedArray(MArray* result, const Span<ScriptingObjectReference<T>>& data)
{
PLATFORM_DEBUG_BREAK; // FIXME
if (data.Length() == 0)
return;
MObject** objects = (MObject**)Allocator::Allocate(data.Length() * sizeof(MObject*));
@@ -305,10 +463,23 @@ struct MConverter<ScriptingObjectReference<T>>
void ToNativeArray(Span<ScriptingObjectReference<T>>& result, const MArray* data)
{
PLATFORM_DEBUG_BREAK; // FIXME
MObject** dataPtr = MCore::Array::GetAddress<MObject*>(data);
for (int32 i = 0; i < result.Length(); i++)
result.Get()[i] = (T*)ScriptingObject::ToNative(dataPtr[i]);
}
void FreeManaged(MObject* data)
{
PLATFORM_DEBUG_BREAK; // FIXME
}
void FreeManagedArray(MArray* array)
{
PLATFORM_DEBUG_BREAK; // FIXME
MCore::Array::Free(array);
MCore::GCHandle::Free(*(MGCHandle*)&array);
}
};
// Converter for Asset References.
@@ -320,11 +491,13 @@ struct MConverter<AssetReference<T>>
{
MObject* Box(const AssetReference<T>& data, const MClass* klass)
{
PLATFORM_DEBUG_BREAK; // FIXME
return data.GetManagedInstance();
}
void Unbox(AssetReference<T>& result, MObject* data)
{
PLATFORM_DEBUG_BREAK; // FIXME
result = (T*)ScriptingObject::ToNative(data);
}
@@ -341,10 +514,22 @@ struct MConverter<AssetReference<T>>
void ToNativeArray(Span<AssetReference<T>>& result, const MArray* data)
{
//PLATFORM_DEBUG_BREAK; // FIXME
MObject** dataPtr = MCore::Array::GetAddress<MObject*>(data);
for (int32 i = 0; i < result.Length(); i++)
result.Get()[i] = (T*)ScriptingObject::ToNative(dataPtr[i]);
}
void FreeManaged(MObject* data)
{
PLATFORM_DEBUG_BREAK; // FIXME
}
void FreeManagedArray(MArray* array)
{
MCore::Array::Free(array);
MCore::GCHandle::Free(*(MGCHandle*)&array);
}
};
// TODO: use MarshalAs=Guid on SoftAssetReference to pass guid over bindings and not load asset in glue code
@@ -355,6 +540,7 @@ struct MConverter<SoftAssetReference<T>>
{
void ToManagedArray(MArray* result, const Span<SoftAssetReference<T>>& data)
{
//PLATFORM_DEBUG_BREAK; // FIXME
if (data.Length() == 0)
return;
MObject** objects = (MObject**)Allocator::Allocate(data.Length() * sizeof(MObject*));
@@ -366,10 +552,23 @@ struct MConverter<SoftAssetReference<T>>
void ToNativeArray(Span<SoftAssetReference<T>>& result, const MArray* data)
{
//PLATFORM_DEBUG_BREAK; // FIXME
MObject** dataPtr = MCore::Array::GetAddress<MObject*>(data);
for (int32 i = 0; i < result.Length(); i++)
result.Get()[i] = (T*)ScriptingObject::ToNative(dataPtr[i]);
}
void FreeManaged(MObject* data)
{
PLATFORM_DEBUG_BREAK; // FIXME
}
void FreeManagedArray(MArray* array)
{
PLATFORM_DEBUG_BREAK; // FIXME
MCore::Array::Free(array);
MCore::GCHandle::Free(*(MGCHandle*)&array);
}
};
// Converter for Array.
@@ -378,6 +577,7 @@ struct MConverter<Array<T>>
{
MObject* Box(const Array<T>& data, const MClass* klass)
{
PLATFORM_DEBUG_BREAK; // FIXME
if (!klass)
return nullptr;
MArray* result = MCore::Array::New(klass->GetElementClass(), data.Count());
@@ -395,6 +595,23 @@ struct MConverter<Array<T>>
Span<T> resultSpan(result.Get(), length);
converter.ToNativeArray(resultSpan, array);
}
void FreeManaged(MObject* data)
{
PLATFORM_DEBUG_BREAK; // FIXME
MArray* array = (MArray*)data;
MConverter<T> converter;
converter.FreeManagedArray(array);
//MCore::Array::Free(array);
//MCore::GCHandle::Free(*(MGCHandle*)&data);
}
void FreeManagedArray(MArray* array)
{
PLATFORM_DEBUG_BREAK; // FIXME
MCore::Array::Free(array);
MCore::GCHandle::Free(*(MGCHandle*)&array);
}
};
namespace MUtils
@@ -435,6 +652,7 @@ namespace MUtils
/// <summary>
/// Unboxes MObject to the native value of the given type.
/// </summary>
///
template<class T>
T Unbox(MObject* object)
{
@@ -444,6 +662,71 @@ namespace MUtils
return result;
}
/// <summary>
/// Unboxes MObject to the native value of the given type.
/// </summary>
/// <param name="object">The object.</param>
/// <param name="releaseHandle">True if boxed managed handle should be released.</param>
template<class T>
T Unbox(MObject* object, bool releaseHandle)
{
MConverter<T> converter;
T result;
converter.Unbox(result, object);
if (releaseHandle)
converter.FreeManaged(object);
return result;
}
/// <summary>
/// Unboxes MObject to the native value of the given type.
/// </summary>
///
template<>
inline bool Unbox(MObject* object)
{
MConverter<bool> converter;
int32 result;
converter.Unbox(*(bool*)&result, object);
return result != 0;
}
/// <summary>
/// Unboxes MObject to the native value of the given type.
/// </summary>
/// <param name="object">The object.</param>
/// <param name="releaseHandle">True if boxed managed handle should be released.</param>
template<>
inline bool Unbox(MObject* object, bool releaseHandle)
{
MConverter<bool> converter;
int32 result;
converter.Unbox(*(bool*)&result, object);
if (releaseHandle)
converter.FreeManaged(object);
return result != 0;
}
/// <summary>
/// Releases the managed resources of a boxed object.
/// </summary>
template<class T>
void FreeManaged(MObject* object)
{
MConverter<T> converter;
converter.FreeManaged(object);
}
/// <summary>
/// Releases the managed resources of a boxed object.
/// </summary>
template<class T>
void FreeManagedArray(MArray* array)
{
MConverter<T> converter;
converter.FreeManagedArray(array);
}
/// <summary>
/// Links managed array data to the unmanaged BytesContainer.
/// </summary>
@@ -580,6 +863,52 @@ namespace MUtils
}
#if USE_NETCORE
/// <summary>
/// Allocates new boolean array and copies data from the given unmanaged data container. The managed runtime is responsible for releasing the returned array data.
/// </summary>
/// <param name="data">The input data.</param>
/// <returns>The output array.</returns>
template<typename T>
FORCE_INLINE T* ToNativeArray(const Array<T>& data)
{
// System.Runtime.InteropServices.Marshalling.ArrayMarshaller uses CoTask memory alloc to native data pointer
T* arr = (T*)MCore::GC::AllocateMemory(data.Count() * sizeof(T), true);
Platform::MemoryCopy(arr, data.Get(), data.Count() * sizeof(T));
return arr;
}
/// <summary>
/// Allocates new boolean array and copies data from the given unmanaged data container. The managed runtime is responsible for releasing the returned array data.
/// </summary>
/// <param name="data">The input data.</param>
/// <returns>The output array.</returns>
template<typename T, typename AllocationType = HeapAllocation>
FORCE_INLINE NativeArray<T> ToNativeArrayWrapper(const Array<T, AllocationType>& data)
{
// System.Runtime.InteropServices.Marshalling.ArrayMarshaller uses CoTask memory alloc to native data pointer
NativeArray<T> arr;
arr.length = data.Count();
arr.data = (T*)MCore::GC::AllocateMemory(arr.length * sizeof(T), true);
Platform::MemoryCopy(arr.data, data.Get(), arr.length * sizeof(T));
return arr;
}
/// <summary>
/// Allocates new boolean array and copies data from the given unmanaged data container. The managed runtime is responsible for releasing the returned array data.
/// </summary>
/// <param name="data">The input data.</param>
/// <returns>The output array.</returns>
template<typename T>
FORCE_INLINE NativeArray<T> ToNativeArrayWrapper(const Span<T>& data)
{
// System.Runtime.InteropServices.Marshalling.ArrayMarshaller uses CoTask memory alloc to native data pointer
NativeArray<T> arr;
arr.length = data.Length();
arr.data = (T*)MCore::GC::AllocateMemory(arr.length * sizeof(T), true);
Platform::MemoryCopy(arr.data, data.Get(), arr.length * sizeof(T));
return arr;
}
/// <summary>
/// Allocates new boolean array and copies data from the given unmanaged data container. The managed runtime is responsible for releasing the returned array data.
/// </summary>

View File

@@ -400,9 +400,14 @@ MObject* MCore::Object::Box(void* value, const MClass* klass)
}
void* MCore::Object::Unbox(MObject* obj)
{
CRASH; // Should not be used anymore
}
void MCore::Object::Unbox(MObject* obj, void* dest)
{
static void* UnboxValuePtr = GetStaticMethodPointer(TEXT("UnboxValue"));
return CallStaticMethod<void*, void*>(UnboxValuePtr, obj);
return CallStaticMethod<void, void*, void*>(UnboxValuePtr, obj, dest);
}
MObject* MCore::Object::New(const MClass* klass)
@@ -462,12 +467,24 @@ StringView MCore::String::GetChars(MString* obj)
return StringView(chars, length);
}
void MCore::String::Free(MString* obj)
{
static void* FreeStringPtr = GetStaticMethodPointer(TEXT("FreeString"));
CallStaticMethod<void, void*>(FreeStringPtr, obj);
}
MArray* MCore::Array::New(const MClass* elementKlass, int32 length)
{
static void* NewArrayPtr = GetStaticMethodPointer(TEXT("NewArray"));
return (MArray*)CallStaticMethod<void*, void*, long long>(NewArrayPtr, elementKlass->_handle, length);
}
void MCore::Array::Free(const MArray* array)
{
static void* FreeArrayPtr = GetStaticMethodPointer(TEXT("FreeArray"));
CallStaticMethod<void, void*>(FreeArrayPtr, (void*)array);
}
MClass* MCore::Array::GetClass(MClass* elementKlass)
{
static void* GetArrayTypeFromElementTypePtr = GetStaticMethodPointer(TEXT("GetArrayTypeFromElementType"));
@@ -883,7 +900,6 @@ bool MAssembly::LoadCorlib()
bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePath)
{
// TODO: Use new hostfxr delegate load_assembly_bytes? (.NET 8+)
// Open .Net assembly
static void* LoadAssemblyImagePtr = GetStaticMethodPointer(TEXT("LoadAssemblyImage"));
_handle = CallStaticMethod<void*, const Char*>(LoadAssemblyImagePtr, assemblyPath.Get());

View File

@@ -738,6 +738,11 @@ void* MCore::Object::Unbox(MObject* obj)
//return mono_object_unbox(obj);
}
void MCore::Object::Unbox(MObject* obj, void* dest)
{
CRASH; // Not applicable
}
MObject* MCore::Object::New(const MClass* klass)
{
return mono_object_new(mono_domain_get(), klass->GetNative());
@@ -787,12 +792,20 @@ StringView MCore::String::GetChars(MString* obj)
return StringView(mono_string_chars(obj), (int32)mono_string_length(obj));
}
void MCore::String::Free(MString* obj)
{
}
MArray* MCore::Array::New(const MClass* elementKlass, int32 length)
{
// TODO: use shared empty arrays cache
return mono_array_new(mono_domain_get(), elementKlass->GetNative(), length);
}
void MCore::Array::Free(const MArray* array)
{
}
MClass* MCore::Array::GetClass(MClass* elementKlass)
{
MonoClass* monoClass = mono_array_class_get(elementKlass->GetNative(), 1);

View File

@@ -80,6 +80,10 @@ void* MCore::Object::Unbox(MObject* obj)
return nullptr;
}
void MCore::Object::Unbox(MObject* obj, void* dest)
{
}
MObject* MCore::Object::New(const MClass* klass)
{
return nullptr;
@@ -124,11 +128,19 @@ StringView MCore::String::GetChars(MString* obj)
return StringView::Empty;
}
void MCore::String::Free(MString* obj)
{
}
MArray* MCore::Array::New(const MClass* elementKlass, int32 length)
{
return nullptr;
}
void MCore::Array::Free(const MArray* array)
{
}
MClass* MCore::Array::GetClass(MClass* elementKlass)
{
return nullptr;

View File

@@ -0,0 +1,82 @@
// Copyright (c) Wojciech Figat. All rights reserved.
#include "Engine/Engine/CommandLine.h"
#include "Engine/Core/Types/StringView.h"
#include "Engine/Core/Collections/Array.h"
#include <ThirdParty/catch2/catch.hpp>
TEST_CASE("CommandLine")
{
SECTION("Test Argument Parser")
{
SECTION("Single quoted word")
{
String input("\"word\"");
Array<StringAnsi> arguments;
CHECK(!CommandLine::ParseArguments(input, arguments));
CHECK(arguments.Count() == 1);
CHECK(arguments[0].Compare(StringAnsi("word")) == 0);
}
SECTION("Quotes at the beginning of the word")
{
String input("start\"word\"");
Array<StringAnsi> arguments;
CHECK(!CommandLine::ParseArguments(input, arguments));
CHECK(arguments.Count() == 1);
CHECK(arguments[0].Compare(StringAnsi("start\"word\"")) == 0);
}
SECTION("Quotes in the middle of the word")
{
String input("start\"word\"end");
Array<StringAnsi> arguments;
CHECK(!CommandLine::ParseArguments(input, arguments));
CHECK(arguments.Count() == 1);
CHECK(arguments[0].Compare(StringAnsi("start\"word\"end")) == 0);
}
SECTION("Quotes at the end of the word")
{
String input("\"word\"end");
Array<StringAnsi> arguments;
CHECK(!CommandLine::ParseArguments(input, arguments));
CHECK(arguments.Count() == 1);
CHECK(arguments[0].Compare(StringAnsi("\"word\"end")) == 0);
}
SECTION("Multiple words")
{
String input("The quick brown fox");
Array<StringAnsi> arguments;
CHECK(!CommandLine::ParseArguments(input, arguments));
CHECK(arguments.Count() == 4);
CHECK(arguments[0].Compare(StringAnsi("The")) == 0);
CHECK(arguments[1].Compare(StringAnsi("quick")) == 0);
CHECK(arguments[2].Compare(StringAnsi("brown")) == 0);
CHECK(arguments[3].Compare(StringAnsi("fox")) == 0);
}
SECTION("Multiple words with quotes")
{
String input("The \"quick brown fox\" jumps over the \"lazy\" dog");
Array<StringAnsi> arguments;
CHECK(!CommandLine::ParseArguments(input, arguments));
CHECK(arguments.Count() == 7);
CHECK(arguments[0].Compare(StringAnsi("The")) == 0);
CHECK(arguments[1].Compare(StringAnsi("quick brown fox")) == 0);
CHECK(arguments[2].Compare(StringAnsi("jumps")) == 0);
CHECK(arguments[3].Compare(StringAnsi("over")) == 0);
CHECK(arguments[4].Compare(StringAnsi("the")) == 0);
CHECK(arguments[5].Compare(StringAnsi("lazy")) == 0);
CHECK(arguments[6].Compare(StringAnsi("dog")) == 0);
}
SECTION("Flax.Build sample parameters")
{
String input("-log -mutex -workspace=\"C:\\path with spaces/to/FlaxEngine/\" -configuration=Debug -hotreload=\".HotReload.1\"");
Array<StringAnsi> arguments;
CHECK(!CommandLine::ParseArguments(input, arguments));
CHECK(arguments.Count() == 5);
CHECK(arguments[0].Compare(StringAnsi("-log")) == 0);
CHECK(arguments[1].Compare(StringAnsi("-mutex")) == 0);
CHECK(arguments[2].Compare(StringAnsi("-workspace=\"C:\\path with spaces/to/FlaxEngine/\"")) == 0);
CHECK(arguments[3].Compare(StringAnsi("-configuration=Debug")) == 0);
CHECK(arguments[4].Compare(StringAnsi("-hotreload=\".HotReload.1\"")) == 0);
}
}
}

View File

@@ -38,7 +38,7 @@ TEST_CASE("Scripting")
CHECK(method);
MObject* result = method->Invoke(nullptr, nullptr, nullptr);
CHECK(result);
int32 resultValue = MUtils::Unbox<int32>(result);
int32 resultValue = MUtils::Unbox<int32>(result, true);
CHECK(resultValue == 0);
}

View File

@@ -745,7 +745,7 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value)
StringAsANSI<40> fieldNameAnsi(*fieldName, fieldName.Length());
auto field = mclass->GetField(fieldNameAnsi.Get());
if (field)
value = MUtils::UnboxVariant(field->GetValueBoxed(instance));
value = MUtils::UnboxVariant(field->GetValueBoxed(instance), true);
break;
}
}

View File

@@ -633,8 +633,62 @@ namespace Flax.Build.Bindings
returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemArrayMarshaller))";
else if (returnValueType == "object[]")
returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemObjectArrayMarshaller))";
else if (functionInfo.ReturnType.IsArrayOrSpan || returnNativeType == "Array")
{
string unmanagedType = "";
if (functionInfo.ReturnType.GenericArgs != null && functionInfo.ReturnType.GenericArgs[0].IsPod(buildData, caller))
{
switch (functionInfo.ReturnType.GenericArgs[0].Type)
{
case "bool": unmanagedType = "U1"; break;
case "byte": unmanagedType = "U1"; break;
case "sbyte": unmanagedType = "I1"; break;
case "char": unmanagedType = "I2"; break;
case "short": case "int16": unmanagedType = "I2"; break;
case "ushort": case "uint16": unmanagedType = "U2"; break;
case "int": case "int32": unmanagedType = "I4"; break;
case "uint": case "uint32": unmanagedType = "U4"; break;
case "long": case "int64": unmanagedType = "I8"; break;
case "ulong": case "uint64": unmanagedType = "U8"; break;
case "float": unmanagedType = "R4"; break;
case "double": unmanagedType = "R8"; break;
case "Float2":
case "Double2":
case "Vector2":
case "Float3":
case "Double3":
case "Vector3":
case "Float4":
case "Double4":
case "Vector4":
case "Color":
case "Color32":
case "Guid":
unmanagedType = "Any"; // FIXME
break;
default:
unmanagedType = "Any";
//Log.Warning($"unknown type: '{parameterInfo.Type.GenericArgs[0].Type}'");
break;
}
}
if (!string.IsNullOrEmpty(unmanagedType))
{
string arraySubType = "";
if (unmanagedType != "Any")
arraySubType = $"ArraySubType = UnmanagedType.{unmanagedType}, ";
returnMarshalType = $"MarshalAs(UnmanagedType.LPArray, {arraySubType}SizeParamIndex = {(!functionInfo.IsStatic ? 1 : 0) + functionInfo.Parameters.Count + (functionInfo.Glue.CustomParameters.FindIndex(x => x.Name == $"__returnCount"))})";
}
else
returnMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"__returnCount\")";
}
else if (functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer" || functionInfo.ReturnType.Type == "BytesContainer" || returnNativeType == "Array")
{
//[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 8)]
returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = nameof(__returnCount))";
}
else if (functionInfo.ReturnType.Type == "Dictionary")
returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)";
else if (returnValueType == "byte[]")
@@ -686,11 +740,56 @@ namespace Flax.Build.Bindings
parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemArrayMarshaller))";
else if (nativeType == "object[]")
parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemObjectArrayMarshaller))";
else if (parameterInfo.Type.Type == "Array" && parameterInfo.Type.GenericArgs.Count > 0 && parameterInfo.Type.GenericArgs[0].Type == "bool")
parameterMarshalType = $"MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = {(!functionInfo.IsStatic ? 1 : 0) + functionInfo.Parameters.Count + (functionInfo.Glue.CustomParameters.FindIndex(x => x.Name == $"__{parameterInfo.Name}Count"))})";
else if (parameterInfo.Type.Type == "Array" || parameterInfo.Type.Type == "Span" || parameterInfo.Type.Type == "DataContainer" || parameterInfo.Type.Type == "BytesContainer" || nativeType == "Array")
else if (parameterInfo.Type.IsArrayOrSpan || nativeType == "Array")
{
parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"__{parameterInfo.Name}Count\")";
string unmanagedType = "";
if (parameterInfo.Type.GenericArgs != null && parameterInfo.Type.GenericArgs[0].IsPod(buildData, caller))
{
switch (parameterInfo.Type.GenericArgs[0].Type)
{
case "bool": unmanagedType = "U1"; break;
case "byte": unmanagedType = "U1"; break;
case "sbyte": unmanagedType = "I1"; break;
case "char": unmanagedType = "I2"; break;
case "short": case "int16": unmanagedType = "I2"; break;
case "ushort": case "uint16": unmanagedType = "U2"; break;
case "int": case "int32": unmanagedType = "I4"; break;
case "uint": case "uint32": unmanagedType = "U4"; break;
case "long": case "int64": unmanagedType = "I8"; break;
case "ulong": case "uint64": unmanagedType = "U8"; break;
case "float": unmanagedType = "R4"; break;
case "double": unmanagedType = "R8"; break;
case "Float2":
case "Double2":
case "Vector2":
case "Float3":
case "Double3":
case "Vector3":
case "Float4":
case "Double4":
case "Vector4":
case "Color":
case "Color32":
case "Guid":
unmanagedType = "Any"; // FIXME
break;
default:
unmanagedType = "Any";
//Log.Warning($"unknown type: '{parameterInfo.Type.GenericArgs[0].Type}'");
break;
}
}
if (!string.IsNullOrEmpty(unmanagedType))
{
string arraySubType = "";
if (unmanagedType != "Any")
arraySubType = $"ArraySubType = UnmanagedType.{unmanagedType}, ";
parameterMarshalType = $"MarshalAs(UnmanagedType.LPArray, {arraySubType}SizeParamIndex = {(!functionInfo.IsStatic ? 1 : 0) + functionInfo.Parameters.Count + (functionInfo.Glue.CustomParameters.FindIndex(x => x.Name == $"__{parameterInfo.Name}Count"))})";
}
else
parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"__{parameterInfo.Name}Count\")";
if (!parameterInfo.IsOut && !parameterInfo.IsRef)
parameterMarshalType += ", In"; // The usage of 'LibraryImportAttribute' does not follow recommendations. It is recommended to use explicit '[In]' and '[Out]' attributes on array parameters.
}
@@ -711,7 +810,12 @@ namespace Flax.Build.Bindings
if (parameterInfo.IsOut)
contents.Append("out ");
else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller))
contents.Append("ref ");
{
if (parameterInfo.IsConst || parameterInfo.Type.IsConst)
contents.Append("in ");
else
contents.Append("ref ");
}
// Out parameters that need additional converting will be converted at the native side (eg. object reference)
if (parameterInfo.IsOut && !string.IsNullOrEmpty(GenerateCSharpManagedToNativeConverter(buildData, parameterInfo.Type, caller)))
@@ -736,7 +840,7 @@ namespace Flax.Build.Bindings
if (parameterInfo.IsOut && parameterInfo.DefaultValue == "var __resultAsRef")
{
// TODO: make this code shared with MarshalUsing selection from the above
if (parameterInfo.Type.Type == "Array" || parameterInfo.Type.Type == "Span" || parameterInfo.Type.Type == "DataContainer" || parameterInfo.Type.Type == "BytesContainer")
if (parameterInfo.Type.IsArrayOrSpan)
parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"{parameterInfo.Name}Count\")";
else if (parameterInfo.Type.Type == "Dictionary")
parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)";
@@ -754,7 +858,12 @@ namespace Flax.Build.Bindings
if (parameterInfo.IsOut)
contents.Append("out ");
else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller))
contents.Append("ref ");
{
if (parameterInfo.IsConst || parameterInfo.Type.IsConst)
contents.Append("in ");
else
contents.Append("/*xcvg1*/ ref ");
}
contents.Append(nativeType);
contents.Append(' ');
contents.Append(parameterInfo.Name);
@@ -802,7 +911,17 @@ namespace Flax.Build.Bindings
if (parameterInfo.IsOut)
contents.Append("out ");
else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller))
contents.Append("ref ");
{
if (isSetter || parameterInfo.Type.IsConst)
contents.Append("in ");
else
{
//CanUpdate(BehaviorUpdateContext
if (parameterInfo.Name == "context" && functionInfo.Name == "CanUpdate")
separator = separator;
contents.Append("/*xcvg2*/ ref ");
}
}
var convertFunc = GenerateCSharpManagedToNativeConverter(buildData, parameterInfo.Type, caller);
var paramName = isSetter ? "value" : parameterInfo.Name;
@@ -836,7 +955,12 @@ namespace Flax.Build.Bindings
if (parameterInfo.IsOut)
contents.Append("out ");
else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller))
contents.Append("ref ");
{
if (parameterInfo.IsConst ||parameterInfo.Type.IsConst)
contents.Append("in ");
else
contents.Append("ref ");
}
// Pass value
contents.Append(parameterInfo.DefaultValue);
@@ -1369,7 +1493,12 @@ namespace Flax.Build.Bindings
if (parameterInfo.IsOut)
contents.Append("out ");
else if (parameterInfo.IsRef)
contents.Append("ref ");
{
if (parameterInfo.IsConst || parameterInfo.Type.IsConst)
contents.Append("in ");
else
contents.Append("/*faffaf1*/ ref ");
}
else if (parameterInfo.IsThis)
contents.Append("this ");
else if (parameterInfo.IsParams)
@@ -1431,7 +1560,7 @@ namespace Flax.Build.Bindings
if (parameterInfo.IsOut)
contents.Append("out ");
else if (parameterInfo.IsRef)
contents.Append("ref ");
contents.Append("/*faffaf2*/ ref ");
else if (parameterInfo.IsThis)
contents.Append("this ");
else if (parameterInfo.IsParams)
@@ -1522,8 +1651,8 @@ namespace Flax.Build.Bindings
public static class ManagedToNative
{
public static {{classInfo.Name}} ConvertToManaged(IntPtr unmanaged) => Unsafe.As<{{classInfo.Name}}>(ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(unmanaged));
public static IntPtr ConvertToUnmanaged({{classInfo.Name}} managed) => managed != null ? ManagedHandle.ToIntPtr(managed, GCHandleType.Weak) : IntPtr.Zero;
public static void Free(IntPtr unmanaged) {}
public static IntPtr ConvertToUnmanaged({{classInfo.Name}} managed) => managed != null ? ManagedHandle.ToIntPtr(managed/*, GCHandleType.Weak*/) : IntPtr.Zero;
public static void Free(IntPtr unmanaged) => ManagedHandleMarshaller.Free(unmanaged);
}
#if FLAX_EDITOR
[HideInEditor]
@@ -1708,7 +1837,7 @@ namespace Flax.Build.Bindings
{
var managedType = GenerateCSharpNativeToManaged(buildData, marshalType.GenericArgs[0], structureInfo);
toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{managedType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;");
toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;");
toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}/*, GCHandleType.Weak*/) : IntPtr.Zero; // plaa 1");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}");
// Permanent ScriptingObject handle is passed from native side, do not release it
@@ -1717,7 +1846,7 @@ namespace Flax.Build.Bindings
else if (marshalType.Type == "ScriptingObject")
{
toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<FlaxEngine.Object>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;");
toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;");
toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}/*, GCHandleType.Weak*/) : IntPtr.Zero; // plaa 2");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}");
// Permanent ScriptingObject handle is passed from native side, do not release it
@@ -1726,7 +1855,7 @@ namespace Flax.Build.Bindings
else if (marshalType.IsPtr && originalType != "IntPtr" && !originalType.EndsWith("*"))
{
toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;");
toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;");
toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}/*, GCHandleType.Weak*/) : IntPtr.Zero; // plaa 3");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}");
// Permanent ScriptingObject handle is passed from native side, do not release it
@@ -1735,7 +1864,7 @@ namespace Flax.Build.Bindings
else if (marshalType.Type == "Dictionary")
{
toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;");
toNativeContent.AppendLine($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak);");
toNativeContent.AppendLine($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}/*, GCHandleType.Weak*/);");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}");
freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}");
}
@@ -1757,7 +1886,7 @@ namespace Flax.Build.Bindings
{
// Array elements passed as GCHandles
toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>(Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)) : null;");
toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(NativeInterop.ManagedArrayToGCHandleWrappedArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero;");
toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(NativeInterop.ManagedArrayToGCHandleWrappedArray(managed.{fieldInfo.Name})/*, GCHandleType.Weak*/) : IntPtr.Zero;");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<IntPtr> ptrs = (Unsafe.As<ManagedArray>(handle.Target)).ToSpan<IntPtr>(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(ptr).Free(); }} }} (Unsafe.As<ManagedArray>(handle.Target)).Free(); handle.Free(); }}");
// Permanent ScriptingObject handle is passed from native side, do not release it
@@ -1775,7 +1904,7 @@ namespace Flax.Build.Bindings
else if (marshalType.Type == "Version")
{
toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;");
toNativeContent.AppendLine($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak);");
toNativeContent.AppendLine($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}/*, GCHandleType.Weak*/);");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}");
freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}");
}
@@ -1824,66 +1953,77 @@ namespace Flax.Build.Bindings
toManagedContent.Append("return managed;");
}
string hideInEditorAttribute = (buildData.Target != null & buildData.Target.IsEditor) ? "[HideInEditor]" : "";
string marshalManagedType = structureInfo.Name;
string marshalNativeType = $"{structureInfo.Name}Internal";
contents.AppendLine(string.Join(Environment.NewLine + indent, (indent + $$"""
/// <summary>
/// Marshaller for type <see cref="{{structureInfo.Name}}"/>.
/// Marshaller for type <see cref="{{marshalManagedType}}"/>.
/// </summary>
{{InsertHideInEditorSection()}}
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedIn, typeof({{marshallerFullName}}.ManagedToNative))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedOut, typeof({{marshallerFullName}}.ManagedToNative))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementIn, typeof({{marshallerFullName}}.ManagedToNative))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedOut, typeof({{marshallerFullName}}.NativeToManaged))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedIn, typeof({{marshallerFullName}}.NativeToManaged))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementOut, typeof({{marshallerFullName}}.NativeToManaged))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedRef, typeof({{marshallerFullName}}.Bidirectional))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedRef, typeof({{marshallerFullName}}.Bidirectional))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementRef, typeof({{marshallerFullName}}))]
{{hideInEditorAttribute}}
[CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ManagedToUnmanagedIn, typeof({{marshallerFullName}}.ManagedToNative))]
[CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.UnmanagedToManagedOut, typeof({{marshallerFullName}}.ManagedToNative))]
[CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ElementIn, typeof({{marshallerFullName}}.ManagedToNative))]
[CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ManagedToUnmanagedOut, typeof({{marshallerFullName}}.NativeToManaged))]
[CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.UnmanagedToManagedIn, typeof({{marshallerFullName}}.NativeToManaged))]
[CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ElementOut, typeof({{marshallerFullName}}.NativeToManaged))]
[CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ManagedToUnmanagedRef, typeof({{marshallerFullName}}.Bidirectional))]
[CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.UnmanagedToManagedRef, typeof({{marshallerFullName}}.Bidirectional))]
[CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ElementRef, typeof({{marshallerFullName}}))]
{{GenerateCSharpAccessLevel(structureInfo.Access)}}static unsafe class {{marshallerName}}
{
#pragma warning disable 1591
#pragma warning disable 618
{{structContents.Replace("\n", Environment.NewLine + " ").ToString().TrimEnd()}}
{{InsertHideInEditorSection()}}
{{hideInEditorAttribute}}
public static class NativeToManaged
{
public static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => {{marshallerFullName}}.ToManaged(unmanaged);
public static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => {{marshallerFullName}}.ToNative(managed);
public static void Free({{structureInfo.Name}}Internal unmanaged)
public static {{marshalManagedType}} ConvertToManaged({{marshalNativeType}} unmanaged) => {{marshallerFullName}}.ToManaged(unmanaged);
public static {{marshalNativeType}} ConvertToUnmanaged({{marshalManagedType}} managed) => {{marshallerFullName}}.ToNative(managed);
public static void Free({{marshalNativeType}} unmanaged)
{
{{freeContents2.Replace("\n", Environment.NewLine + " ").ToString().TrimEnd()}}
}
}
{{InsertHideInEditorSection()}}
{{hideInEditorAttribute}}
public static class ManagedToNative
{
public static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => {{marshallerFullName}}.ToManaged(unmanaged);
public static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => {{marshallerFullName}}.ToNative(managed);
public static void Free({{structureInfo.Name}}Internal unmanaged) => {{marshallerFullName}}.Free(unmanaged);
public static {{marshalManagedType}} ConvertToManaged({{marshalNativeType}} unmanaged) => {{marshallerFullName}}.ToManaged(unmanaged);
public static {{marshalNativeType}} ConvertToUnmanaged({{marshalManagedType}} managed) => {{marshallerFullName}}.ToNative(managed);
public static void Free({{marshalNativeType}} unmanaged) => {{marshallerFullName}}.Free(unmanaged);
}
{{InsertHideInEditorSection()}}
public struct Bidirectional
{{hideInEditorAttribute}}
public ref struct Bidirectional
{
{{structureInfo.Name}} managed;
{{structureInfo.Name}}Internal unmanaged;
public void FromManaged({{structureInfo.Name}} managed) => this.managed = managed;
public {{structureInfo.Name}}Internal ToUnmanaged() { unmanaged = {{marshallerFullName}}.ToNative(managed); return unmanaged; }
public void FromUnmanaged({{structureInfo.Name}}Internal unmanaged) => this.unmanaged = unmanaged;
public {{structureInfo.Name}} ToManaged() { managed = {{marshallerFullName}}.ToManaged(unmanaged); return managed; }
public void Free() => NativeToManaged.Free(unmanaged);
{{marshalManagedType}} managed;
{{marshalNativeType}} unmanaged;
public void FromManaged({{marshalManagedType}} managed) => this.managed = managed;
public {{marshalNativeType}} ToUnmanaged() { unmanaged = {{marshallerFullName}}.ToNative(managed); return unmanaged; }
public void FromUnmanaged({{marshalNativeType}} unmanaged)
{
if (!unmanaged.Equals(this.unmanaged))
{{marshallerName}}.Free(this.unmanaged); // Release temporary handles before replacing them with permanent handles
this.unmanaged = unmanaged;
}
public {{marshalManagedType}} ToManaged() { managed = {{marshallerFullName}}.ToManaged(unmanaged); return managed; }
public void Free()
{
NativeToManaged.Free(unmanaged);
}
}
internal static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => ToManaged(unmanaged);
internal static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => ToNative(managed);
internal static void Free({{structureInfo.Name}}Internal unmanaged)
internal static {{marshalManagedType}} ConvertToManaged({{marshalNativeType}} unmanaged) => ToManaged(unmanaged);
internal static {{marshalNativeType}} ConvertToUnmanaged({{marshalManagedType}} managed) => ToNative(managed);
internal static void Free({{marshalNativeType}} unmanaged)
{
{{freeContents.Replace("\n", Environment.NewLine + " ").ToString().TrimEnd()}}
}
internal static {{structureInfo.Name}} ToManaged({{structureInfo.Name}}Internal unmanaged)
internal static {{marshalManagedType}} ToManaged({{marshalNativeType}} unmanaged)
{
{{toManagedContent.Replace("\n", Environment.NewLine + " ").ToString().TrimEnd()}}
}
internal static {{structureInfo.Name}}Internal ToNative({{structureInfo.Name}} managed)
internal static {{marshalNativeType}} ToNative({{marshalManagedType}} managed)
{
{{toNativeContent.Replace("\n", Environment.NewLine + " ").ToString().TrimEnd()}}
}
@@ -1892,13 +2032,6 @@ namespace Flax.Build.Bindings
}
""").Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)));
string InsertHideInEditorSection()
{
return (buildData.Target != null & buildData.Target.IsEditor) ? $$"""
[HideInEditor]
""" : "";
}
PutStringBuilder(toManagedContent);
PutStringBuilder(toNativeContent);
PutStringBuilder(freeContents);
@@ -2231,7 +2364,7 @@ namespace Flax.Build.Bindings
if (parameterInfo.IsOut)
contents.Append("out ");
else if (parameterInfo.IsRef)
contents.Append("ref ");
contents.Append("/*faffaf3*/ ref ");
else if (parameterInfo.IsThis)
contents.Append("this ");
else if (parameterInfo.IsParams)

View File

@@ -119,7 +119,8 @@ namespace Flax.Build.Bindings
// Pass as pointer to local variable converted for managed runtime
if (paramType.IsPtr)
result = string.Format(nativeToManaged, '*' + paramName);
contents.Append($" auto __param_{paramName} = {result};").AppendLine();
contents.Append($" auto __param_orig_{paramName} = {result};").AppendLine();
contents.Append($" auto __param_{paramName} = __param_orig_{paramName};").AppendLine();
result = $"&__param_{paramName}";
useLocalVar = true;
}
@@ -387,17 +388,7 @@ namespace Flax.Build.Bindings
// Find namespace for this type to build a fullname
if (apiType != null)
{
var e = apiType.Parent;
while (!(e is FileInfo))
{
e = e.Parent;
}
if (e is FileInfo fileInfo && !managedType.StartsWith(fileInfo.Namespace))
{
managedType = fileInfo.Namespace + '.' + managedType.Replace(".", "+");
}
}
managedType = apiType.Namespace + '.' + managedType.Replace(".", "+");
// Use runtime lookup from fullname of the C# class
return "Scripting::FindClass(\"" + managedType + "\")";
@@ -540,14 +531,25 @@ namespace Flax.Build.Bindings
if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null)
{
var arrayTypeInfo = typeInfo.GenericArgs[0];
#if USE_NETCORE
// Boolean arrays does not support custom marshalling for some unknown reason
if (arrayTypeInfo.Type == "bool")
{
type = "bool*";
return "MUtils::ToBoolArray({0})";
}
var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller);
#if USE_NETCORE
if (arrayApiType?.IsPod ?? false)
{
if (functionInfo == null)
{
// Storage type should be wrapped
type = $"NativeArray<{arrayApiType.FullNameNative}>";
return $"MUtils::ToNativeArrayWrapper<{arrayApiType.FullNameNative}>({{0}})/*sahho*/";
}
else
{
//type = arrayApiType.FullNameNative + "*/*wahho*/";
type = $"NativeArray<{arrayApiType.FullNameNative}>/*tahho*/";
//if (arrayTypeInfo.Type == "bool")
// return "MUtils::ToBoolArray({0})/*bahho*/";
return "MUtils::ToNativeArrayWrapper({0})/*nahhoa7eatra*/";
}
}
#endif
type = "MArray*";
if (arrayApiType != null && arrayApiType.MarshalAs != null)
@@ -563,12 +565,14 @@ namespace Flax.Build.Bindings
return "MUtils::ToArray({0}, " + GenerateCppGetMClass(buildData, arrayTypeInfo, caller, functionInfo) + ")";
}
#if !USE_NETCORE
// Span
if (typeInfo.Type == "Span" && typeInfo.GenericArgs != null)
{
type = "MonoArray*";
return "MUtils::Span({0}, " + GenerateCppGetMClass(buildData, typeInfo.GenericArgs[0], caller, functionInfo) + ")";
}
#endif
// BytesContainer
if (typeInfo.Type == "BytesContainer" && typeInfo.GenericArgs == null)
@@ -599,7 +603,7 @@ namespace Flax.Build.Bindings
{
CppIncludeFiles.Add("Engine/Scripting/Internal/ManagedBitArray.h");
#if USE_NETCORE
// Boolean arrays does not support custom marshalling for some unknown reason
// Special case for copying bits to bytes
type = "bool*";
return "MUtils::ToBoolArray({0})";
#else
@@ -751,6 +755,7 @@ namespace Flax.Build.Bindings
// Array
if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null)
{
needLocalVariable = true;
var arrayTypeInfo = typeInfo.GenericArgs[0];
var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller);
if (arrayApiType != null && arrayApiType.MarshalAs != null)
@@ -770,6 +775,16 @@ namespace Flax.Build.Bindings
genericArgs += ", " + typeInfo.GenericArgs[1];
result = $"Array<{genericArgs}>({result})";
}
else if (arrayApiType?.IsPod ?? false)
{
type = arrayApiType.FullNameNative + '*';
result = $"Array<{genericArgs}>(({arrayApiType.FullNameNative}*){{0}}, {{1}})/*xviox1 {arrayTypeInfo.Type}*/";
}
else if (arrayApiType?.Name == "bool")
{
type = "bool*";
result = "Array<bool>({0}, {1})/*xviox2*/\"";
}
return result;
}
@@ -779,11 +794,20 @@ namespace Flax.Build.Bindings
type = "MArray*";
// Scripting Objects pointers has to be converted from managed object pointer into native object pointer to use Array converted for this
var t = FindApiTypeInfo(buildData, typeInfo.GenericArgs[0], caller);
if (typeInfo.GenericArgs[0].IsPtr && t != null && t.IsScriptingObject)
var arrayApiType = FindApiTypeInfo(buildData, typeInfo.GenericArgs[0], caller);
if (typeInfo.GenericArgs[0].IsPtr && arrayApiType != null && arrayApiType.IsScriptingObject)
{
// TODO: array needs to be freed
return "MUtils::ToSpan<" + typeInfo.GenericArgs[0] + ">(" + "MUtils::ToArray<" + typeInfo.GenericArgs[0] + ">({0}))";
}
else if (arrayApiType?.IsPod ?? false)
{
type = arrayApiType.FullNameNative + '*';
var nativeName = arrayApiType.FullNameNative;
if (typeInfo.GenericArgs[0].IsConst)
nativeName = "const " + nativeName;
return $"Span<{nativeName}>({{0}}, {{1}})/*spantag */";
}
return "MUtils::ToSpan<" + typeInfo.GenericArgs[0] + ">({0})";
}
@@ -998,6 +1022,11 @@ namespace Flax.Build.Bindings
}
#endif
if (functionInfo.UniqueName == "SetView" && callerName == "SceneRenderTaskInternal")
callerName = callerName;
if (functionInfo.UniqueName == "SetDriveControl")
callerName = callerName;
// Setup function binding glue to ensure that wrapper method signature matches for C++ and C#
functionInfo.Glue = new FunctionInfo.GlueInfo
{
@@ -1155,6 +1184,7 @@ namespace Flax.Build.Bindings
},
IsOut = parameterInfo.IsOut,
IsRef = isRefOut || parameterInfo.Type.IsRef,
IsConst = true,
});
}
#endif
@@ -1244,8 +1274,16 @@ namespace Flax.Build.Bindings
callParams += ", ";
separator = true;
var name = parameterInfo.Name;
var countParamName = $"__{parameterInfo.Name}Count";
if (CppParamsThatNeedConversion[i] && (!FindApiTypeInfo(buildData, parameterInfo.Type, caller)?.IsStruct ?? false))
{
name = '*' + name;
countParamName = '*' + countParamName + "/*plop*/";
}
else if (parameterInfo.Type.IsRef)
{
countParamName = '*' + countParamName + "/*plipa*/";
}
string param = string.Empty;
if (string.IsNullOrWhiteSpace(CppParamsWrappersCache[i]))
@@ -1258,7 +1296,7 @@ namespace Flax.Build.Bindings
else
{
// Convert value
param += string.Format(CppParamsWrappersCache[i], name);
param += string.Format(CppParamsWrappersCache[i], name, countParamName);
}
// Special case for output result parameters that needs additional converting from native to managed format (such as non-POD structures or output array parameter)
@@ -1293,11 +1331,21 @@ namespace Flax.Build.Bindings
callParams += parameterInfo.Name;
callParams += "Temp";
}
// Instruct for more optoimized value move operation
// Instruct for more optimized value move operation
else if (parameterInfo.Type.IsMoveRef)
{
callParams += $"MoveTemp({param})";
}
else if (parameterInfo.Type.IsRef && !parameterInfo.Type.IsConst)
{
// Non-const lvalue reference parameters needs to be passed via temporary value
if (parameterInfo.IsOut || parameterInfo.IsRef)
contents.Append(indent).AppendFormat("{2}& {0}Temp = {1};", parameterInfo.Name, param, parameterInfo.Type.ToString(false)).AppendLine();
else
contents.Append(indent).AppendFormat("{2} {0}Temp = {1};", parameterInfo.Name, param, parameterInfo.Type.ToString(false)).AppendLine();
callParams += parameterInfo.Name;
callParams += "Temp";
}
else
{
callParams += param;
@@ -1332,6 +1380,25 @@ namespace Flax.Build.Bindings
contents.Append(';');
contents.AppendLine();
#if false
for (var i = 0; i < functionInfo.Parameters.Count; i++)
{
var parameterInfo = functionInfo.Parameters[i];
if (CppParamsThatNeedConversion[i] || CppParamsThatNeedLocalVariable[i])
{
var apiType = FindApiTypeInfo(buildData, parameterInfo.Type, caller);
if (parameterInfo.Type.Type == "Array")
{
contents.Append(indent).AppendLine($"/* converthing: {parameterInfo.Name} Type == {parameterInfo.Type.Type} ({parameterInfo.Type.ToString()}) */");
contents.Append(indent).AppendLine($"/* converthing2: {parameterInfo.Name} IsArray == {parameterInfo.Type.IsArray} */");
contents.Append(indent).AppendLine($"/* converthing3: {parameterInfo.Name} apitype == {apiType?.Name ?? ""} */");
//contents.Append(indent).AppendLine($"MUtils::FreeManagedArray<{parameterInfo.Type.GenericArgs[0].ToString(false)}>({parameterInfo.Name}Temp);");
}
}
}
#endif
// Convert special parameters back to managed world
if (!useInlinedReturn)
{
@@ -1343,6 +1410,7 @@ namespace Flax.Build.Bindings
if (CppParamsThatNeedConversion[i])
{
var value = string.Format(CppParamsThatNeedConversionWrappers[i], parameterInfo.Name + "Temp");
var elementType = parameterInfo.Type.GenericArgs != null ? FindApiTypeInfo(buildData, parameterInfo.Type.GenericArgs[0], caller) : null;
// MObject* parameters returned by reference need write barrier for GC
if (parameterInfo.IsOut)
@@ -1352,14 +1420,30 @@ namespace Flax.Build.Bindings
{
if (apiType.IsClass)
{
contents.Append(indent).AppendFormat("MCore::GC::WriteRef({0}, (MObject*){1});", parameterInfo.Name, value).AppendLine();
#if USE_NETCORE
if (parameterInfo.Type.Type == "Array")
{
if (elementType?.IsPod ?? false)
{
contents.Append(indent).Append($"// isgigs1a").AppendLine();
contents.Append(indent).Append($"*{parameterInfo.Name} = ({parameterInfo.Type.GenericArgs[0]}*)MCore::GC::AllocateMemory(sizeof({parameterInfo.Type.GenericArgs[0]}) * {parameterInfo.Name}Temp.Count());").AppendLine();
contents.Append(indent).Append($"Platform::MemoryCopy(*{parameterInfo.Name}, {parameterInfo.Name}Temp.Get(), sizeof({parameterInfo.Type.GenericArgs[0]}) * {parameterInfo.Name}Temp.Count());").AppendLine();
}
else
{
contents.Append(indent).Append($"// isgigs1b").AppendLine();
contents.Append(indent).AppendFormat("MCore::GC::WriteRef({0}, (MObject*){1});", parameterInfo.Name, value).AppendLine();
}
// Array marshallers need to know amount of items written in the buffer
contents.Append(indent).AppendFormat("*__{0}Count = {1}.Count();", parameterInfo.Name, parameterInfo.Name + "Temp").AppendLine();
}
else
#endif
{
contents.Append(indent).Append($"// isgigs2").AppendLine();
contents.Append(indent).AppendFormat("MCore::GC::WriteRef({0}, (MObject*){1});", parameterInfo.Name, value).AppendLine();
}
continue;
}
if (apiType.IsStruct && !apiType.IsPod)
@@ -1375,22 +1459,30 @@ namespace Flax.Build.Bindings
// BytesContainer
if (parameterInfo.Type.Type == "BytesContainer" && parameterInfo.Type.GenericArgs == null)
{
contents.Append(indent).Append($"// hshsf").AppendLine();
contents.Append(indent).AppendFormat("MCore::GC::WriteRef({0}, (MObject*){1});", parameterInfo.Name, value).AppendLine();
// Array marshallers need to know amount of items written in the buffer
contents.Append(indent).AppendFormat("*__{0}Count = {1}.Length();", parameterInfo.Name, parameterInfo.Name + "Temp").AppendLine();
continue;
//contents.Append(indent).AppendFormat("*__{0}Count = {1}.Length();", parameterInfo.Name, parameterInfo.Name + "Temp").AppendLine();
//continue;
}
throw new Exception($"Unsupported type of parameter '{parameterInfo}' in method '{functionInfo}' to be passed using 'out'");
else
throw new Exception($"Unsupported type of parameter '{parameterInfo}' in method '{functionInfo}' to be passed using 'out'");
}
}
contents.Append(indent).AppendFormat("*{0} = {1};", parameterInfo.Name, value).AppendLine();
else if (parameterInfo.Type.Type == "Array" && (elementType?.IsPod ?? false))
contents.Append(indent).AppendFormat("*{0} = {1}.data;", parameterInfo.Name, value).AppendLine();
else
contents.Append(indent).AppendFormat("*{0} = {1};", parameterInfo.Name, value).AppendLine();
#if USE_NETCORE
if (parameterInfo.Type.Type == "Array")
if (parameterInfo.Type.Type == "Array" || (parameterInfo.Type.Type == "BytesContainer" && parameterInfo.Type.GenericArgs == null))
{
contents.Append(indent).Append("// hshshj").AppendLine();
// Array marshallers need to know amount of items written in the buffer
contents.Append(indent).AppendFormat("*__{0}Count = {1}.Count();", parameterInfo.Name, parameterInfo.Name + "Temp").AppendLine();
if (parameterInfo.Type.Type is "Span" or "BytesContainer")
contents.Append(indent).AppendFormat("*__{0}Count = {1}.Length();", parameterInfo.Name, parameterInfo.Name + "Temp").AppendLine();
else
contents.Append(indent).AppendFormat("*__{0}Count = {1}.Count();", parameterInfo.Name, parameterInfo.Name + "Temp").AppendLine();
}
#endif
}
@@ -1550,7 +1642,8 @@ namespace Flax.Build.Bindings
if (paramIsRef)
{
// Pass as pointer to value when using ref/out parameter
contents.Append($" auto __param_{parameterInfo.Name} = {paramValue};").AppendLine();
contents.Append($" auto __param_orig_{parameterInfo.Name} = {paramValue};").AppendLine();
contents.Append($" auto __param_{parameterInfo.Name} = __param_orig_{parameterInfo.Name};").AppendLine();
paramValue = $"&__param_{parameterInfo.Name}";
useLocalVar = true;
}
@@ -1630,6 +1723,124 @@ namespace Flax.Build.Bindings
else if (!passAsParamPtr)
paramValue = '*' + paramValue;
contents.Append($" {parameterInfo.Name} = {paramValue};").AppendLine();
// Release temporary GCHandles
/*if (passAsParamPtr)
{
//contents.Append($" if (params[{i}] != (void*)&__param_{parameterInfo.Name})").AppendLine();
contents.Append($" {{").AppendLine();
contents.Append($" auto __param{i}_handle = *(MGCHandle*)&params[{i}];").AppendLine();
contents.Append($" MCore::GCHandle::Free(__param{i}_handle);").AppendLine();
contents.Append($" }}").AppendLine();
}
else
{
//contents.Append($" if (params[{i}] != (void*)&__param_{parameterInfo.Name})").AppendLine();
contents.Append($" {{").AppendLine();
contents.Append($" auto __param{i}_handle = *(MGCHandle*)&params[{i}];").AppendLine();
contents.Append($" MCore::GCHandle::Free(__param{i}_handle);").AppendLine();
contents.Append($" }}").AppendLine();
}*/
}
}
}
// Release GCHandles of boxed values
for (var i = 0; i < functionInfo.Parameters.Count; i++)
{
var parameterInfo = functionInfo.Parameters[i];
var paramValue = GenerateCppWrapperNativeToBox(buildData, parameterInfo.Type, classInfo, out var apiType, parameterInfo.Name);
var paramIsRef = parameterInfo.IsRef || parameterInfo.IsOut;
if (paramValue.Contains("MUtils::Box<")) // FIXME
{
if (paramIsRef)
{
//contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.ToString(false)}>((MObject*)__param_{parameterInfo.Name});").AppendLine();
/*if (useThunk)
{
// Release the original handle
contents.Append($" if (__param_orig_{parameterInfo.Name} != __param_{parameterInfo.Name}) //asdf1a").AppendLine();
contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.ToString(false)}>((MObject*)__param_{parameterInfo.Name});").AppendLine();
}*/
contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.Type}>((MObject*)__param_{parameterInfo.Name});").AppendLine();
if (useThunk)
{
// Release the original handle
contents.Append($" if (__param_orig_{parameterInfo.Name} != __param_{ parameterInfo.Name})").AppendLine();
contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.Type}>((MObject*)__param_orig_{parameterInfo.Name});").AppendLine();
}
}
else if (apiType != null && !apiType.IsInBuild)
{
// int: ispod, isvaluetype
// vector: ispod, isvaluetype, isstruct
// guid: ispod, isvaluetype, isstruct, isinbuild
contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.Type}>((MObject*)params[{i}]); //asdf1b").AppendLine();
//contents.Append($" auto __param{i}_handle = *(MGCHandle*)&params[{i}]; // asdf1b").AppendLine();
//contents.Append($" ASSERT((((unsigned long long)__param{i}_handle & 0xC000000000000000) >> 62) == 0);").AppendLine();
//contents.Append($" MCore::GCHandle::Free(__param{i}_handle);").AppendLine();
}
else if (apiType != null && !apiType.IsValueType)
{
if (parameterInfo.Type.Type.ToLower().Contains("string"))
apiType = apiType;
contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.Type}>((MObject*)params[{i}]); //asdf1d").AppendLine();
}
else //if (apiType != null)
{
if (parameterInfo.Type.Type.ToLower().Contains("string"))
apiType = apiType;
//contents.Append($" //asdf1c {parameterInfo.Type.Type}").AppendLine();
contents.Append($" auto __param{i}_handle = *(MGCHandle*)&params[{i}]; // asdf1c").AppendLine();
//contents.Append($" ASSERT((((unsigned long long)__param{i}_handle & 0xC000000000000000) >> 62) == 0);").AppendLine();
contents.Append($" MCore::GCHandle::Free(__param{i}_handle);").AppendLine();
}
}
else if (paramValue.Contains("MUtils::ToArray(")) // FIXME
{
var genericType = parameterInfo.Type.GenericArgs[0].ToString(false);
if (paramIsRef)
{
//contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.ToString(false)}>((MObject*)__param_{parameterInfo.Name});").AppendLine();
/*if (useThunk)
{
// Release the original handle
contents.Append($" if (__param_orig_{parameterInfo.Name} != __param_{parameterInfo.Name}) //asdf1a").AppendLine();
contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.ToString(false)}>((MObject*)__param_{parameterInfo.Name});").AppendLine();
}*/
contents.Append($" MUtils::FreeManagedArray<{genericType}>(__param_{parameterInfo.Name}); //fgfgh3").AppendLine();
if (useThunk)
{
// Release the original handle
contents.Append($" if (__param_orig_{parameterInfo.Name} != __param_{ parameterInfo.Name})").AppendLine();
contents.Append($" MUtils::FreeManagedArray<{genericType}>(__param_orig_{parameterInfo.Name}); //fgfgh4").AppendLine();
}
}
else if (apiType != null && !apiType.IsInBuild)
{
// int: ispod, isvaluetype
// vector: ispod, isvaluetype, isstruct
// guid: ispod, isvaluetype, isstruct, isinbuild
contents.Append($" MUtils::FreeManagedArray<{genericType}>((MArray*)params[{i}]); //fgfgh5").AppendLine();
//contents.Append($" auto __param{i}_handle = *(MGCHandle*)&params[{i}]; // asdf1b").AppendLine();
//contents.Append($" ASSERT((((unsigned long long)__param{i}_handle & 0xC000000000000000) >> 62) == 0);").AppendLine();
//contents.Append($" MCore::GCHandle::Free(__param{i}_handle);").AppendLine();
}
else if (apiType != null && !apiType.IsValueType)
{
if (parameterInfo.Type.Type.ToLower().Contains("string"))
apiType = apiType;
contents.Append($" MUtils::FreeManagedArray<{genericType}>((MArray*)params[{i}]); //fgfgh6").AppendLine();
}
else //if (apiType != null)
{
if (parameterInfo.Type.Type.ToLower().Contains("string"))
apiType = apiType;
//contents.Append($" //asdf1c {parameterInfo.Type.Type}").AppendLine();
contents.Append($" auto __param{i}_handle = *(MGCHandle*)&params[{i}]; //fgfgh7").AppendLine();
//contents.Append($" ASSERT((((unsigned long long)__param{i}_handle & 0xC000000000000000) >> 62) == 0);").AppendLine();
contents.Append($" MCore::GCHandle::Free(__param{i}_handle);").AppendLine();
}
}
}
@@ -2085,6 +2296,22 @@ namespace Flax.Build.Bindings
else if (!passAsParamPtr)
paramValue = '*' + paramValue;
contents.Append($" arg{i} = {paramValue};").AppendLine();
// Release temporary GCHandles
if (CppParamsThatNeedConversion[i])
{
// Release the original handle
contents.Append($" if (&__param_orig_arg{i} != &__param_arg{i})").AppendLine();
contents.Append($" FreeManaged(({managedType})__param_arg{i});").AppendLine();
}
/*contents.Append($" FreeManaged(({managedType})__param_arg{i}); //hmm1b").AppendLine();
if (CppParamsThatNeedConversion[i])
{
// Release the original handle
contents.Append($" if (&__param_orig_arg{i} != &__param_arg{i})").AppendLine();
contents.Append($" FreeManaged(({managedType})__param_orig_arg{i});").AppendLine();
}*/
paramType.IsRef = true;
}
}
@@ -2382,6 +2609,7 @@ namespace Flax.Build.Bindings
{
Type = fieldInfo.Type,
Name = "value",
IsConst = true,
},
},
ReturnType = new TypeInfo
@@ -2461,14 +2689,20 @@ namespace Flax.Build.Bindings
contents.AppendLine(" }").AppendLine();
// Unboxing structures from managed object to native data
contents.AppendLine(" static void Unbox(void* ptr, MObject* managed)");
contents.AppendLine(" static void Unbox(void* ptr, MObject* data)");
contents.AppendLine(" {");
if (structureInfo.MarshalAs != null)
contents.AppendLine($" MISSING_CODE(\"Boxing native type {structureInfo.Name} as {structureInfo.MarshalAs}\");"); // TODO: impl this
else if (structureInfo.IsPod)
contents.AppendLine($" Platform::MemoryCopy(ptr, MCore::Object::Unbox(managed), sizeof({structureTypeNameNative}));");
contents.AppendLine($" MCore::Object::Unbox(data, ptr);");
//contents.AppendLine($" Platform::MemoryCopy(ptr, MCore::Object::Unbox(data), sizeof({structureTypeNameNative}));");
else
contents.AppendLine($" *({structureTypeNameNative}*)ptr = ToNative(*({GenerateCppManagedWrapperName(structureInfo)}*)MCore::Object::Unbox(managed));");
{
contents.AppendLine($" {GenerateCppManagedWrapperName(structureInfo)} managed;");
contents.AppendLine($" MCore::Object::Unbox(data, &managed);");
contents.AppendLine($" *({structureTypeNameNative}*)ptr = ToNative(managed);");
}
//contents.AppendLine($" *({structureTypeNameNative}*)ptr = ToNative(*({GenerateCppManagedWrapperName(structureInfo)}*)MCore::Object::Unbox(data));");
contents.AppendLine(" }").AppendLine();
}
else
@@ -2991,37 +3225,74 @@ namespace Flax.Build.Bindings
header.AppendLine("namespace {");
header.AppendFormat("{0} ToManaged(const {1}& value);", wrapperName, fullName).AppendLine();
header.AppendFormat("{1} ToNative(const {0}& value);", wrapperName, fullName).AppendLine();
header.AppendFormat("void FreeManaged(const {0}& value);", wrapperName).AppendLine();
header.AppendLine("}");
// Generate MConverter for a structure
header.Append("template<>").AppendLine();
header.AppendFormat("struct MConverter<{0}>", fullName).AppendLine();
header.Append('{').AppendLine();
header.AppendFormat(" MObject* Box(const {0}& data, const MClass* klass)", fullName).AppendLine();
header.AppendFormat(" DLLEXPORT USED MObject* Box(const {0}& data, const MClass* klass)", fullName).AppendLine();
header.Append(" {").AppendLine();
header.Append(" auto managed = ToManaged(data);").AppendLine();
header.Append(" return MCore::Object::Box((void*)&managed, klass);").AppendLine();
header.Append(" auto boxed = MCore::Object::Box((void*)&managed, klass);").AppendLine();
header.Append(" ::FreeManaged(managed);").AppendLine();
header.Append(" return boxed;").AppendLine();
header.Append(" }").AppendLine();
header.AppendFormat(" void Unbox({0}& result, MObject* data)", fullName).AppendLine();
header.AppendFormat(" DLLEXPORT USED void Unbox({0}& result, MObject* data)", fullName).AppendLine();
header.Append(" {").AppendLine();
header.AppendFormat(" result = ToNative(*reinterpret_cast<{0}*>(MCore::Object::Unbox(data)));", wrapperName).AppendLine();
//header.AppendFormat(" result = ToNative(*reinterpret_cast<{0}*>(MCore::Object::Unbox(data)));", wrapperName).AppendLine();
header.AppendFormat(" {0} managed;", wrapperName).AppendLine();
header.Append(" MCore::Object::Unbox(data, &managed);").AppendLine();
header.Append(" result = ToNative(managed);").AppendLine();
header.Append(" ::FreeManaged(managed);").AppendLine();
header.Append(" }").AppendLine();
header.AppendFormat(" void ToManagedArray(MArray* result, const Span<{0}>& data)", fullName).AppendLine();
header.AppendFormat(" DLLEXPORT USED void ToManagedArray(MArray* result, const Span<{0}>& data)", fullName).AppendLine();
header.Append(" {").AppendLine();
header.AppendFormat(" MClass* klass = {0}::TypeInitializer.GetClass();", fullName).AppendLine();
header.AppendFormat(" {0}* resultPtr = ({0}*)MCore::Array::GetAddress(result);", wrapperName).AppendLine();
header.Append(" for (int32 i = 0; i < data.Length(); i++)").AppendLine();
header.Append(" {").AppendLine();
header.Append(" auto managed = ToManaged(data[i]);").AppendLine();
header.Append(" MCore::GC::WriteValue(&resultPtr[i], &managed, 1, klass);").AppendLine();
header.Append(" auto managed = ToManaged(data[i]);").AppendLine();
header.Append(" MCore::GC::WriteValue(&resultPtr[i], &managed, 1, klass);").AppendLine();
header.Append(" }").AppendLine();
header.Append(" }").AppendLine();
header.AppendFormat(" void ToNativeArray(Span<{0}>& result, const MArray* data)", fullName).AppendLine();
header.AppendFormat(" DLLEXPORT USED void ToNativeArray(Span<{0}>& result, const MArray* data)", fullName).AppendLine();
header.Append(" {").AppendLine();
header.AppendFormat(" {0}* dataPtr = ({0}*)MCore::Array::GetAddress(data);", wrapperName).AppendLine();
header.Append(" for (int32 i = 0; i < result.Length(); i++)").AppendLine();
header.Append(" result[i] = ToNative(dataPtr[i]);").AppendLine();
header.Append(" result[i] = ToNative(dataPtr[i]);").AppendLine();
header.Append(" }").AppendLine();
header.AppendFormat(" DLLEXPORT USED void FreeManaged(MObject* data)").AppendLine();
header.Append(" {").AppendLine();
if (false) // FIXME: unboxing allocates temporary handles which FreeManaged does not free...
{
header.AppendFormat(" auto managed = reinterpret_cast<{0}*>(MCore::Object::Unbox(data));", wrapperName).AppendLine();
header.Append(" ::FreeManaged(*managed);").AppendLine();
}
header.Append(" MCore::GCHandle::Free(*(MGCHandle*)&data);").AppendLine();
header.Append(" }").AppendLine();
header.AppendFormat(" DLLEXPORT USED void FreeManagedArray(MArray* array)").AppendLine();
header.Append(" {").AppendLine();
header.AppendFormat(" {0}* resultPtr = ({0}*)MCore::Array::GetAddress(array);", wrapperName).AppendLine();
header.Append(" const auto length = MCore::Array::GetLength(array);").AppendLine();
header.Append(" for (int32 i = 0; i < length; ++i)").AppendLine();
header.Append(" {").AppendLine();
header.AppendFormat(" ::FreeManaged(*reinterpret_cast<{0}*>(&resultPtr[i]));", wrapperName).AppendLine();
header.Append(" }").AppendLine();
//header.Append(" PLATFORM_DEBUG_BREAK; // FIXME 1").AppendLine();
//header.AppendFormat(" auto managed = MCore::Object::Unbox(data);", wrapperName).AppendLine();
//header.AppendFormat(" ::FreeManaged(*reinterpret_cast<{0}*>(managed));", wrapperName).AppendLine();
header.Append(" MCore::Array::Free(array);").AppendLine();
header.Append(" MCore::GCHandle::Free(*(MGCHandle*)&array);").AppendLine();
header.Append(" }").AppendLine();
header.Append('}').Append(';').AppendLine();
// Generate converting function native -> managed
@@ -3069,9 +3340,11 @@ namespace Flax.Build.Bindings
continue;
CppNonPodTypesConvertingGeneration = true;
var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out _, out _, null, out _);
var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out var fieldTypeName, out var fieldType, null, out _);
CppNonPodTypesConvertingGeneration = false;
var arrayCountName = $"value.{fieldInfo.Name}.length";
if (fieldInfo.Type.IsArray)
{
// Fixed-size array needs to unbox every item manually
@@ -3084,7 +3357,7 @@ namespace Flax.Build.Bindings
}
else
{
wrapper = string.Format(wrapper.Remove(wrapper.Length - 6), string.Format("value.{0}", fieldInfo.Name));
wrapper = string.Format(wrapper.Remove(wrapper.Length - 6), string.Format("value.{0}", fieldInfo.Name), arrayCountName);
header.AppendFormat(" auto tmp{0} = {1};", fieldInfo.Name, wrapper).AppendLine();
header.AppendFormat(" for (int32 i = 0; i < {0} && i < tmp{1}.Count(); i++)", fieldInfo.Type.ArraySize, fieldInfo.Name).AppendLine();
header.AppendFormat(" result.{0}[i] = tmp{0}[i];", fieldInfo.Name).AppendLine();
@@ -3095,9 +3368,133 @@ namespace Flax.Build.Bindings
if (string.IsNullOrEmpty(wrapper))
header.AppendFormat(" result.{0} = value.{0};", fieldInfo.Name).AppendLine();
else
header.AppendFormat(" result.{0} = {1};", fieldInfo.Name, string.Format(wrapper, string.Format("value.{0}", fieldInfo.Name))).AppendLine();
{
var arrayElementType = fieldInfo.Type.GenericArgs != null ? FindApiTypeInfo(buildData, fieldInfo.Type.GenericArgs[0], apiType) : null;
var ispod = (arrayElementType?.IsPod.ToString() ?? "null");
header.Append($" // {fieldInfo.Type.Type}, {fieldType?.FullNameNative}, {fieldType?.Name}, {fieldTypeName}, {ispod}").AppendLine();
if (fieldInfo.Type.IsArrayOrSpan && (arrayElementType?.IsPod ?? false))
{
header.AppendFormat(" result.{0} = {1}; /*kaek1 */", fieldInfo.Name, string.Format(wrapper, $"value.{fieldInfo.Name}.data", $"value.{fieldInfo.Name}.length")).AppendLine();
}
else
{
header.AppendFormat(" result.{0} = {1}; /*kaek2 */", fieldInfo.Name, string.Format(wrapper, $"value.{fieldInfo.Name}", $"value.{fieldInfo.Name}.lengthnot")).AppendLine();
}
//var fieldType = FindApiTypeInfo(buildData, fieldInfo.Type, apiType);
//header.AppendFormat(" result.{0} = {1}; /*kaek2*/", fieldInfo.Name, string.Format(wrapper, $"value.{fieldInfo.Name}")).AppendLine();
}
/*if (wrapper.Contains("::ToNative(")) // FIXME
{
header.Append($" auto __param{fieldInfo.Name}_handle = *(MGCHandle*)&value.{fieldInfo.Name};").AppendLine();
header.Append($" ASSERT((((unsigned long long)__param{fieldInfo.Name}_handle & 0xC000000000000000) >> 62) == 0); // asdf3").AppendLine();
header.Append($" MCore::GCHandle::Free(__param{fieldInfo.Name}_handle);").AppendLine();
}*/
}
header.Append(" return result;").AppendLine();
header.Append('}').AppendLine();
// Generate release function for temporary managed handles
header.AppendLine();
header.AppendFormat("void FreeManaged(const {0}& value)", wrapperName).AppendLine();
header.Append('{').AppendLine();
for (var i = 0; i < fields.Count; i++)
{
var fieldInfo = fields[i];
if (fieldInfo.IsStatic || fieldInfo.IsConstexpr)
continue;
CppNonPodTypesConvertingGeneration = true;
var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out var managedType, out var fieldApiType, null, out _);
CppNonPodTypesConvertingGeneration = false;
if (fieldInfo.Type.IsArray)
{
//header.Append($" fixed_array_type = fixed_array_type; // {fieldInfo.Name} ({managedType}) is fixed array").AppendLine();
}
else if (string.IsNullOrEmpty(wrapper))
{
if (managedType == "bool")
{
}
else
{
}
}
else if (fieldInfo.Type.Type == "String" || managedType == "MString*")
{
if (fieldInfo.Type.Type != "String")
{
}
if (managedType != "MString*")
{
}
header.Append($" MCore::String::Free(value.{fieldInfo.Name});").AppendLine();
}
else if (fieldInfo.Type.Type == "Span")
{
}
else if (fieldInfo.Type.Type == "Array")
{
if (managedType == "MArray*")
{
}
else if (managedType == "Char*" || managedType == "char*")
{
}
else
{
//header.Append($" unhandled_type = unhandled_type; // {fieldInfo.Name} ({managedType}) should be cleaned? // asdfggg56").AppendLine();
}
}
else if (fieldApiType == null)
{
header.Append($" weird_type = weird_type; // {fieldInfo.Name} ({managedType}) is a weird one // okdfiodftg").AppendLine();
}
else if (fieldApiType.IsScriptingObject)
{
header.Append($" // {fieldInfo.Name} is a permanent handle // asdf2").AppendLine();
}
else if (!fieldApiType.IsPod) // FIXMEb
{
if (fieldApiType.IsClass)
{
}
else
{
header.Append($" ::FreeManaged(value.{fieldInfo.Name}); // asdfggg588").AppendLine();
//header.Append($" unhandled_type = unhandled_type; // {fieldInfo.Name} ({managedType}) should be cleaned? // asdfggg588").AppendLine();
}
header.Append($" auto __param{fieldInfo.Name}_handle = *(MGCHandle*)&value.{fieldInfo.Name};").AppendLine();
//header.Append($" ASSERT((((unsigned long long)__param{fieldInfo.Name}_handle & 0xC000000000000000) >> 62) == 0); // asdf3").AppendLine();
header.Append($" if ((((unsigned long long)__param{fieldInfo.Name}_handle & 0xC000000000000000) >> 62) != 0) // asdf3").AppendLine();
header.Append($" __param{fieldInfo.Name}_handle = __param{fieldInfo.Name}_handle;").AppendLine();
header.Append($" MCore::GCHandle::Free(__param{fieldInfo.Name}_handle);").AppendLine();
}
else if (fieldApiType.IsPod)
{
}
else
{
}
}
if (classInfo != null)
{
header.Append($" auto __value_handle = *(MGCHandle*)&value;").AppendLine();
header.Append($" ASSERT((((unsigned long long)__value_handle & 0xC000000000000000) >> 62) == 0); // asdf4").AppendLine();
header.Append($" MCore::GCHandle::Free(__value_handle);").AppendLine();
}
header.Append('}').AppendLine();
header.AppendLine("}");
}
@@ -3108,7 +3505,7 @@ namespace Flax.Build.Bindings
header.AppendFormat("struct MConverter<{0}>", fullName).AppendLine();
header.Append('{').AppendLine();
header.AppendFormat(" static MObject* Box(const {0}& data, const MClass* klass)", fullName).AppendLine();
header.AppendFormat(" DLLEXPORT USED static MObject* Box(const {0}& data, const MClass* klass)", fullName).AppendLine();
header.Append(" {").AppendLine();
header.Append(" MObject* obj = MCore::Object::New(klass);").AppendLine();
for (var i = 0; i < fields.Count; i++)
@@ -3128,13 +3525,13 @@ namespace Flax.Build.Bindings
header.Append(" return obj;").AppendLine();
header.Append(" }").AppendLine();
header.AppendFormat(" static MObject* Box(const {0}& data)", fullName).AppendLine();
header.AppendFormat(" DLLEXPORT USED static MObject* Box(const {0}& data)", fullName).AppendLine();
header.Append(" {").AppendLine();
header.AppendFormat(" MClass* klass = {0}::TypeInitializer.GetClass();", fullName).AppendLine();
header.Append(" return Box(data, klass);").AppendLine();
header.Append(" }").AppendLine();
header.AppendFormat(" static void Unbox({0}& result, MObject* obj)", fullName).AppendLine();
header.AppendFormat(" DLLEXPORT USED static void Unbox({0}& result, MObject* obj)", fullName).AppendLine();
header.Append(" {").AppendLine();
header.Append(" MClass* klass = MCore::Object::GetClass(obj);").AppendLine();
header.Append(" void* v = nullptr;").AppendLine();
@@ -3156,26 +3553,49 @@ namespace Flax.Build.Bindings
}
header.Append(" }").AppendLine();
header.AppendFormat(" static {0} Unbox(MObject* data)", fullName).AppendLine();
header.AppendFormat(" DLLEXPORT USED static {0} Unbox(MObject* data)", fullName).AppendLine();
header.Append(" {").AppendLine();
header.AppendFormat(" {0} result;", fullName).AppendLine();
header.Append(" Unbox(result, data);").AppendLine();
header.Append(" return result;").AppendLine();
header.Append(" }").AppendLine();
header.AppendFormat(" void ToManagedArray(MArray* result, const Span<{0}>& data)", fullName).AppendLine();
header.AppendFormat(" DLLEXPORT USED void ToManagedArray(MArray* result, const Span<{0}>& data)", fullName).AppendLine();
header.Append(" {").AppendLine();
header.Append(" for (int32 i = 0; i < data.Length(); i++)").AppendLine();
header.Append(" MCore::GC::WriteArrayRef(result, Box(data[i]), i);").AppendLine();
header.Append(" }").AppendLine();
header.AppendFormat(" void ToNativeArray(Span<{0}>& result, const MArray* data)", fullName).AppendLine();
header.AppendFormat(" DLLEXPORT USED void ToNativeArray(Span<{0}>& result, const MArray* data)", fullName).AppendLine();
header.Append(" {").AppendLine();
header.Append(" MObject** dataPtr = (MObject**)MCore::Array::GetAddress(data);").AppendLine();
header.Append(" for (int32 i = 0; i < result.Length(); i++)").AppendLine();
header.AppendFormat(" Unbox(result[i], dataPtr[i]);", fullName).AppendLine();
header.Append(" }").AppendLine();
header.AppendFormat(" DLLEXPORT USED void FreeManaged(MObject* data)").AppendLine();
header.Append(" {").AppendLine();
header.Append(" PLATFORM_DEBUG_BREAK; // FIXME 2b").AppendLine();
header.Append(" MCore::GCHandle::Free(*(MGCHandle*)&data);").AppendLine();
header.Append(" }").AppendLine();
header.AppendFormat(" DLLEXPORT USED void FreeManagedArray(MArray* array)").AppendLine();
header.Append(" {").AppendLine();
header.Append(" PLATFORM_DEBUG_BREAK; // FIXME 2").AppendLine();
header.Append(" MObject** resultPtr = (MObject**)MCore::Array::GetAddress(array);").AppendLine();
header.Append(" const auto length = MCore::Array::GetLength(array);").AppendLine();
header.Append(" for (int32 i = 0; i < length; ++i)").AppendLine();
header.Append(" {").AppendLine();
header.Append(" FreeManaged(resultPtr[i]);").AppendLine();
//header.AppendFormat(" MCore::GCHandle::Free(*(MGCHandle*)&resultPtr[i]);", wrapperName).AppendLine();
header.Append(" }").AppendLine();
//header.Append(" PLATFORM_DEBUG_BREAK; // FIXME 1").AppendLine();
//header.AppendFormat(" auto managed = MCore::Object::Unbox(data);", wrapperName).AppendLine();
//header.AppendFormat(" ::FreeManaged(*reinterpret_cast<{0}*>(managed));", wrapperName).AppendLine();
header.Append(" MCore::Array::Free(array);").AppendLine();
header.Append(" MCore::GCHandle::Free(*(MGCHandle*)&array);").AppendLine();
header.Append(" }").AppendLine();
header.Append('}').Append(';').AppendLine();
}
}

View File

@@ -18,30 +18,42 @@ namespace Flax.Build.Bindings
public bool IsRef;
public bool IsMoveRef;
public bool IsPtr;
/// <summary>
/// Is this a fixed-length type of array.
/// </summary>
public bool IsArray;
public bool IsBitField;
public int ArraySize;
public int BitSize;
public List<TypeInfo> GenericArgs;
/// <summary>
/// Gets a value indicating whether this type is void.
/// Is this a void type.
/// </summary>
public bool IsVoid => Type == "void" && !IsPtr;
/// <summary>
/// Gets a value indicating whether this type is constant reference to a value.
/// Is this a constant reference to a value.
/// </summary>
public bool IsConstRef => IsRef && IsConst;
/// <summary>
/// Gets a value indicating whether this type is a reference to another object.
/// Is this a reference to another FlaxEngine.Object.
/// </summary>
public bool IsObjectRef => (Type == "ScriptingObjectReference" ||
Type == "AssetReference" ||
Type == "WeakAssetReference" ||
Type == "SoftAssetReference" ||
Type == "SoftObjectReference") && GenericArgs != null;
public bool IsObjectRef => Type is "ScriptingObjectReference"
or "AssetReference"
or "WeakAssetReference"
or "SoftAssetReference"
or "SoftObjectReference"
&& GenericArgs != null;
/// <summary>
/// Is this an inbuilt container type.
/// </summary>
public bool IsArrayOrSpan => Type is "Array" or "Span" or "DataContainer" or "BytesContainer";
public TypeInfo()
{