Merge branch 'FlaxEngine:master' into add_spline_edit_options
This commit is contained in:
@@ -248,6 +248,7 @@
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=bitangent/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=bitangents/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Bokeh/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=borderless/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=BRDF/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=coeff/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=colliders/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// <inheritdoc />
|
||||
protected override OptionType[] Options => new[]
|
||||
{
|
||||
new OptionType("null", null),
|
||||
new OptionType("Texture", typeof(TextureBrush)),
|
||||
new OptionType("Sprite", typeof(SpriteBrush)),
|
||||
new OptionType("GPU Texture", typeof(GPUTextureBrush)),
|
||||
|
||||
@@ -158,7 +158,9 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
if (comboBox.SelectedIndex != -1)
|
||||
{
|
||||
var option = _options[comboBox.SelectedIndex];
|
||||
value = option.Creator(option.Type);
|
||||
if (option.Type != null)
|
||||
value = option.Creator(option.Type);
|
||||
|
||||
}
|
||||
SetValue(value);
|
||||
RebuildLayoutOnRefresh();
|
||||
|
||||
@@ -42,6 +42,10 @@ public class Editor : EditorModule
|
||||
options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions");
|
||||
options.ScriptingAPI.SystemReferences.Add("System.ComponentModel.TypeConverter");
|
||||
|
||||
// Enable optimizations for Editor, disable this for debugging the editor
|
||||
if (options.Configuration == TargetConfiguration.Development)
|
||||
options.ScriptingAPI.Optimization = true;
|
||||
|
||||
options.PublicDependencies.Add("Engine");
|
||||
options.PrivateDependencies.Add("pugixml");
|
||||
options.PrivateDependencies.Add("curl");
|
||||
|
||||
@@ -1579,7 +1579,7 @@ namespace FlaxEditor
|
||||
private static void RequestStartPlayOnEditMode()
|
||||
{
|
||||
if (Instance.StateMachine.IsEditMode)
|
||||
Instance.Simulation.RequestStartPlay();
|
||||
Instance.Simulation.RequestStartPlayScenes();
|
||||
if (Instance.StateMachine.IsPlayMode)
|
||||
Instance.StateMachine.StateChanged -= RequestStartPlayOnEditMode;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
|
||||
private List<ContextMenu> _menus = new List<ContextMenu>();
|
||||
private List<SingleSelectGroupItem> _items = new List<SingleSelectGroupItem>();
|
||||
private bool _hasSelected = false;
|
||||
private SingleSelectGroupItem _selectedItem;
|
||||
|
||||
public T Selected
|
||||
@@ -31,7 +32,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
set
|
||||
{
|
||||
var index = _items.FindIndex(x => x.Value.Equals(value));
|
||||
if (index != -1 && !_selectedItem.Value.Equals(value))
|
||||
if (index != -1 && (!_hasSelected || !_selectedItem.Value.Equals(value)))
|
||||
{
|
||||
SetSelected(_items[index]);
|
||||
}
|
||||
@@ -70,7 +71,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
if (item.Tooltip != null)
|
||||
btn.TooltipText = item.Tooltip;
|
||||
item.Buttons.Add(btn);
|
||||
if (item.Equals(_selectedItem))
|
||||
if (_hasSelected && item.Equals(_selectedItem))
|
||||
btn.Checked = true;
|
||||
}
|
||||
|
||||
@@ -82,6 +83,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
btn.Checked = false;
|
||||
}
|
||||
_selectedItem = item;
|
||||
_hasSelected = true;
|
||||
|
||||
SelectedChanged?.Invoke(item.Value);
|
||||
item.Selected?.Invoke();
|
||||
|
||||
@@ -253,6 +253,12 @@ namespace FlaxEditor.GUI.Docking
|
||||
tabColor = style.BackgroundHighlighted;
|
||||
Render2D.FillRectangle(tabRect, tabColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
tabColor = style.BackgroundHighlighted;
|
||||
Render2D.DrawLine(tabRect.BottomLeft - new Float2(0 , 1), tabRect.UpperLeft, tabColor);
|
||||
Render2D.DrawLine(tabRect.BottomRight - new Float2(0 , 1), tabRect.UpperRight, tabColor);
|
||||
}
|
||||
|
||||
if (tab.Icon.IsValid)
|
||||
{
|
||||
|
||||
@@ -282,7 +282,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
var x = time * zoom + Timeline.StartOffset;
|
||||
|
||||
// Header line
|
||||
var lineRect = new Rectangle(x - 0.5f, -verticalLinesHeaderExtend + timeAxisHeaderOffset, 1.0f, verticalLinesHeaderExtend);
|
||||
var lineRect = new Rectangle(x - 0.5f, -verticalLinesHeaderExtend * 0.6f + timeAxisHeaderOffset, 1.0f, verticalLinesHeaderExtend * 0.6f);
|
||||
Render2D.FillRectangle(lineRect, lineColor);
|
||||
|
||||
// Time label
|
||||
@@ -300,7 +300,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
var labelRect = new Rectangle(x + 2, -verticalLinesHeaderExtend + timeAxisHeaderOffset, 50, verticalLinesHeaderExtend);
|
||||
var labelRect = new Rectangle(x + 2, -verticalLinesHeaderExtend * 0.8f + timeAxisHeaderOffset, 50, verticalLinesHeaderExtend);
|
||||
Render2D.DrawText(style.FontSmall, labelText, labelRect, labelColor, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.8f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
@@ -30,11 +32,33 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
var timeAxisOverlap = Timeline.HeaderTopAreaHeight * 0.5f;
|
||||
var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y - timeAxisOverlap;
|
||||
|
||||
// Time label
|
||||
string labelText;
|
||||
switch (_timeline.TimeShowMode)
|
||||
{
|
||||
case Timeline.TimeShowModes.Frames:
|
||||
labelText = _timeline.CurrentFrame.ToString("###0", CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case Timeline.TimeShowModes.Seconds:
|
||||
labelText = _timeline.CurrentTime.ToString("###0.##'s'", CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case Timeline.TimeShowModes.Time:
|
||||
labelText = TimeSpan.FromSeconds(_timeline.CurrentTime).ToString("g");
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
var color = (_timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground).AlphaMultiplied(0.6f);
|
||||
Matrix3x3.RotationZ(Mathf.PiOverTwo, out var m1);
|
||||
var m2 = Matrix3x3.Translation2D(0, timeAxisHeaderOffset);
|
||||
Matrix3x3.Multiply(ref m1, ref m2, out var m3);
|
||||
Render2D.PushTransform(ref m3);
|
||||
Render2D.DrawSprite(icon, new Rectangle(new Float2(4, -Width), Size), _timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground);
|
||||
// TODO: Convert to its own sprite or 9 slice
|
||||
Render2D.DrawSprite(icon, new Rectangle(new Float2(10, -icon.Size.X * 0.5f - 1), Size + new Float2(0, 1)), color);
|
||||
Render2D.FillRectangle(new Rectangle(new Float2(-6, -icon.Size.Y * 0.5f + 7), new Float2(timeAxisOverlap, 5)), color);
|
||||
Render2D.PopTransform();
|
||||
var textMatrix = Matrix3x3.Translation2D(12, timeAxisHeaderOffset);
|
||||
Render2D.PushTransform(ref textMatrix);
|
||||
Render2D.DrawText(style.FontSmall, labelText, style.Foreground, new Float2(2, -6));
|
||||
Render2D.PopTransform();
|
||||
|
||||
Render2D.FillRectangle(new Rectangle(Width * 0.5f, Height + timeAxisHeaderOffset, 1, _timeline.MediaPanel.Height - timeAxisHeaderOffset - timeAxisOverlap), _timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground.RGBMultiplied(0.8f));
|
||||
|
||||
@@ -50,6 +50,27 @@ namespace FlaxEditor.GUI.Timeline
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseEnter(Float2 location)
|
||||
{
|
||||
base.OnMouseEnter(location);
|
||||
Cursor = CursorType.Hand;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
Cursor = CursorType.Default;
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Defocus()
|
||||
{
|
||||
Cursor = CursorType.Default;
|
||||
base.Defocus();
|
||||
}
|
||||
|
||||
private void Seek(ref Float2 location)
|
||||
{
|
||||
if (_timeline.PlaybackState == PlaybackStates.Disabled)
|
||||
|
||||
@@ -167,7 +167,7 @@ namespace FlaxEditor.GUI.Timeline
|
||||
/// <summary>
|
||||
/// The header top area height (in pixels).
|
||||
/// </summary>
|
||||
public static readonly float HeaderTopAreaHeight = 22.0f;
|
||||
public static readonly float HeaderTopAreaHeight = 40.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The timeline units per second (on time axis).
|
||||
|
||||
@@ -298,7 +298,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
var animEventTypes = Editor.Instance.CodeEditing.All.Get().Where(x => new ScriptType(typeof(AnimEvent)).IsAssignableFrom(x));
|
||||
foreach (var type in animEventTypes)
|
||||
{
|
||||
if (type.IsAbstract || !type.CanCreateInstance)
|
||||
if (type.IsAbstract || !type.CanCreateInstance || type.HasAttribute(typeof(HideInEditorAttribute), true))
|
||||
continue;
|
||||
var add = new ScriptType(typeof(AnimContinuousEvent)).IsAssignableFrom(type) ? addContinuousEvent : addEvent;
|
||||
var b = add.ContextMenu.AddButton(type.Name);
|
||||
@@ -307,6 +307,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
b.Parent.Tag = time;
|
||||
b.ButtonClicked += OnAddAnimEvent;
|
||||
}
|
||||
if (!addEvent.ContextMenu.Items.Any())
|
||||
addEvent.ContextMenu.AddButton("No Anim Events found").CloseMenuOnClick = false;
|
||||
if (!addContinuousEvent.ContextMenu.Items.Any())
|
||||
addContinuousEvent.ContextMenu.AddButton("No Continuous Anim Events found").CloseMenuOnClick = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -503,7 +503,7 @@ namespace FlaxEditor.Modules
|
||||
// Set paste target if only one actor is selected and no target provided
|
||||
if (pasteTargetActor == null && SelectionCount == 1 && Selection[0] is ActorNode actorNode)
|
||||
{
|
||||
pasteTargetActor = actorNode.Actor;
|
||||
pasteTargetActor = actorNode.Actor.Scene == actorNode.Actor ? actorNode.Actor : actorNode.Actor.Parent;
|
||||
}
|
||||
|
||||
// Create paste action
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace FlaxEditor.Modules
|
||||
private bool _updateOrFixedUpdateWasCalled;
|
||||
private long _breakpointHangFlag;
|
||||
private EditorWindow _enterPlayFocusedWindow;
|
||||
private Scene[] _scenesToReload;
|
||||
|
||||
internal SimulationModule(Editor editor)
|
||||
: base(editor)
|
||||
@@ -68,6 +69,22 @@ namespace FlaxEditor.Modules
|
||||
BreakpointHangEnd?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegates between playing game and playing scenes in editor based on the user's editor preference.
|
||||
/// </summary>
|
||||
public void DelegatePlayOrStopPlayInEditor()
|
||||
{
|
||||
switch (Editor.Options.Options.Interface.PlayButtonAction)
|
||||
{
|
||||
case Options.InterfaceOptions.PlayAction.PlayGame:
|
||||
Editor.Simulation.RequestPlayGameOrStopPlay();
|
||||
return;
|
||||
case Options.InterfaceOptions.PlayAction.PlayScenes:
|
||||
Editor.Simulation.RequestPlayScenesOrStopPlay();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if play mode has been requested.
|
||||
/// </summary>
|
||||
@@ -76,7 +93,7 @@ namespace FlaxEditor.Modules
|
||||
/// <summary>
|
||||
/// Requests start playing in editor.
|
||||
/// </summary>
|
||||
public void RequestStartPlay()
|
||||
public void RequestStartPlayScenes()
|
||||
{
|
||||
if (Editor.StateMachine.IsEditMode)
|
||||
{
|
||||
@@ -89,6 +106,57 @@ namespace FlaxEditor.Modules
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests playing game start or stop in editor from the project's configured FirstScene.
|
||||
/// </summary>
|
||||
public void RequestPlayGameOrStopPlay()
|
||||
{
|
||||
if (Editor.StateMachine.IsPlayMode)
|
||||
{
|
||||
RequestStopPlay();
|
||||
}
|
||||
else
|
||||
{
|
||||
RequestStartPlayGame();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests start playing in editor from the project's configured FirstScene.
|
||||
/// </summary>
|
||||
public void RequestStartPlayGame()
|
||||
{
|
||||
if (!Editor.StateMachine.IsEditMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var firstScene = Content.Settings.GameSettings.Load().FirstScene;
|
||||
if (firstScene == Guid.Empty)
|
||||
{
|
||||
if (Level.IsAnySceneLoaded)
|
||||
Editor.Simulation.RequestStartPlayScenes();
|
||||
return;
|
||||
}
|
||||
|
||||
_scenesToReload = Level.Scenes;
|
||||
Level.UnloadAllScenes();
|
||||
Level.LoadScene(firstScene);
|
||||
|
||||
Editor.PlayModeEnd += OnPlayGameEnd;
|
||||
RequestPlayScenesOrStopPlay();
|
||||
}
|
||||
|
||||
private void OnPlayGameEnd()
|
||||
{
|
||||
Editor.PlayModeEnd -= OnPlayGameEnd;
|
||||
|
||||
Level.UnloadAllScenes();
|
||||
|
||||
foreach (var scene in _scenesToReload)
|
||||
Level.LoadScene(scene.ID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests stop playing in editor.
|
||||
/// </summary>
|
||||
@@ -106,14 +174,14 @@ namespace FlaxEditor.Modules
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests the playing start or stop in editor.
|
||||
/// Requests the playing scenes start or stop in editor.
|
||||
/// </summary>
|
||||
public void RequestPlayOrStopPlay()
|
||||
public void RequestPlayScenesOrStopPlay()
|
||||
{
|
||||
if (Editor.StateMachine.IsPlayMode)
|
||||
RequestStopPlay();
|
||||
else
|
||||
RequestStartPlay();
|
||||
RequestStartPlayScenes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace FlaxEditor.Modules
|
||||
private bool _progressFailed;
|
||||
|
||||
ContextMenuSingleSelectGroup<int> _numberOfClientsGroup = new ContextMenuSingleSelectGroup<int>();
|
||||
private Scene[] _scenesToReload;
|
||||
|
||||
private ContextMenuButton _menuFileSaveScenes;
|
||||
private ContextMenuButton _menuFileCloseScenes;
|
||||
@@ -556,8 +555,8 @@ namespace FlaxEditor.Modules
|
||||
cm = MenuGame.ContextMenu;
|
||||
cm.VisibleChanged += OnMenuGameShowHide;
|
||||
|
||||
_menuGamePlayGame = cm.AddButton("Play Game", PlayGame);
|
||||
_menuGamePlayCurrentScenes = cm.AddButton("Play Current Scenes", inputOptions.Play.ToString(), PlayScenes);
|
||||
_menuGamePlayGame = cm.AddButton("Play Game", Editor.Simulation.RequestPlayGameOrStopPlay);
|
||||
_menuGamePlayCurrentScenes = cm.AddButton("Play Current Scenes", Editor.Simulation.RequestPlayScenesOrStopPlay);
|
||||
_menuGameStop = cm.AddButton("Stop Game", Editor.Simulation.RequestStopPlay);
|
||||
_menuGamePause = cm.AddButton("Pause", inputOptions.Pause.ToString(), Editor.Simulation.RequestPausePlay);
|
||||
|
||||
@@ -566,8 +565,8 @@ namespace FlaxEditor.Modules
|
||||
_numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu);
|
||||
|
||||
cm.AddSeparator();
|
||||
cm.AddButton("Cook & Run", CookAndRun).LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after.");
|
||||
cm.AddButton("Run cooked game", RunCookedGame).LinkTooltip("Runs the game build from the last cooking output. Use Cook&Play or Game Cooker first.");
|
||||
cm.AddButton("Cook & Run", Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after.");
|
||||
cm.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked).LinkTooltip("Runs the game build from the last cooking output. Use Cook&Play or Game Cooker first.");
|
||||
|
||||
// Tools
|
||||
MenuTools = MainMenu.AddButton("Tools");
|
||||
@@ -658,23 +657,23 @@ namespace FlaxEditor.Modules
|
||||
Parent = mainWindow,
|
||||
};
|
||||
|
||||
_toolStripSaveAll = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Save64, Editor.SaveAll).LinkTooltip("Save all (Ctrl+S)");
|
||||
_toolStripSaveAll = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Save64, Editor.SaveAll).LinkTooltip($"Save all ({inputOptions.Save})");
|
||||
ToolStrip.AddSeparator();
|
||||
_toolStripUndo = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Undo64, Editor.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
|
||||
_toolStripRedo = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Redo64, Editor.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
|
||||
_toolStripUndo = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Undo64, Editor.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
|
||||
_toolStripRedo = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Redo64, Editor.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
|
||||
ToolStrip.AddSeparator();
|
||||
_toolStripTranslate = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Translate32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate).LinkTooltip("Change Gizmo tool mode to Translate (1)");
|
||||
_toolStripRotate = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Rotate32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip("Change Gizmo tool mode to Rotate (2)");
|
||||
_toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip("Change Gizmo tool mode to Scale (3)");
|
||||
_toolStripTranslate = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Translate32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate).LinkTooltip($"Change Gizmo tool mode to Translate ({inputOptions.TranslateMode})");
|
||||
_toolStripRotate = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Rotate32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip($"Change Gizmo tool mode to Rotate ({inputOptions.RotateMode})");
|
||||
_toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip($"Change Gizmo tool mode to Scale ({inputOptions.ScaleMode})");
|
||||
ToolStrip.AddSeparator();
|
||||
|
||||
// Cook scenes
|
||||
_toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip("Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options (Ctrl+F10)");
|
||||
|
||||
// Cook and run
|
||||
_toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, CookAndRun).LinkTooltip("Cook & Run - build game for the current platform and run it locally");
|
||||
_toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip("Cook & Run - build game for the current platform and run it locally");
|
||||
_toolStripCook.ContextMenu = new ContextMenu();
|
||||
_toolStripCook.ContextMenu.AddButton("Run cooked game", RunCookedGame);
|
||||
_toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked);
|
||||
_toolStripCook.ContextMenu.AddSeparator();
|
||||
var numberOfClientsMenu = _toolStripCook.ContextMenu.AddChildMenu("Number of game clients");
|
||||
_numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu);
|
||||
@@ -682,7 +681,7 @@ namespace FlaxEditor.Modules
|
||||
ToolStrip.AddSeparator();
|
||||
|
||||
// Play
|
||||
_toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play64, OnPlayPressed).LinkTooltip("Play Game");
|
||||
_toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play64, Editor.Simulation.DelegatePlayOrStopPlayInEditor).LinkTooltip($"Play In Editor ({inputOptions.Play})");
|
||||
_toolStripPlay.ContextMenu = new ContextMenu();
|
||||
var playSubMenu = _toolStripPlay.ContextMenu.AddChildMenu("Play button action");
|
||||
var playActionGroup = new ContextMenuSingleSelectGroup<InterfaceOptions.PlayAction>();
|
||||
@@ -1039,65 +1038,6 @@ namespace FlaxEditor.Modules
|
||||
Editor.Options.Apply(options);
|
||||
}
|
||||
|
||||
private void OnPlayPressed()
|
||||
{
|
||||
switch (Editor.Options.Options.Interface.PlayButtonAction)
|
||||
{
|
||||
case InterfaceOptions.PlayAction.PlayGame:
|
||||
if (Editor.IsPlayMode)
|
||||
Editor.Simulation.RequestStopPlay();
|
||||
else
|
||||
PlayGame();
|
||||
return;
|
||||
case InterfaceOptions.PlayAction.PlayScenes:
|
||||
PlayScenes();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayGame()
|
||||
{
|
||||
var firstScene = GameSettings.Load().FirstScene;
|
||||
if (firstScene == Guid.Empty)
|
||||
{
|
||||
if (Level.IsAnySceneLoaded)
|
||||
Editor.Simulation.RequestStartPlay();
|
||||
return;
|
||||
}
|
||||
|
||||
_scenesToReload = Level.Scenes;
|
||||
Level.UnloadAllScenes();
|
||||
Level.LoadScene(firstScene);
|
||||
|
||||
Editor.PlayModeEnd += OnPlayGameSceneEnding;
|
||||
Editor.Simulation.RequestPlayOrStopPlay();
|
||||
}
|
||||
|
||||
private void OnPlayGameSceneEnding()
|
||||
{
|
||||
Editor.PlayModeEnd -= OnPlayGameSceneEnding;
|
||||
|
||||
Level.UnloadAllScenes();
|
||||
|
||||
foreach (var scene in _scenesToReload)
|
||||
Level.LoadScene(scene.ID);
|
||||
}
|
||||
|
||||
private void PlayScenes()
|
||||
{
|
||||
Editor.Simulation.RequestPlayOrStopPlay();
|
||||
}
|
||||
|
||||
private void CookAndRun()
|
||||
{
|
||||
Editor.Windows.GameCookerWin.BuildAndRun();
|
||||
}
|
||||
|
||||
private void RunCookedGame()
|
||||
{
|
||||
Editor.Windows.GameCookerWin.RunCooked();
|
||||
}
|
||||
|
||||
private void OnMainWindowClosing()
|
||||
{
|
||||
// Clear UI references (GUI cannot be used after window closing)
|
||||
|
||||
@@ -60,6 +60,10 @@ namespace FlaxEditor.Options
|
||||
[EditorDisplay("Common"), EditorOrder(200)]
|
||||
public InputBinding FocusSelection = new InputBinding(KeyboardKeys.F);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Shift+F")]
|
||||
[EditorDisplay("Common"), EditorOrder(200)]
|
||||
public InputBinding LockFocusSelection = new InputBinding(KeyboardKeys.F, KeyboardKeys.Shift);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Ctrl+F")]
|
||||
[EditorDisplay("Common"), EditorOrder(210)]
|
||||
public InputBinding Search = new InputBinding(KeyboardKeys.F, KeyboardKeys.Control);
|
||||
|
||||
@@ -138,6 +138,8 @@ namespace FlaxEditor.Actions
|
||||
}
|
||||
}
|
||||
|
||||
// Store previously looked up names and the results
|
||||
Dictionary<string, bool> foundNamesResults = new();
|
||||
for (int i = 0; i < nodeParents.Count; i++)
|
||||
{
|
||||
var node = nodeParents[i];
|
||||
@@ -145,15 +147,28 @@ namespace FlaxEditor.Actions
|
||||
var parent = actor?.Parent;
|
||||
if (parent != null)
|
||||
{
|
||||
bool IsNameValid(string name)
|
||||
{
|
||||
if (!foundNamesResults.TryGetValue(name, out bool found))
|
||||
{
|
||||
found = parent.GetChild(name) != null;
|
||||
foundNamesResults.Add(name, found);
|
||||
}
|
||||
return !found;
|
||||
}
|
||||
|
||||
// Fix name collisions
|
||||
var name = actor.Name;
|
||||
for (int j = 0; j < parent.ChildrenCount; j++)
|
||||
var children = parent.Children;
|
||||
for (int j = 0; j < children.Length; j++)
|
||||
{
|
||||
var child = parent.Children[j];
|
||||
if (child != actor && child.Name == actor.Name)
|
||||
var child = children[j];
|
||||
if (child != actor && child.Name == name)
|
||||
{
|
||||
var children = parent.Children;
|
||||
actor.Name = Utilities.Utils.IncrementNameNumber(name, x => children.All(y => y.Name != x));
|
||||
string newName = Utilities.Utils.IncrementNameNumber(name, x => IsNameValid(x));
|
||||
foundNamesResults[newName] = true;
|
||||
actor.Name = newName;
|
||||
// Multiple actors may have the same name, continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,10 +177,7 @@ namespace FlaxEditor.Actions
|
||||
}
|
||||
|
||||
for (int i = 0; i < nodeParents.Count; i++)
|
||||
{
|
||||
var node = nodeParents[i];
|
||||
node.PostPaste();
|
||||
}
|
||||
nodeParents[i].PostPaste();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -479,6 +479,7 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
InitFpsCounter();
|
||||
_showFpsButon = ViewWidgetShowMenu.AddButton("FPS Counter", () => ShowFpsCounter = !ShowFpsCounter);
|
||||
_showFpsButon.CloseMenuOnClick = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -133,6 +133,8 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
}
|
||||
|
||||
private bool _lockedFocus;
|
||||
private double _lockedFocusOffset;
|
||||
private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32);
|
||||
private StaticModel _previewStaticModel;
|
||||
private int _previewModelEntryIndex;
|
||||
@@ -369,9 +371,11 @@ namespace FlaxEditor.Viewport
|
||||
// Show grid widget
|
||||
_showGridButton = ViewWidgetShowMenu.AddButton("Grid", () => Grid.Enabled = !Grid.Enabled);
|
||||
_showGridButton.Icon = Style.Current.CheckBoxTick;
|
||||
_showGridButton.CloseMenuOnClick = false;
|
||||
|
||||
// Show navigation widget
|
||||
_showNavigationButton = ViewWidgetShowMenu.AddButton("Navigation", () => ShowNavigation = !ShowNavigation);
|
||||
_showNavigationButton.CloseMenuOnClick = false;
|
||||
|
||||
// Create camera widget
|
||||
ViewWidgetButtonMenu.AddSeparator();
|
||||
@@ -386,11 +390,43 @@ namespace FlaxEditor.Viewport
|
||||
InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
|
||||
InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
|
||||
InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
|
||||
InputActions.Add(options => options.LockFocusSelection, LockFocusSelection);
|
||||
InputActions.Add(options => options.FocusSelection, FocusSelection);
|
||||
InputActions.Add(options => options.RotateSelection, RotateSelection);
|
||||
InputActions.Add(options => options.Delete, _editor.SceneEditing.Delete);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
var selection = TransformGizmo.SelectedParents;
|
||||
var requestUnlockFocus = FlaxEngine.Input.Mouse.GetButtonDown(MouseButton.Right) || FlaxEngine.Input.Mouse.GetButtonDown(MouseButton.Left);
|
||||
if (TransformGizmo.SelectedParents.Count == 0 || (requestUnlockFocus && ContainsFocus))
|
||||
{
|
||||
UnlockFocusSelection();
|
||||
}
|
||||
else if (_lockedFocus)
|
||||
{
|
||||
var selectionBounds = BoundingSphere.Empty;
|
||||
for (int i = 0; i < selection.Count; i++)
|
||||
{
|
||||
selection[i].GetEditorSphere(out var sphere);
|
||||
BoundingSphere.Merge(ref selectionBounds, ref sphere, out selectionBounds);
|
||||
}
|
||||
|
||||
if (ContainsFocus)
|
||||
{
|
||||
var viewportFocusDistance = Vector3.Distance(ViewPosition, selectionBounds.Center) / 10f;
|
||||
_lockedFocusOffset -= FlaxEngine.Input.Mouse.ScrollDelta * viewportFocusDistance;
|
||||
}
|
||||
|
||||
var focusDistance = Mathf.Max(selectionBounds.Radius * 2d, 100d);
|
||||
ViewPosition = selectionBounds.Center + (-ViewDirection * (focusDistance + _lockedFocusOffset));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the selection outline effect or restored the default one.
|
||||
/// </summary>
|
||||
@@ -753,6 +789,23 @@ namespace FlaxEditor.Viewport
|
||||
FocusSelection(ref orientation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lock focus on the current selection gizmo.
|
||||
/// </summary>
|
||||
public void LockFocusSelection()
|
||||
{
|
||||
_lockedFocus = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unlock focus on the current selection.
|
||||
/// </summary>
|
||||
public void UnlockFocusSelection()
|
||||
{
|
||||
_lockedFocus = false;
|
||||
_lockedFocusOffset = 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Focuses the viewport on the current selection of the gizmo.
|
||||
/// </summary>
|
||||
|
||||
@@ -194,16 +194,20 @@ namespace FlaxEditor.Viewport.Previews
|
||||
{
|
||||
// Show Bounds
|
||||
_showBoundsButton = ViewWidgetShowMenu.AddButton("Bounds", () => ShowBounds = !ShowBounds);
|
||||
_showBoundsButton.CloseMenuOnClick = false;
|
||||
|
||||
// Show Skeleton
|
||||
_showNodesButton = ViewWidgetShowMenu.AddButton("Skeleton", () => ShowNodes = !ShowNodes);
|
||||
_showNodesButton.CloseMenuOnClick = false;
|
||||
|
||||
// Show Skeleton Names
|
||||
_showNodesNamesButton = ViewWidgetShowMenu.AddButton("Skeleton Names", () => ShowNodesNames = !ShowNodesNames);
|
||||
_showNodesNamesButton.CloseMenuOnClick = false;
|
||||
|
||||
// Show Floor
|
||||
_showFloorButton = ViewWidgetShowMenu.AddButton("Floor", button => ShowFloor = !ShowFloor);
|
||||
_showFloorButton.IndexInParent = 1;
|
||||
_showFloorButton.CloseMenuOnClick = false;
|
||||
}
|
||||
|
||||
// Enable shadows
|
||||
|
||||
@@ -171,6 +171,7 @@ namespace FlaxEditor.Viewport.Previews
|
||||
// Show Default Scene
|
||||
_showDefaultSceneButton = ViewWidgetShowMenu.AddButton("Default Scene", () => ShowDefaultSceneActors = !ShowDefaultSceneActors);
|
||||
_showDefaultSceneButton.Checked = true;
|
||||
_showDefaultSceneButton.CloseMenuOnClick = false;
|
||||
}
|
||||
|
||||
// Setup preview scene
|
||||
|
||||
@@ -199,13 +199,18 @@ namespace FlaxEditor.Viewport.Previews
|
||||
if (useWidgets)
|
||||
{
|
||||
_showBoundsButton = ViewWidgetShowMenu.AddButton("Bounds", () => ShowBounds = !ShowBounds);
|
||||
_showBoundsButton.CloseMenuOnClick = false;
|
||||
_showNormalsButton = ViewWidgetShowMenu.AddButton("Normals", () => ShowNormals = !ShowNormals);
|
||||
_showNormalsButton.CloseMenuOnClick = false;
|
||||
_showTangentsButton = ViewWidgetShowMenu.AddButton("Tangents", () => ShowTangents = !ShowTangents);
|
||||
_showTangentsButton.CloseMenuOnClick = false;
|
||||
_showBitangentsButton = ViewWidgetShowMenu.AddButton("Bitangents", () => ShowBitangents = !ShowBitangents);
|
||||
_showBitangentsButton.CloseMenuOnClick = false;
|
||||
|
||||
// Show Floor
|
||||
_showFloorButton = ViewWidgetShowMenu.AddButton("Floor", button => ShowFloor = !ShowFloor);
|
||||
_showFloorButton.IndexInParent = 1;
|
||||
_showFloorButton.CloseMenuOnClick = false;
|
||||
|
||||
// Show current LOD widget
|
||||
_showCurrentLODButton = ViewWidgetShowMenu.AddButton("Current LOD", button =>
|
||||
@@ -214,6 +219,7 @@ namespace FlaxEditor.Viewport.Previews
|
||||
_showCurrentLODButton.Icon = _showCurrentLOD ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
|
||||
});
|
||||
_showCurrentLODButton.IndexInParent = 2;
|
||||
_showCurrentLODButton.CloseMenuOnClick = false;
|
||||
|
||||
// Preview LODs mode widget
|
||||
var PreviewLODsMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
|
||||
@@ -186,8 +186,11 @@ namespace FlaxEditor.Viewport.Previews
|
||||
if (!useWidgets)
|
||||
return;
|
||||
_showBoundsButton = ViewWidgetShowMenu.AddButton("Bounds", () => ShowBounds = !ShowBounds);
|
||||
_showBoundsButton.CloseMenuOnClick = false;
|
||||
_showOriginButton = ViewWidgetShowMenu.AddButton("Origin", () => ShowOrigin = !ShowOrigin);
|
||||
_showOriginButton.CloseMenuOnClick = false;
|
||||
_showParticleCounterButton = ViewWidgetShowMenu.AddButton("Particles Counter", () => ShowParticlesCounter = !ShowParticlesCounter);
|
||||
_showParticleCounterButton.CloseMenuOnClick = false;
|
||||
|
||||
// Play/Pause widget
|
||||
{
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace FlaxEditor.Viewport.Previews
|
||||
_showCurrentLODButton.Icon = _showCurrentLOD ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
|
||||
});
|
||||
_showCurrentLODButton.IndexInParent = 2;
|
||||
_showCurrentLODButton.CloseMenuOnClick = false;
|
||||
|
||||
// PreviewLODS mode widget
|
||||
var PreviewLODSMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
private readonly Undo _undo;
|
||||
private object _object;
|
||||
private bool _isRegisteredForScriptsReload;
|
||||
private Label _typeText;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance of the Json asset object that is being edited.
|
||||
@@ -136,6 +137,22 @@ namespace FlaxEditor.Windows.Assets
|
||||
}
|
||||
}
|
||||
_presenter.Select(_object);
|
||||
|
||||
if (_typeText != null)
|
||||
_typeText.Dispose();
|
||||
var typeText = new ClickableLabel
|
||||
{
|
||||
Text = $"{Asset.DataTypeName}",
|
||||
TooltipText = "Asset data type (full name)",
|
||||
AnchorPreset = AnchorPresets.TopRight,
|
||||
AutoWidth = true,
|
||||
Parent = this,
|
||||
};
|
||||
typeText.LocalX += -(typeText.Width + 4);
|
||||
typeText.LocalY += (_toolstrip.Height - typeText.Height) * 0.5f;
|
||||
typeText.RightClick = () => Clipboard.Text = Asset.DataTypeName;
|
||||
_typeText = typeText;
|
||||
|
||||
_undo.Clear();
|
||||
ClearEditedFlag();
|
||||
|
||||
@@ -175,6 +192,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
_isRegisteredForScriptsReload = false;
|
||||
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
|
||||
}
|
||||
_typeText = null;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
// Set paste target if only one actor is selected and no target provided
|
||||
if (pasteTargetActor == null && Selection.Count == 1 && Selection[0] is ActorNode actorNode)
|
||||
{
|
||||
pasteTargetActor = actorNode.Actor;
|
||||
pasteTargetActor = actorNode.Actor.IsPrefabRoot ? actorNode.Actor : actorNode.Actor.Parent;
|
||||
}
|
||||
|
||||
// Create paste action
|
||||
|
||||
@@ -247,8 +247,17 @@ namespace FlaxEditor.Windows
|
||||
for (int i = 0; i < _viewDropdown.Items.Count; i++)
|
||||
{
|
||||
var filterButton = filters.ContextMenu.AddButton(_viewDropdown.Items[i], OnFilterClicked);
|
||||
filterButton.CloseMenuOnClick = false;
|
||||
filterButton.Tag = i;
|
||||
}
|
||||
filters.ContextMenu.ButtonClicked += button =>
|
||||
{
|
||||
foreach (var item in (filters.ContextMenu).Items)
|
||||
{
|
||||
if (item is ContextMenuButton filterButton)
|
||||
filterButton.Checked = _viewDropdown.IsSelected(filterButton.Text);
|
||||
}
|
||||
};
|
||||
filters.ContextMenu.VisibleChanged += control =>
|
||||
{
|
||||
if (!control.Visible)
|
||||
@@ -340,6 +349,9 @@ namespace FlaxEditor.Windows
|
||||
/// <returns>The created renaming popup.</returns>
|
||||
public void Rename(ContentItem item)
|
||||
{
|
||||
if (!item.CanRename)
|
||||
return;
|
||||
|
||||
// Show element in the view
|
||||
Select(item, true);
|
||||
|
||||
|
||||
@@ -303,7 +303,7 @@ namespace FlaxEditor.Windows
|
||||
Editor.Options.OptionsChanged += OnOptionsChanged;
|
||||
OnOptionsChanged(Editor.Options.Options);
|
||||
|
||||
InputActions.Add(options => options.Play, Editor.Simulation.RequestPlayOrStopPlay);
|
||||
InputActions.Add(options => options.Play, Editor.Simulation.DelegatePlayOrStopPlayInEditor);
|
||||
InputActions.Add(options => options.Pause, Editor.Simulation.RequestResumeOrPause);
|
||||
InputActions.Add(options => options.StepFrame, Editor.Simulation.RequestPlayOneFrame);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace FlaxEditor.Windows
|
||||
InputActions.Add(options => options.SelectAll, Editor.SceneEditing.SelectAllScenes);
|
||||
InputActions.Add(options => options.Delete, Editor.SceneEditing.Delete);
|
||||
InputActions.Add(options => options.Search, () => Editor.Windows.SceneWin.Search());
|
||||
InputActions.Add(options => options.Play, Editor.Simulation.RequestPlayOrStopPlay);
|
||||
InputActions.Add(options => options.Play, Editor.Simulation.DelegatePlayOrStopPlayInEditor);
|
||||
InputActions.Add(options => options.Pause, Editor.Simulation.RequestResumeOrPause);
|
||||
InputActions.Add(options => options.StepFrame, Editor.Simulation.RequestPlayOneFrame);
|
||||
}
|
||||
|
||||
@@ -233,11 +233,10 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
||||
// Get nested animation time position
|
||||
float nestedAnimPrevPos = animPrevPos - nestedAnim.Time;
|
||||
const float nestedAnimLength = nestedAnim.Anim->GetLength();
|
||||
const float nestedAnimDuration = nestedAnim.Anim->GetDuration();
|
||||
const float nestedAnimSpeed = nestedAnim.Speed * speed;
|
||||
const float frameRateMatchScale = (float)nestedAnim.Anim->Data.FramesPerSecond / (float)anim->Data.FramesPerSecond;
|
||||
nestedAnimPos = nestedAnimPos / nestedAnimDuration * nestedAnimSpeed * frameRateMatchScale;
|
||||
nestedAnimPrevPos = nestedAnimPrevPos / nestedAnimDuration * nestedAnimSpeed * frameRateMatchScale;
|
||||
const float frameRateMatchScale = nestedAnimSpeed / (float)anim->Data.FramesPerSecond;
|
||||
nestedAnimPos = nestedAnimPos * frameRateMatchScale;
|
||||
nestedAnimPrevPos = nestedAnimPrevPos * frameRateMatchScale;
|
||||
GetAnimSamplePos(nestedAnim.Loop, nestedAnimLength, nestedAnim.StartTime, nestedAnimPrevPos, nestedAnimPos, nestedAnimPos, nestedAnimPrevPos);
|
||||
|
||||
ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "AudioBackendXAudio2.h"
|
||||
#include "Engine/Audio/AudioSettings.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Collections/ChunkedArray.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Audio/Audio.h"
|
||||
#include "Engine/Audio/AudioSource.h"
|
||||
@@ -208,8 +209,8 @@ namespace XAudio2
|
||||
bool ForceDirty = true;
|
||||
Listener Listeners[AUDIO_MAX_LISTENERS];
|
||||
CriticalSection Locker;
|
||||
Array<Source> Sources(32); // TODO: use ChunkedArray for better performance
|
||||
Array<Buffer*> Buffers(64); // TODO: use ChunkedArray for better performance or use buffers pool?
|
||||
ChunkedArray<Source, 32> Sources;
|
||||
ChunkedArray<Buffer*, 64> Buffers; // TODO: use ChunkedArray for better performance or use buffers pool?
|
||||
EngineCallback Callback;
|
||||
|
||||
Listener* GetListener()
|
||||
|
||||
@@ -495,8 +495,13 @@ Variant::Variant(Variant&& other) noexcept
|
||||
other.AsDictionary = nullptr;
|
||||
break;
|
||||
case VariantType::ManagedObject:
|
||||
#if USE_NETCORE
|
||||
AsUint64 = other.AsUint64;
|
||||
other.AsUint64 = 0;
|
||||
#elif USE_MONO
|
||||
AsUint = other.AsUint;
|
||||
other.AsUint = 0;
|
||||
#endif
|
||||
break;
|
||||
case VariantType::Null:
|
||||
case VariantType::Void:
|
||||
@@ -1019,8 +1024,13 @@ Variant& Variant::operator=(Variant&& other)
|
||||
other.AsDictionary = nullptr;
|
||||
break;
|
||||
case VariantType::ManagedObject:
|
||||
#if USE_NETCORE
|
||||
AsUint64 = other.AsUint64;
|
||||
other.AsUint64 = 0;
|
||||
#elif USE_MONO
|
||||
AsUint = other.AsUint;
|
||||
other.AsUint = 0;
|
||||
#endif
|
||||
break;
|
||||
case VariantType::Null:
|
||||
case VariantType::Void:
|
||||
@@ -2419,7 +2429,11 @@ void Variant::SetType(const VariantType& type)
|
||||
AsDictionary = New<Dictionary<Variant, Variant>>();
|
||||
break;
|
||||
case VariantType::ManagedObject:
|
||||
#if USE_NETCORE
|
||||
AsUint64 = 0;
|
||||
#elif USE_MONO
|
||||
AsUint = 0;
|
||||
#endif
|
||||
break;
|
||||
case VariantType::Structure:
|
||||
AllocStructure();
|
||||
@@ -2532,7 +2546,11 @@ void Variant::SetType(VariantType&& type)
|
||||
AsDictionary = New<Dictionary<Variant, Variant>>();
|
||||
break;
|
||||
case VariantType::ManagedObject:
|
||||
#if USE_NETCORE
|
||||
AsUint64 = 0;
|
||||
#elif USE_MONO
|
||||
AsUint = 0;
|
||||
#endif
|
||||
break;
|
||||
case VariantType::Structure:
|
||||
AllocStructure();
|
||||
@@ -2671,7 +2689,11 @@ void Variant::SetManagedObject(MObject* object)
|
||||
{
|
||||
if (Type.Type != VariantType::ManagedObject || Type.TypeName)
|
||||
SetType(VariantType(VariantType::ManagedObject));
|
||||
#if USE_NETCORE
|
||||
AsUint64 = 0;
|
||||
#elif USE_MONO
|
||||
AsUint = 0;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FlaxEngine.Interop
|
||||
{
|
||||
@@ -20,26 +21,135 @@ namespace FlaxEngine.Interop
|
||||
{
|
||||
// TODO: Use .NET8 Unsafe.BitCast<TRet, ValueType>(returnValue) for more efficient casting of value types over boxing cast
|
||||
|
||||
internal static IntPtr MarshalReturnValue<TRet>(ref TRet returnValue)
|
||||
internal static class InvokerMarshallers<T>
|
||||
{
|
||||
internal delegate IntPtr Delegate(ref T value);
|
||||
internal static Delegate deleg;
|
||||
internal static Delegate delegThunk;
|
||||
|
||||
static InvokerMarshallers()
|
||||
{
|
||||
Type type = typeof(T);
|
||||
if (type == typeof(string))
|
||||
deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueString), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Delegate>();
|
||||
else if (type == typeof(ManagedHandle))
|
||||
deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueManagedHandle), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Delegate>();
|
||||
else if (type == typeof(Type))
|
||||
deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueType), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Delegate>();
|
||||
else if (type.IsArray)
|
||||
deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueArray), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(type).CreateDelegate<Delegate>();
|
||||
else if (type == typeof(bool))
|
||||
deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueBool), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Delegate>();
|
||||
else
|
||||
deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueWrapped), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(type).CreateDelegate<Delegate>();
|
||||
|
||||
if (type == typeof(string))
|
||||
delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueString), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Delegate>();
|
||||
else if (type == typeof(ManagedHandle))
|
||||
delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueManagedHandle), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Delegate>();
|
||||
else if (type == typeof(Type))
|
||||
delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueType), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Delegate>();
|
||||
else if (type.IsArray)
|
||||
delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueArray), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(type).CreateDelegate<Delegate>();
|
||||
else if (type == typeof(System.Boolean))
|
||||
delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueMonoBoolean), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Delegate>();
|
||||
else if (type == typeof(IntPtr))
|
||||
delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueIntPtr), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Delegate>();
|
||||
else if (type == typeof(System.Int16))
|
||||
delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueInt16), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Delegate>();
|
||||
else if (type == typeof(System.Int32))
|
||||
delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueInt32), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Delegate>();
|
||||
else if (type == typeof(System.Int64))
|
||||
delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueInt64), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Delegate>();
|
||||
else if (type == typeof(System.UInt16))
|
||||
delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueUInt16), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Delegate>();
|
||||
else if (type == typeof(System.UInt32))
|
||||
delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueUInt32), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Delegate>();
|
||||
else if (type == typeof(System.UInt64))
|
||||
delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueUInt64), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Delegate>();
|
||||
else
|
||||
delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueWrapped), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(type).CreateDelegate<Delegate>();
|
||||
}
|
||||
}
|
||||
|
||||
internal static IntPtr MarshalReturnValueString(ref string returnValue)
|
||||
{
|
||||
return returnValue != null ? ManagedString.ToNativeWeak(returnValue) : IntPtr.Zero;
|
||||
}
|
||||
|
||||
internal static IntPtr MarshalReturnValueManagedHandle(ref ManagedHandle returnValue)
|
||||
{
|
||||
return returnValue != null ? ManagedHandle.ToIntPtr(returnValue) : IntPtr.Zero;
|
||||
}
|
||||
|
||||
internal static IntPtr MarshalReturnValueType(ref Type returnValue)
|
||||
{
|
||||
return returnValue != null ? ManagedHandle.ToIntPtr(GetTypeGCHandle(returnValue)) : IntPtr.Zero;
|
||||
}
|
||||
|
||||
internal static IntPtr MarshalReturnValueArray<TRet>(ref TRet returnValue)
|
||||
{
|
||||
if (returnValue == null)
|
||||
return IntPtr.Zero;
|
||||
if (typeof(TRet) == typeof(string))
|
||||
return ManagedString.ToNativeWeak(Unsafe.As<string>(returnValue));
|
||||
if (typeof(TRet) == typeof(ManagedHandle))
|
||||
return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnValue);
|
||||
if (typeof(TRet) == typeof(bool))
|
||||
return (bool)(object)returnValue ? boolTruePtr : boolFalsePtr;
|
||||
if (typeof(TRet) == typeof(Type))
|
||||
return ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As<Type>(returnValue)));
|
||||
if (typeof(TRet).IsArray)
|
||||
{
|
||||
var elementType = typeof(TRet).GetElementType();
|
||||
if (ArrayFactory.GetMarshalledType(elementType) == elementType)
|
||||
return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As<Array>(returnValue)), GCHandleType.Weak);
|
||||
return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As<Array>(returnValue)), GCHandleType.Weak);
|
||||
}
|
||||
return ManagedHandle.ToIntPtr(returnValue, GCHandleType.Weak);
|
||||
var elementType = typeof(TRet).GetElementType();
|
||||
if (ArrayFactory.GetMarshalledType(elementType) == elementType)
|
||||
return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As<Array>(returnValue)), GCHandleType.Weak);
|
||||
return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As<Array>(returnValue)), GCHandleType.Weak);
|
||||
}
|
||||
|
||||
internal static IntPtr MarshalReturnValueBool(ref bool returnValue)
|
||||
{
|
||||
return returnValue ? boolTruePtr : boolFalsePtr;
|
||||
}
|
||||
|
||||
internal static IntPtr MarshalReturnValueIntPtr(ref IntPtr returnValue)
|
||||
{
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
internal static IntPtr MarshalReturnValueMonoBoolean(ref bool returnValue)
|
||||
{
|
||||
return returnValue ? 1 : 0;
|
||||
}
|
||||
|
||||
internal static IntPtr MarshalReturnValueInt16(ref Int16 returnValue)
|
||||
{
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
internal static IntPtr MarshalReturnValueInt32(ref Int32 returnValue)
|
||||
{
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
internal static IntPtr MarshalReturnValueInt64(ref Int64 returnValue)
|
||||
{
|
||||
return new IntPtr(returnValue);
|
||||
}
|
||||
|
||||
internal static IntPtr MarshalReturnValueUInt16(ref UInt16 returnValue)
|
||||
{
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
internal static IntPtr MarshalReturnValueUInt32(ref UInt32 returnValue)
|
||||
{
|
||||
return new IntPtr(returnValue);
|
||||
}
|
||||
|
||||
internal static IntPtr MarshalReturnValueUInt64(ref UInt64 returnValue)
|
||||
{
|
||||
return new IntPtr((long)returnValue);
|
||||
}
|
||||
|
||||
internal static IntPtr MarshalReturnValueWrapped<TRet>(ref TRet returnValue)
|
||||
{
|
||||
return returnValue != null ? ManagedHandle.ToIntPtr(returnValue, GCHandleType.Weak) : IntPtr.Zero;
|
||||
}
|
||||
|
||||
internal static IntPtr MarshalReturnValue<TRet>(ref TRet returnValue)
|
||||
{
|
||||
return InvokerMarshallers<TRet>.deleg(ref returnValue);
|
||||
}
|
||||
|
||||
internal static IntPtr MarshalReturnValueGeneric(Type returnType, object returnObject)
|
||||
@@ -63,39 +173,7 @@ namespace FlaxEngine.Interop
|
||||
|
||||
internal static IntPtr MarshalReturnValueThunk<TRet>(ref TRet returnValue)
|
||||
{
|
||||
if (returnValue == null)
|
||||
return IntPtr.Zero;
|
||||
if (typeof(TRet) == typeof(string))
|
||||
return ManagedString.ToNativeWeak(Unsafe.As<string>(returnValue));
|
||||
if (typeof(TRet) == typeof(IntPtr))
|
||||
return (IntPtr)(object)returnValue;
|
||||
if (typeof(TRet) == typeof(ManagedHandle))
|
||||
return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnValue);
|
||||
if (typeof(TRet) == typeof(Type))
|
||||
return ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As<Type>(returnValue)));
|
||||
if (typeof(TRet).IsArray)
|
||||
{
|
||||
var elementType = typeof(TRet).GetElementType();
|
||||
if (ArrayFactory.GetMarshalledType(elementType) == elementType)
|
||||
return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As<Array>(returnValue)), GCHandleType.Weak);
|
||||
return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As<Array>(returnValue)), GCHandleType.Weak);
|
||||
}
|
||||
// Match Mono bindings and pass value as pointer to prevent boxing it
|
||||
if (typeof(TRet) == typeof(System.Boolean))
|
||||
return new IntPtr(((System.Boolean)(object)returnValue) ? 1 : 0);
|
||||
if (typeof(TRet) == typeof(System.Int16))
|
||||
return new IntPtr((int)(System.Int16)(object)returnValue);
|
||||
if (typeof(TRet) == typeof(System.Int32))
|
||||
return new IntPtr((int)(System.Int32)(object)returnValue);
|
||||
if (typeof(TRet) == typeof(System.Int64))
|
||||
return new IntPtr((long)(System.Int64)(object)returnValue);
|
||||
if (typeof(TRet) == typeof(System.UInt16))
|
||||
return (IntPtr)new UIntPtr((ulong)(System.UInt16)(object)returnValue);
|
||||
if (typeof(TRet) == typeof(System.UInt32))
|
||||
return (IntPtr)new UIntPtr((ulong)(System.UInt32)(object)returnValue);
|
||||
if (typeof(TRet) == typeof(System.UInt64))
|
||||
return (IntPtr)new UIntPtr((ulong)(System.UInt64)(object)returnValue);
|
||||
return ManagedHandle.ToIntPtr(returnValue, GCHandleType.Weak);
|
||||
return InvokerMarshallers<TRet>.delegThunk(ref returnValue);
|
||||
}
|
||||
|
||||
internal static IntPtr MarshalReturnValueThunkGeneric(Type returnType, object returnObject)
|
||||
@@ -205,7 +283,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -215,7 +293,7 @@ namespace FlaxEngine.Interop
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -242,7 +320,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -262,7 +340,7 @@ namespace FlaxEngine.Interop
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -291,7 +369,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -317,7 +395,7 @@ namespace FlaxEngine.Interop
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -347,7 +425,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -379,7 +457,7 @@ namespace FlaxEngine.Interop
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -410,7 +488,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -448,7 +526,7 @@ namespace FlaxEngine.Interop
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -480,7 +558,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -490,7 +568,7 @@ namespace FlaxEngine.Interop
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -517,7 +595,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -537,7 +615,7 @@ namespace FlaxEngine.Interop
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -566,7 +644,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -592,7 +670,7 @@ namespace FlaxEngine.Interop
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -622,7 +700,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -654,7 +732,7 @@ namespace FlaxEngine.Interop
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -685,7 +763,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -723,7 +801,7 @@ namespace FlaxEngine.Interop
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -755,7 +833,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -765,7 +843,7 @@ namespace FlaxEngine.Interop
|
||||
return MarshalReturnValue(ref ret);
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -792,7 +870,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -812,7 +890,7 @@ namespace FlaxEngine.Interop
|
||||
return MarshalReturnValue(ref ret);
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -841,7 +919,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -867,7 +945,7 @@ namespace FlaxEngine.Interop
|
||||
return MarshalReturnValue(ref ret);
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -897,7 +975,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -929,7 +1007,7 @@ namespace FlaxEngine.Interop
|
||||
return MarshalReturnValue(ref ret);
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -960,7 +1038,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -998,7 +1076,7 @@ namespace FlaxEngine.Interop
|
||||
return MarshalReturnValue(ref ret);
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -1030,7 +1108,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -1040,7 +1118,7 @@ namespace FlaxEngine.Interop
|
||||
return MarshalReturnValue(ref ret);
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -1067,7 +1145,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -1087,7 +1165,7 @@ namespace FlaxEngine.Interop
|
||||
return MarshalReturnValue(ref ret);
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -1116,7 +1194,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -1142,7 +1220,7 @@ namespace FlaxEngine.Interop
|
||||
return MarshalReturnValue(ref ret);
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -1172,7 +1250,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -1204,7 +1282,7 @@ namespace FlaxEngine.Interop
|
||||
return MarshalReturnValue(ref ret);
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
@@ -1235,7 +1313,7 @@ namespace FlaxEngine.Interop
|
||||
return Unsafe.As<ThunkInvokerDelegate>(CreateDelegateFromMethod(method, false));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
|
||||
{
|
||||
(Type[] types, InvokerDelegate deleg) = (Tuple<Type[], InvokerDelegate>)(delegateContext);
|
||||
@@ -1273,7 +1351,7 @@ namespace FlaxEngine.Interop
|
||||
return MarshalReturnValue(ref ret);
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
//[DebuggerStepThrough]
|
||||
internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
|
||||
{
|
||||
ThunkInvokerDelegate deleg = Unsafe.As<ThunkInvokerDelegate>(delegateContext);
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace FlaxEngine.Interop
|
||||
private int _elementSize;
|
||||
private int _length;
|
||||
|
||||
[ThreadStatic] private static Dictionary<ManagedArray, ManagedHandle> pooledArrayHandles;
|
||||
|
||||
public static ManagedArray WrapNewArray(Array arr) => new ManagedArray(arr, arr.GetType());
|
||||
|
||||
public static ManagedArray WrapNewArray(Array arr, Type arrayType) => new ManagedArray(arr, arrayType);
|
||||
@@ -37,22 +39,38 @@ namespace FlaxEngine.Interop
|
||||
/// Returns an instance of ManagedArray from shared pool.
|
||||
/// </summary>
|
||||
/// <remarks>The resources must be released by calling FreePooled() instead of Free()-method.</remarks>
|
||||
public static ManagedArray WrapPooledArray(Array arr)
|
||||
public static (ManagedHandle managedHandle, ManagedArray managedArray) WrapPooledArray(Array arr)
|
||||
{
|
||||
ManagedArray managedArray = ManagedArrayPool.Get();
|
||||
managedArray.WrapArray(arr, arr.GetType());
|
||||
return managedArray;
|
||||
|
||||
if (pooledArrayHandles == null)
|
||||
pooledArrayHandles = new();
|
||||
if (!pooledArrayHandles.TryGetValue(managedArray, out ManagedHandle handle))
|
||||
{
|
||||
handle = ManagedHandle.Alloc(managedArray);
|
||||
pooledArrayHandles.Add(managedArray, handle);
|
||||
}
|
||||
return (handle, managedArray);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instance of ManagedArray from shared pool.
|
||||
/// </summary>
|
||||
/// <remarks>The resources must be released by calling FreePooled() instead of Free()-method.</remarks>
|
||||
public static ManagedArray WrapPooledArray(Array arr, Type arrayType)
|
||||
public static ManagedHandle WrapPooledArray(Array arr, Type arrayType)
|
||||
{
|
||||
ManagedArray managedArray = ManagedArrayPool.Get(arr.Length * NativeInterop.GetTypeSize(arr.GetType().GetElementType()));
|
||||
managedArray.WrapArray(arr, arrayType);
|
||||
return managedArray;
|
||||
|
||||
if (pooledArrayHandles == null)
|
||||
pooledArrayHandles = new();
|
||||
if (!pooledArrayHandles.TryGetValue(managedArray, out ManagedHandle handle))
|
||||
{
|
||||
handle = ManagedHandle.Alloc(managedArray);
|
||||
pooledArrayHandles.Add(managedArray, handle);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
internal static ManagedArray AllocateNewArray(int length, Type arrayType, Type elementType)
|
||||
@@ -64,12 +82,20 @@ namespace FlaxEngine.Interop
|
||||
/// <summary>
|
||||
/// Returns an instance of ManagedArray from shared pool.
|
||||
/// </summary>
|
||||
/// <remarks>The resources must be released by calling FreePooled() instead of Free()-method.</remarks>
|
||||
public static ManagedArray AllocatePooledArray<T>(int length) where T : unmanaged
|
||||
/// <remarks>The resources must be released by calling FreePooled() instead of Free()-method. Do not release the returned ManagedHandle.</remarks>
|
||||
public static (ManagedHandle managedHandle, ManagedArray managedArray) AllocatePooledArray<T>(int length) where T : unmanaged
|
||||
{
|
||||
ManagedArray managedArray = ManagedArrayPool.Get(length * Unsafe.SizeOf<T>());
|
||||
managedArray.Allocate<T>(length);
|
||||
return managedArray;
|
||||
|
||||
if (pooledArrayHandles == null)
|
||||
pooledArrayHandles = new();
|
||||
if (!pooledArrayHandles.TryGetValue(managedArray, out ManagedHandle handle))
|
||||
{
|
||||
handle = ManagedHandle.Alloc(managedArray);
|
||||
pooledArrayHandles.Add(managedArray, handle);
|
||||
}
|
||||
return (handle, managedArray);
|
||||
}
|
||||
|
||||
public ManagedArray(Array arr, Type elementType) => WrapArray(arr, elementType);
|
||||
@@ -91,18 +117,19 @@ namespace FlaxEngine.Interop
|
||||
|
||||
internal void Allocate<T>(int length) where T : unmanaged
|
||||
{
|
||||
_length = length;
|
||||
_arrayType = typeof(T[]);
|
||||
_elementType = typeof(T);
|
||||
_elementSize = Unsafe.SizeOf<T>();
|
||||
|
||||
// Try to reuse existing allocated buffer
|
||||
if (length * Unsafe.SizeOf<T>() > _unmanagedAllocationSize)
|
||||
if (length * _elementSize > _unmanagedAllocationSize)
|
||||
{
|
||||
if (_unmanagedAllocationSize > 0)
|
||||
NativeInterop.NativeFree(_unmanagedData.ToPointer());
|
||||
_unmanagedData = (IntPtr)NativeInterop.NativeAlloc(length, Unsafe.SizeOf<T>());
|
||||
_unmanagedAllocationSize = Unsafe.SizeOf<T>() * length;
|
||||
_unmanagedData = (IntPtr)NativeInterop.NativeAlloc(length, _elementSize);
|
||||
_unmanagedAllocationSize = _elementSize * length;
|
||||
}
|
||||
_length = length;
|
||||
_arrayType = typeof(T).MakeArrayType();
|
||||
_elementType = typeof(T);
|
||||
_elementSize = Unsafe.SizeOf<T>();
|
||||
}
|
||||
|
||||
private ManagedArray()
|
||||
@@ -112,12 +139,12 @@ namespace FlaxEngine.Interop
|
||||
private ManagedArray(IntPtr ptr, int length, Type arrayType, Type elementType)
|
||||
{
|
||||
Assert.IsTrue(arrayType.IsArray);
|
||||
_elementType = elementType;
|
||||
_elementSize = NativeInterop.GetTypeSize(elementType);
|
||||
_unmanagedData = ptr;
|
||||
_unmanagedAllocationSize = Marshal.SizeOf(elementType) * length;
|
||||
_unmanagedAllocationSize = _elementSize * length;
|
||||
_length = length;
|
||||
_arrayType = arrayType;
|
||||
_elementType = elementType;
|
||||
_elementSize = NativeInterop.GetTypeSize(_elementType);
|
||||
}
|
||||
|
||||
~ManagedArray()
|
||||
@@ -330,15 +357,9 @@ namespace FlaxEngine.Interop
|
||||
{
|
||||
private IntPtr handle;
|
||||
|
||||
private ManagedHandle(IntPtr handle)
|
||||
{
|
||||
this.handle = handle;
|
||||
}
|
||||
private ManagedHandle(IntPtr handle) => this.handle = handle;
|
||||
|
||||
private ManagedHandle(object value, GCHandleType type)
|
||||
{
|
||||
handle = ManagedHandlePool.AllocateHandle(value, type);
|
||||
}
|
||||
private ManagedHandle(object value, GCHandleType type) => handle = ManagedHandlePool.AllocateHandle(value, type);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ManagedHandle Alloc(object value) => new ManagedHandle(value, GCHandleType.Normal);
|
||||
@@ -383,7 +404,7 @@ namespace FlaxEngine.Interop
|
||||
|
||||
public override int GetHashCode() => handle.GetHashCode();
|
||||
|
||||
public override bool Equals(object o) => o is ManagedHandle other && Equals(other);
|
||||
public override bool Equals(object obj) => obj is ManagedHandle other && handle == other.handle;
|
||||
|
||||
public bool Equals(ManagedHandle other) => handle == other.handle;
|
||||
|
||||
@@ -391,42 +412,44 @@ namespace FlaxEngine.Interop
|
||||
|
||||
public static bool operator !=(ManagedHandle a, ManagedHandle b) => a.handle != b.handle;
|
||||
|
||||
private static class ManagedHandlePool
|
||||
internal static class ManagedHandlePool
|
||||
{
|
||||
private const int WeakPoolCollectionSizeThreshold = 10000000;
|
||||
private const int WeakPoolCollectionTimeThreshold = 500;
|
||||
|
||||
private static ulong normalHandleAccumulator = 0;
|
||||
private static ulong pinnedHandleAccumulator = 0;
|
||||
private static ulong weakHandleAccumulator = 0;
|
||||
// Rolling numbers for handles, two bits reserved for the type
|
||||
private static ulong normalHandleAccumulator = ((ulong)GCHandleType.Normal << 62) & 0xC000000000000000;
|
||||
private static ulong pinnedHandleAccumulator = ((ulong)GCHandleType.Pinned << 62) & 0xC000000000000000;
|
||||
private static ulong weakHandleAccumulator = ((ulong)GCHandleType.Weak << 62) & 0xC000000000000000;
|
||||
|
||||
private static object poolLock = new object();
|
||||
private static Dictionary<IntPtr, object> persistentPool = new Dictionary<nint, object>();
|
||||
private static Dictionary<IntPtr, GCHandle> pinnedPool = new Dictionary<nint, GCHandle>();
|
||||
// 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.
|
||||
private static Dictionary<IntPtr, object> persistentPool = new();
|
||||
private static Dictionary<IntPtr, GCHandle> pinnedPool = new();
|
||||
|
||||
// Manage double-buffered pool for weak handles in order to avoid collecting in-flight handles
|
||||
[ThreadStatic] private static Dictionary<IntPtr, object> weakPool;
|
||||
[ThreadStatic] private static Dictionary<IntPtr, object> weakPoolOther;
|
||||
[ThreadStatic] private static ulong nextWeakPoolCollection;
|
||||
[ThreadStatic] private static int nextWeakPoolGCCollection;
|
||||
[ThreadStatic] private static long lastWeakPoolCollectionTime;
|
||||
// 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 ulong nextWeakPoolCollection;
|
||||
private static int nextWeakPoolGCCollection;
|
||||
private static long lastWeakPoolCollectionTime;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to free all references to old weak handles so GC can collect them.
|
||||
/// </summary>
|
||||
private static void TryCollectWeakHandles()
|
||||
internal static void TryCollectWeakHandles()
|
||||
{
|
||||
if (weakHandleAccumulator < nextWeakPoolCollection)
|
||||
return;
|
||||
|
||||
nextWeakPoolCollection = weakHandleAccumulator + 1000;
|
||||
if (weakPool == null)
|
||||
{
|
||||
weakPool = new Dictionary<nint, object>();
|
||||
weakPoolOther = new Dictionary<nint, object>();
|
||||
nextWeakPoolGCCollection = GC.CollectionCount(0) + 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to swap pools after garbage collection or whenever the pool gets too large
|
||||
var gc0CollectionCount = GC.CollectionCount(0);
|
||||
if (gc0CollectionCount < nextWeakPoolGCCollection && weakPool.Count < WeakPoolCollectionSizeThreshold)
|
||||
@@ -443,130 +466,157 @@ namespace FlaxEngine.Interop
|
||||
weakPool.Clear();
|
||||
}
|
||||
|
||||
private static IntPtr NewHandle(GCHandleType type)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static IntPtr NewHandle(GCHandleType type) => type switch
|
||||
{
|
||||
IntPtr handle;
|
||||
if (type == GCHandleType.Normal)
|
||||
handle = (IntPtr)Interlocked.Increment(ref normalHandleAccumulator);
|
||||
else if (type == GCHandleType.Pinned)
|
||||
handle = (IntPtr)Interlocked.Increment(ref pinnedHandleAccumulator);
|
||||
else //if (type == GCHandleType.Weak || type == GCHandleType.WeakTrackResurrection)
|
||||
handle = (IntPtr)Interlocked.Increment(ref weakHandleAccumulator);
|
||||
|
||||
// Two bits reserved for the type
|
||||
handle |= (IntPtr)(((ulong)type << 62) & 0xC000000000000000);
|
||||
return handle;
|
||||
}
|
||||
GCHandleType.Normal => (IntPtr)Interlocked.Increment(ref normalHandleAccumulator),
|
||||
GCHandleType.Pinned => (IntPtr)Interlocked.Increment(ref pinnedHandleAccumulator),
|
||||
GCHandleType.Weak => (IntPtr)Interlocked.Increment(ref weakHandleAccumulator),
|
||||
GCHandleType.WeakTrackResurrection => (IntPtr)Interlocked.Increment(ref weakHandleAccumulator),
|
||||
_ => throw new NotImplementedException(type.ToString())
|
||||
};
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static GCHandleType GetHandleType(IntPtr handle)
|
||||
{
|
||||
return (GCHandleType)(((ulong)handle & 0xC000000000000000) >> 62);
|
||||
}
|
||||
private static GCHandleType GetHandleType(IntPtr handle) => (GCHandleType)(((ulong)handle & 0xC000000000000000) >> 62);
|
||||
|
||||
internal static IntPtr AllocateHandle(object value, GCHandleType type)
|
||||
{
|
||||
TryCollectWeakHandles();
|
||||
IntPtr handle = NewHandle(type);
|
||||
if (type == GCHandleType.Normal)
|
||||
switch (type)
|
||||
{
|
||||
lock (poolLock)
|
||||
case GCHandleType.Normal:
|
||||
lock (persistentPool)
|
||||
persistentPool.Add(handle, value);
|
||||
}
|
||||
else if (type == GCHandleType.Pinned)
|
||||
{
|
||||
lock (poolLock)
|
||||
break;
|
||||
case GCHandleType.Pinned:
|
||||
lock (pinnedPool)
|
||||
pinnedPool.Add(handle, GCHandle.Alloc(value, GCHandleType.Pinned));
|
||||
break;
|
||||
case GCHandleType.Weak:
|
||||
case GCHandleType.WeakTrackResurrection:
|
||||
lock (weakPoolLock)
|
||||
{
|
||||
TryCollectWeakHandles();
|
||||
weakPool.Add(handle, value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (type == GCHandleType.Weak || type == GCHandleType.WeakTrackResurrection)
|
||||
weakPool.Add(handle, value);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
internal static object GetObject(IntPtr handle)
|
||||
{
|
||||
TryCollectWeakHandles();
|
||||
object value;
|
||||
GCHandleType type = GetHandleType(handle);
|
||||
if (type == GCHandleType.Normal)
|
||||
switch (GetHandleType(handle))
|
||||
{
|
||||
lock (poolLock)
|
||||
case GCHandleType.Normal:
|
||||
lock (persistentPool)
|
||||
{
|
||||
if (persistentPool.TryGetValue(handle, out value))
|
||||
if (persistentPool.TryGetValue(handle, out object value))
|
||||
return value;
|
||||
}
|
||||
}
|
||||
else if (type == GCHandleType.Pinned)
|
||||
{
|
||||
lock (poolLock)
|
||||
break;
|
||||
case GCHandleType.Pinned:
|
||||
lock (pinnedPool)
|
||||
{
|
||||
if (pinnedPool.TryGetValue(handle, out GCHandle gchandle))
|
||||
return gchandle.Target;
|
||||
if (pinnedPool.TryGetValue(handle, out GCHandle gcHandle))
|
||||
return gcHandle.Target;
|
||||
}
|
||||
break;
|
||||
case GCHandleType.Weak:
|
||||
case GCHandleType.WeakTrackResurrection:
|
||||
lock (weakPoolLock)
|
||||
{
|
||||
TryCollectWeakHandles();
|
||||
if (weakPool.TryGetValue(handle, out object value))
|
||||
return value;
|
||||
else if (weakPoolOther.TryGetValue(handle, out value))
|
||||
return value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (weakPool.TryGetValue(handle, out value))
|
||||
return value;
|
||||
else if (weakPoolOther.TryGetValue(handle, out value))
|
||||
return value;
|
||||
|
||||
throw new NativeInteropException("Invalid ManagedHandle");
|
||||
}
|
||||
|
||||
internal static void SetObject(IntPtr handle, object value)
|
||||
{
|
||||
TryCollectWeakHandles();
|
||||
GCHandleType type = GetHandleType(handle);
|
||||
if (type == GCHandleType.Normal)
|
||||
switch (GetHandleType(handle))
|
||||
{
|
||||
lock (poolLock)
|
||||
case GCHandleType.Normal:
|
||||
lock (persistentPool)
|
||||
{
|
||||
if (persistentPool.ContainsKey(handle))
|
||||
persistentPool[handle] = value;
|
||||
ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(persistentPool, handle);
|
||||
if (!Unsafe.IsNullRef(ref obj))
|
||||
{
|
||||
obj = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (type == GCHandleType.Pinned)
|
||||
{
|
||||
lock (poolLock)
|
||||
break;
|
||||
case GCHandleType.Pinned:
|
||||
lock (pinnedPool)
|
||||
{
|
||||
if (pinnedPool.TryGetValue(handle, out GCHandle gchandle))
|
||||
gchandle.Target = value;
|
||||
ref GCHandle gcHandle = ref CollectionsMarshal.GetValueRefOrNullRef(pinnedPool, handle);
|
||||
if (!Unsafe.IsNullRef(ref gcHandle))
|
||||
{
|
||||
gcHandle.Target = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GCHandleType.Weak:
|
||||
case GCHandleType.WeakTrackResurrection:
|
||||
lock (weakPoolLock)
|
||||
{
|
||||
TryCollectWeakHandles();
|
||||
{
|
||||
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 if (weakPool.ContainsKey(handle))
|
||||
weakPool[handle] = value;
|
||||
else if (weakPoolOther.ContainsKey(handle))
|
||||
weakPoolOther[handle] = value;
|
||||
|
||||
throw new NativeInteropException("Invalid ManagedHandle");
|
||||
}
|
||||
|
||||
internal static void FreeHandle(IntPtr handle)
|
||||
{
|
||||
TryCollectWeakHandles();
|
||||
GCHandleType type = GetHandleType(handle);
|
||||
if (type == GCHandleType.Normal)
|
||||
switch (GetHandleType(handle))
|
||||
{
|
||||
lock (poolLock)
|
||||
case GCHandleType.Normal:
|
||||
lock (persistentPool)
|
||||
{
|
||||
if (persistentPool.Remove(handle))
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (type == GCHandleType.Pinned)
|
||||
{
|
||||
lock (poolLock)
|
||||
break;
|
||||
case GCHandleType.Pinned:
|
||||
lock (pinnedPool)
|
||||
{
|
||||
if (pinnedPool.Remove(handle, out GCHandle gchandle))
|
||||
if (pinnedPool.Remove(handle, out GCHandle gcHandle))
|
||||
{
|
||||
gchandle.Free();
|
||||
gcHandle.Free();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
break;
|
||||
case GCHandleType.Weak:
|
||||
case GCHandleType.WeakTrackResurrection:
|
||||
lock (weakPoolLock)
|
||||
TryCollectWeakHandles();
|
||||
return;
|
||||
|
||||
}
|
||||
throw new NativeInteropException("Invalid ManagedHandle");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,14 +200,13 @@ namespace FlaxEngine.Interop
|
||||
public void FromManaged(Array managed)
|
||||
{
|
||||
if (managed != null)
|
||||
managedArray = ManagedArray.WrapPooledArray(managed);
|
||||
(handle, managedArray) = ManagedArray.WrapPooledArray(managed);
|
||||
}
|
||||
|
||||
public IntPtr ToUnmanaged()
|
||||
{
|
||||
if (managedArray == null)
|
||||
return IntPtr.Zero;
|
||||
handle = ManagedHandle.Alloc(managedArray, GCHandleType.Weak);
|
||||
return ManagedHandle.ToIntPtr(handle);
|
||||
}
|
||||
|
||||
@@ -216,7 +215,6 @@ namespace FlaxEngine.Interop
|
||||
if (managedArray == null)
|
||||
return;
|
||||
managedArray.FreePooled();
|
||||
//handle.Free(); // No need to free weak handles
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +333,6 @@ namespace FlaxEngine.Interop
|
||||
#endif
|
||||
[CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ManagedToUnmanagedIn, typeof(ArrayMarshaller<,>.ManagedToNative))]
|
||||
[CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.UnmanagedToManagedOut, typeof(ArrayMarshaller<,>.ManagedToNative))]
|
||||
[CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ElementIn, typeof(ArrayMarshaller<,>.ManagedToNative))]
|
||||
[CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ManagedToUnmanagedOut, typeof(ArrayMarshaller<,>.NativeToManaged))]
|
||||
[CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.UnmanagedToManagedIn, typeof(ArrayMarshaller<,>.NativeToManaged))]
|
||||
[CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ElementOut, typeof(ArrayMarshaller<,>.NativeToManaged))]
|
||||
@@ -388,38 +385,27 @@ namespace FlaxEngine.Interop
|
||||
#if FLAX_EDITOR
|
||||
[HideInEditor]
|
||||
#endif
|
||||
public static class ManagedToNative
|
||||
public ref struct ManagedToNative
|
||||
{
|
||||
public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[] managed, out int numElements)
|
||||
T[] sourceArray;
|
||||
ManagedArray managedArray;
|
||||
ManagedHandle managedHandle;
|
||||
|
||||
public void FromManaged(T[] managed)
|
||||
{
|
||||
if (managed is null)
|
||||
{
|
||||
numElements = 0;
|
||||
return null;
|
||||
}
|
||||
numElements = managed.Length;
|
||||
ManagedArray managedArray = ManagedArray.AllocatePooledArray<TUnmanagedElement>(managed.Length);
|
||||
return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArray, GCHandleType.Normal);
|
||||
}
|
||||
|
||||
public static ReadOnlySpan<T> GetManagedValuesSource(T[] managed) => managed;
|
||||
|
||||
public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements)
|
||||
{
|
||||
if (unmanaged == null)
|
||||
return Span<TUnmanagedElement>.Empty;
|
||||
ManagedArray managedArray = Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(new IntPtr(unmanaged)).Target);
|
||||
return managedArray.ToSpan<TUnmanagedElement>();
|
||||
}
|
||||
|
||||
public static void Free(TUnmanagedElement* unmanaged)
|
||||
{
|
||||
if (unmanaged == null)
|
||||
return;
|
||||
ManagedHandle handle = ManagedHandle.FromIntPtr(new IntPtr(unmanaged));
|
||||
(Unsafe.As<ManagedArray>(handle.Target)).FreePooled();
|
||||
//handle.Free(); // No need to free weak handles
|
||||
sourceArray = managed;
|
||||
(managedHandle, managedArray) = ManagedArray.AllocatePooledArray<TUnmanagedElement>(managed.Length);
|
||||
}
|
||||
|
||||
public ReadOnlySpan<T> GetManagedValuesSource() => sourceArray;
|
||||
|
||||
public Span<TUnmanagedElement> GetUnmanagedValuesDestination() => managedArray != null ? managedArray.ToSpan<TUnmanagedElement>() : Span<TUnmanagedElement>.Empty;
|
||||
|
||||
public TUnmanagedElement* ToUnmanaged() => (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedHandle);
|
||||
|
||||
public void Free() => managedArray?.FreePooled();
|
||||
}
|
||||
|
||||
#if FLAX_EDITOR
|
||||
@@ -427,26 +413,25 @@ namespace FlaxEngine.Interop
|
||||
#endif
|
||||
public struct Bidirectional
|
||||
{
|
||||
T[] managedArray;
|
||||
ManagedArray unmanagedArray;
|
||||
T[] sourceArray;
|
||||
ManagedArray managedArray;
|
||||
ManagedHandle handle;
|
||||
|
||||
public void FromManaged(T[] managed)
|
||||
{
|
||||
if (managed == null)
|
||||
return;
|
||||
managedArray = managed;
|
||||
unmanagedArray = ManagedArray.AllocatePooledArray<TUnmanagedElement>(managed.Length);
|
||||
handle = ManagedHandle.Alloc(unmanagedArray);
|
||||
sourceArray = managed;
|
||||
(handle, managedArray) = ManagedArray.AllocatePooledArray<TUnmanagedElement>(managed.Length);
|
||||
}
|
||||
|
||||
public ReadOnlySpan<T> GetManagedValuesSource() => managedArray;
|
||||
public ReadOnlySpan<T> GetManagedValuesSource() => sourceArray;
|
||||
|
||||
public Span<TUnmanagedElement> GetUnmanagedValuesDestination()
|
||||
{
|
||||
if (unmanagedArray == null)
|
||||
if (managedArray == null)
|
||||
return Span<TUnmanagedElement>.Empty;
|
||||
return unmanagedArray.ToSpan<TUnmanagedElement>();
|
||||
return managedArray.ToSpan<TUnmanagedElement>();
|
||||
}
|
||||
|
||||
public TUnmanagedElement* ToUnmanaged() => (TUnmanagedElement*)ManagedHandle.ToIntPtr(handle);
|
||||
@@ -454,26 +439,22 @@ namespace FlaxEngine.Interop
|
||||
public void FromUnmanaged(TUnmanagedElement* unmanaged)
|
||||
{
|
||||
ManagedArray arr = Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(new IntPtr(unmanaged)).Target);
|
||||
if (managedArray == null || managedArray.Length != arr.Length)
|
||||
managedArray = new T[arr.Length];
|
||||
if (sourceArray == null || sourceArray.Length != arr.Length)
|
||||
sourceArray = new T[arr.Length];
|
||||
}
|
||||
|
||||
public ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(int numElements)
|
||||
{
|
||||
if (unmanagedArray == null)
|
||||
if (managedArray == null)
|
||||
return ReadOnlySpan<TUnmanagedElement>.Empty;
|
||||
return unmanagedArray.ToSpan<TUnmanagedElement>();
|
||||
return managedArray.ToSpan<TUnmanagedElement>();
|
||||
}
|
||||
|
||||
public Span<T> GetManagedValuesDestination(int numElements) => managedArray;
|
||||
public Span<T> GetManagedValuesDestination(int numElements) => sourceArray;
|
||||
|
||||
public T[] ToManaged() => managedArray;
|
||||
public T[] ToManaged() => sourceArray;
|
||||
|
||||
public void Free()
|
||||
{
|
||||
unmanagedArray.FreePooled();
|
||||
handle.Free();
|
||||
}
|
||||
public void Free() => managedArray.FreePooled();
|
||||
}
|
||||
|
||||
public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[] managed, out int numElements)
|
||||
@@ -484,9 +465,8 @@ namespace FlaxEngine.Interop
|
||||
return null;
|
||||
}
|
||||
numElements = managed.Length;
|
||||
ManagedArray managedArray = ManagedArray.AllocatePooledArray<TUnmanagedElement>(managed.Length);
|
||||
IntPtr handle = ManagedHandle.ToIntPtr(managedArray);
|
||||
return (TUnmanagedElement*)handle;
|
||||
(ManagedHandle managedArrayHandle, _) = ManagedArray.AllocatePooledArray<TUnmanagedElement>(managed.Length);
|
||||
return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArrayHandle);
|
||||
}
|
||||
|
||||
public static ReadOnlySpan<T> GetManagedValuesSource(T[] managed) => managed;
|
||||
@@ -517,7 +497,6 @@ namespace FlaxEngine.Interop
|
||||
return;
|
||||
ManagedHandle handle = ManagedHandle.FromIntPtr(new IntPtr(unmanaged));
|
||||
Unsafe.As<ManagedArray>(handle.Target).FreePooled();
|
||||
handle.Free();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -526,7 +526,7 @@ namespace FlaxEngine.Interop
|
||||
{
|
||||
Type elementType = Unsafe.As<Type>(typeHandle.Target);
|
||||
Type marshalledType = ArrayFactory.GetMarshalledType(elementType);
|
||||
Type arrayType = elementType.MakeArrayType();
|
||||
Type arrayType = ArrayFactory.GetArrayType(elementType);
|
||||
if (marshalledType.IsValueType)
|
||||
{
|
||||
ManagedArray managedArray = ManagedArray.AllocateNewArray((int)size, arrayType, marshalledType);
|
||||
@@ -544,7 +544,7 @@ namespace FlaxEngine.Interop
|
||||
internal static ManagedHandle GetArrayTypeFromElementType(ManagedHandle elementTypeHandle)
|
||||
{
|
||||
Type elementType = Unsafe.As<Type>(elementTypeHandle.Target);
|
||||
Type classType = elementType.MakeArrayType();
|
||||
Type classType = ArrayFactory.GetArrayType(elementType);
|
||||
return GetTypeGCHandle(classType);
|
||||
}
|
||||
|
||||
@@ -649,8 +649,6 @@ namespace FlaxEngine.Interop
|
||||
Type type = value.GetType();
|
||||
if (!type.IsValueType)
|
||||
return ManagedHandle.ToIntPtr(handle);
|
||||
|
||||
// HACK: Get the address of a non-pinned value
|
||||
return ValueTypeUnboxer.GetPointer(value, type);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ using FlaxEngine.Assertions;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace FlaxEngine.Interop
|
||||
{
|
||||
@@ -967,6 +968,7 @@ namespace FlaxEngine.Interop
|
||||
private delegate Array CreateArrayDelegate(long size);
|
||||
|
||||
private static ConcurrentDictionary<Type, Type> marshalledTypes = new ConcurrentDictionary<Type, Type>(1, 3);
|
||||
private static ConcurrentDictionary<Type, Type> arrayTypes = new ConcurrentDictionary<Type, Type>(1, 3);
|
||||
private static ConcurrentDictionary<Type, CreateArrayDelegate> createArrayDelegates = new ConcurrentDictionary<Type, CreateArrayDelegate>(1, 3);
|
||||
|
||||
internal static Type GetMarshalledType(Type elementType)
|
||||
@@ -986,6 +988,15 @@ namespace FlaxEngine.Interop
|
||||
return marshalledTypes.GetOrAdd(elementType, Factory);
|
||||
}
|
||||
|
||||
internal static Type GetArrayType(Type elementType)
|
||||
{
|
||||
static Type Factory(Type type) => type.MakeArrayType();
|
||||
|
||||
if (arrayTypes.TryGetValue(elementType, out var arrayType))
|
||||
return arrayType;
|
||||
return arrayTypes.GetOrAdd(elementType, Factory);
|
||||
}
|
||||
|
||||
internal static Array CreateArray(Type type, long size)
|
||||
{
|
||||
static CreateArrayDelegate Factory(Type type)
|
||||
@@ -1009,44 +1020,79 @@ namespace FlaxEngine.Interop
|
||||
|
||||
internal static class ValueTypeUnboxer
|
||||
{
|
||||
private delegate IntPtr UnboxerDelegate(object value);
|
||||
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 uint pinnedAllocationsPointer = 0;
|
||||
|
||||
private delegate TInternal ToNativeDelegate<T, TInternal>(T value);
|
||||
private delegate IntPtr UnboxerDelegate(object value, object converter);
|
||||
|
||||
private static ConcurrentDictionary<Type, UnboxerDelegate> unboxers = new ConcurrentDictionary<Type, UnboxerDelegate>(1, 3);
|
||||
private static ConcurrentDictionary<Type, (UnboxerDelegate deleg, object toNativeDeleg)> 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);
|
||||
|
||||
internal static IntPtr GetPointer(object value, Type type)
|
||||
{
|
||||
if (!unboxers.TryGetValue(type, out var deleg))
|
||||
if (!unboxers.TryGetValue(type, out var tuple))
|
||||
{
|
||||
// Non-POD structures use internal layout (eg. SpriteHandleManaged in C++ with SpriteHandleMarshaller.SpriteHandleInternal in C#) so convert C# data into C++ data
|
||||
var attr = type.GetCustomAttribute<System.Runtime.InteropServices.Marshalling.NativeMarshallingAttribute>();
|
||||
var toNativeMethod = attr?.NativeType.GetMethod("ToNative", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
if (toNativeMethod != null)
|
||||
{
|
||||
deleg = unboxerToNativeMethod.MakeGenericMethod(toNativeMethod.ReturnType).CreateDelegate<UnboxerDelegate>();
|
||||
tuple.deleg = unboxerToNativeMethod.MakeGenericMethod(type, toNativeMethod.ReturnType).CreateDelegate<UnboxerDelegate>();
|
||||
tuple.toNativeDeleg = toNativeMethod.CreateDelegate(typeof(ToNativeDelegate<,>).MakeGenericType(type, toNativeMethod.ReturnType));
|
||||
}
|
||||
else
|
||||
{
|
||||
deleg = unboxerMethod.MakeGenericMethod(type).CreateDelegate<UnboxerDelegate>();
|
||||
tuple.deleg = unboxerMethod.MakeGenericMethod(type).CreateDelegate<UnboxerDelegate>();
|
||||
}
|
||||
deleg = unboxers.GetOrAdd(type, deleg);
|
||||
tuple = unboxers.GetOrAdd(type, tuple);
|
||||
}
|
||||
return deleg(value);
|
||||
return tuple.deleg(value, tuple.toNativeDeleg);
|
||||
}
|
||||
|
||||
private static IntPtr UnboxPointer<T>(object value) where T : struct
|
||||
private static void PinValue(object value)
|
||||
{
|
||||
// Prevent garbage collector from relocating the boxed value by pinning it temporarily.
|
||||
// The pointer should remain valid quite long time but will be eventually unpinned.
|
||||
uint index = Interlocked.Increment(ref pinnedBoxedValuesPointer) % (uint)pinnedBoxedValues.Length;
|
||||
ref GCHandle handle = ref pinnedBoxedValues[index];
|
||||
if (handle.IsAllocated)
|
||||
handle.Free();
|
||||
handle = GCHandle.Alloc(value, GCHandleType.Pinned);
|
||||
}
|
||||
|
||||
private static IntPtr PinValue<T>(T value) where T : struct
|
||||
{
|
||||
// Store the converted value in unmanaged memory so it will not be relocated by the garbage collector.
|
||||
int size = Unsafe.SizeOf<T>();
|
||||
uint index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length;
|
||||
ref (IntPtr ptr, int size) alloc = ref pinnedAllocations[index];
|
||||
if (alloc.size < size)
|
||||
{
|
||||
if (alloc.ptr != IntPtr.Zero)
|
||||
NativeFree(alloc.ptr.ToPointer());
|
||||
alloc.ptr = new IntPtr(NativeAlloc(size));
|
||||
alloc.size = size;
|
||||
}
|
||||
Unsafe.Write<T>(alloc.ptr.ToPointer(), value);
|
||||
return alloc.ptr;
|
||||
}
|
||||
|
||||
private static IntPtr UnboxPointer<T>(object value, object converter) where T : struct
|
||||
{
|
||||
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()) // Cannot pin structure with references
|
||||
return IntPtr.Zero;
|
||||
PinValue(value);
|
||||
return new IntPtr(Unsafe.AsPointer(ref Unsafe.Unbox<T>(value)));
|
||||
}
|
||||
|
||||
private static IntPtr UnboxPointerWithConverter<T>(object value) where T : struct
|
||||
private static IntPtr UnboxPointerWithConverter<T, TInternal>(object value, object converter) where T : struct where TInternal : struct
|
||||
{
|
||||
var type = value.GetType();
|
||||
var attr = type.GetCustomAttribute<System.Runtime.InteropServices.Marshalling.NativeMarshallingAttribute>();
|
||||
var toNative = attr.NativeType.GetMethod("ToNative", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
value = toNative.Invoke(null, new[] { value });
|
||||
return new IntPtr(Unsafe.AsPointer(ref Unsafe.Unbox<T>(value)));
|
||||
ToNativeDelegate<T, TInternal> toNative = Unsafe.As<ToNativeDelegate<T, TInternal>>(converter);
|
||||
return PinValue<TInternal>(toNative(Unsafe.Unbox<T>(value)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,6 +135,52 @@ void Screen::SetCursorLock(CursorLockMode mode)
|
||||
CursorLock = mode;
|
||||
}
|
||||
|
||||
GameWindowMode Screen::GetGameWindowMode()
|
||||
{
|
||||
GameWindowMode result = GameWindowMode::Windowed;
|
||||
#if !USE_EDITOR
|
||||
auto win = Engine::MainWindow;
|
||||
if (win)
|
||||
{
|
||||
if (GetIsFullscreen() || win->IsFullscreen())
|
||||
result = GameWindowMode::Fullscreen;
|
||||
else if (win->GetSettings().HasBorder)
|
||||
result = GameWindowMode::Windowed;
|
||||
else if (win->GetClientPosition().IsZero() && win->GetSize() == Platform::GetDesktopSize())
|
||||
result = GameWindowMode::FullscreenBorderless;
|
||||
else
|
||||
result = GameWindowMode::Borderless;
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
void Screen::SetGameWindowMode(GameWindowMode windowMode)
|
||||
{
|
||||
#if !USE_EDITOR
|
||||
auto win = Engine::MainWindow;
|
||||
if (!win)
|
||||
return;
|
||||
switch (windowMode)
|
||||
{
|
||||
case GameWindowMode::Windowed:
|
||||
if (GetIsFullscreen())
|
||||
SetIsFullscreen(false);
|
||||
win->SetBorderless(false, false);
|
||||
break;
|
||||
case GameWindowMode::Fullscreen:
|
||||
SetIsFullscreen(true);
|
||||
break;
|
||||
case GameWindowMode::Borderless:
|
||||
win->SetBorderless(true, false);
|
||||
break;
|
||||
case GameWindowMode::FullscreenBorderless:
|
||||
win->SetBorderless(true, true);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ScreenService::Update()
|
||||
{
|
||||
#if USE_EDITOR
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Config/PlatformSettingsBase.h"
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
#include "Engine/Input/Enums.h"
|
||||
#include "Engine/Core/Math/Vector2.h"
|
||||
@@ -80,4 +81,19 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Screen);
|
||||
/// </summary>
|
||||
/// <param name="mode">The mode.</param>
|
||||
API_PROPERTY() static void SetCursorLock(CursorLockMode mode);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the game window mode.
|
||||
/// </summary>
|
||||
/// <returns>The current window mode.</returns>
|
||||
API_PROPERTY() static GameWindowMode GetGameWindowMode();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the game window mode.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A fullscreen mode switch may not happen immediately. It will be performed before next frame rendering. Will not work in editor.
|
||||
/// </remarks>
|
||||
/// <param name="windowMode">The window mode.</param>
|
||||
API_PROPERTY() static void SetGameWindowMode(GameWindowMode windowMode);
|
||||
};
|
||||
|
||||
@@ -333,6 +333,8 @@ void Keyboard::OnCharInput(Char c, Window* target)
|
||||
|
||||
void Keyboard::OnKeyUp(KeyboardKeys key, Window* target)
|
||||
{
|
||||
if (key >= KeyboardKeys::MAX)
|
||||
return;
|
||||
Event& e = _queue.AddOne();
|
||||
e.Type = EventType::KeyUp;
|
||||
e.Target = target;
|
||||
@@ -341,6 +343,8 @@ void Keyboard::OnKeyUp(KeyboardKeys key, Window* target)
|
||||
|
||||
void Keyboard::OnKeyDown(KeyboardKeys key, Window* target)
|
||||
{
|
||||
if (key >= KeyboardKeys::MAX)
|
||||
return;
|
||||
Event& e = _queue.AddOne();
|
||||
e.Type = EventType::KeyDown;
|
||||
e.Target = target;
|
||||
|
||||
@@ -675,20 +675,12 @@ bool Prefab::ApplyAll(Actor* targetActor)
|
||||
}
|
||||
}
|
||||
|
||||
// Setup default instances
|
||||
for (int32 i = 0; i < allPrefabs.Count(); i++)
|
||||
// Setup default instances (skip invalid prefabs)
|
||||
for (int32 i = allPrefabs.Count() - 1; i >= 0; i--)
|
||||
{
|
||||
Prefab* prefab = allPrefabs[i];
|
||||
if (prefab->WaitForLoaded())
|
||||
{
|
||||
LOG(Warning, "Waiting for nesting prefab asset load failed.");
|
||||
return true;
|
||||
}
|
||||
if (prefab->GetDefaultInstance() == nullptr)
|
||||
{
|
||||
LOG(Warning, "Failed to create default prefab instance for the nested prefab asset.");
|
||||
return true;
|
||||
}
|
||||
if (prefab->WaitForLoaded() || prefab->GetDefaultInstance() == nullptr)
|
||||
allPrefabs.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,10 +34,10 @@ void NavMeshBoundsVolume::Deserialize(DeserializeStream& stream, ISerializeModif
|
||||
|
||||
void NavMeshBoundsVolume::OnEnable()
|
||||
{
|
||||
GetScene()->Navigation.Volumes.Add(this);
|
||||
|
||||
// Base
|
||||
Actor::OnEnable();
|
||||
|
||||
GetScene()->Navigation.Volumes.Add(this);
|
||||
}
|
||||
|
||||
void NavMeshBoundsVolume::OnDisable()
|
||||
|
||||
@@ -12,6 +12,7 @@ enum class NetworkMessageIDs : uint8
|
||||
ObjectReplicate,
|
||||
ObjectReplicatePart,
|
||||
ObjectSpawn,
|
||||
ObjectSpawnPart,
|
||||
ObjectDespawn,
|
||||
ObjectRole,
|
||||
ObjectRpc,
|
||||
@@ -30,6 +31,7 @@ public:
|
||||
static void OnNetworkMessageObjectReplicate(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||
static void OnNetworkMessageObjectReplicatePart(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||
static void OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||
static void OnNetworkMessageObjectSpawnPart(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||
static void OnNetworkMessageObjectDespawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||
static void OnNetworkMessageObjectRole(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||
static void OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
|
||||
#define NETWORK_PROTOCOL_VERSION 2
|
||||
#define NETWORK_PROTOCOL_VERSION 3
|
||||
|
||||
float NetworkManager::NetworkFPS = 60.0f;
|
||||
NetworkPeer* NetworkManager::Peer = nullptr;
|
||||
@@ -131,6 +131,7 @@ namespace
|
||||
NetworkInternal::OnNetworkMessageObjectReplicate,
|
||||
NetworkInternal::OnNetworkMessageObjectReplicatePart,
|
||||
NetworkInternal::OnNetworkMessageObjectSpawn,
|
||||
NetworkInternal::OnNetworkMessageObjectSpawnPart,
|
||||
NetworkInternal::OnNetworkMessageObjectDespawn,
|
||||
NetworkInternal::OnNetworkMessageObjectRole,
|
||||
NetworkInternal::OnNetworkMessageObjectRpc,
|
||||
|
||||
@@ -66,8 +66,17 @@ PACK_STRUCT(struct NetworkMessageObjectSpawn
|
||||
{
|
||||
NetworkMessageIDs ID = NetworkMessageIDs::ObjectSpawn;
|
||||
uint32 OwnerClientId;
|
||||
uint32 OwnerSpawnId; // Unique for peer who spawned it and matches OwnerSpawnId inside following part messages
|
||||
Guid PrefabId;
|
||||
uint16 ItemsCount;
|
||||
uint16 ItemsCount; // Total items count
|
||||
uint8 UseParts : 1; // True if spawn message is header-only and all items come in the separate parts
|
||||
});
|
||||
|
||||
PACK_STRUCT(struct NetworkMessageObjectSpawnPart
|
||||
{
|
||||
NetworkMessageIDs ID = NetworkMessageIDs::ObjectSpawnPart;
|
||||
uint32 OwnerClientId;
|
||||
uint32 OwnerSpawnId;
|
||||
});
|
||||
|
||||
PACK_STRUCT(struct NetworkMessageObjectSpawnItem
|
||||
@@ -173,6 +182,12 @@ struct SpawnItem
|
||||
NetworkObjectRole Role;
|
||||
};
|
||||
|
||||
struct SpawnItemParts
|
||||
{
|
||||
NetworkMessageObjectSpawn MsgData;
|
||||
Array<NetworkMessageObjectSpawnItem> Items;
|
||||
};
|
||||
|
||||
struct SpawnGroup
|
||||
{
|
||||
Array<SpawnItem*, InlinedAllocation<8>> Items;
|
||||
@@ -198,6 +213,7 @@ namespace
|
||||
CriticalSection ObjectsLock;
|
||||
HashSet<NetworkReplicatedObject> Objects;
|
||||
Array<ReplicateItem> ReplicationParts;
|
||||
Array<SpawnItemParts> SpawnParts;
|
||||
Array<SpawnItem> SpawnQueue;
|
||||
Array<DespawnItem> DespawnQueue;
|
||||
Array<RpcItem> RpcQueue;
|
||||
@@ -213,6 +229,7 @@ namespace
|
||||
Dictionary<StringAnsiView, StringAnsi*> CSharpCachedNames;
|
||||
#endif
|
||||
Array<Guid> DespawnedObjects;
|
||||
uint32 SpawnId = 0;
|
||||
}
|
||||
|
||||
class NetworkReplicationService : public EngineService
|
||||
@@ -258,7 +275,7 @@ NetworkReplicatedObject* ResolveObject(Guid objectId)
|
||||
return it != Objects.End() ? &it->Item : nullptr;
|
||||
}
|
||||
|
||||
NetworkReplicatedObject* ResolveObject(Guid objectId, Guid parentId, char objectTypeName[128])
|
||||
NetworkReplicatedObject* ResolveObject(Guid objectId, Guid parentId, const char objectTypeName[128])
|
||||
{
|
||||
// Lookup object
|
||||
NetworkReplicatedObject* obj = ResolveObject(objectId);
|
||||
@@ -398,8 +415,33 @@ FORCE_INLINE void GetNetworkName(char buffer[128], const StringAnsiView& name)
|
||||
buffer[name.Length()] = 0;
|
||||
}
|
||||
|
||||
void SetupObjectSpawnMessageItem(SpawnItem* e, NetworkMessage& msg)
|
||||
{
|
||||
ScriptingObject* obj = e->Object.Get();
|
||||
auto it = Objects.Find(obj->GetID());
|
||||
const auto& item = it->Item;
|
||||
|
||||
// Add object into spawn message
|
||||
NetworkMessageObjectSpawnItem msgDataItem;
|
||||
msgDataItem.ObjectId = item.ObjectId;
|
||||
msgDataItem.ParentId = item.ParentId;
|
||||
if (NetworkManager::IsClient())
|
||||
{
|
||||
// Remap local client object ids into server ids
|
||||
IdsRemappingTable.KeyOf(msgDataItem.ObjectId, &msgDataItem.ObjectId);
|
||||
IdsRemappingTable.KeyOf(msgDataItem.ParentId, &msgDataItem.ParentId);
|
||||
}
|
||||
msgDataItem.PrefabObjectID = Guid::Empty;
|
||||
auto* objScene = ScriptingObject::Cast<SceneObject>(obj);
|
||||
if (objScene && objScene->HasPrefabLink())
|
||||
msgDataItem.PrefabObjectID = objScene->GetPrefabObjectID();
|
||||
GetNetworkName(msgDataItem.ObjectTypeName, obj->GetType().Fullname);
|
||||
msg.WriteStructure(msgDataItem);
|
||||
}
|
||||
|
||||
void SendObjectSpawnMessage(const SpawnGroup& group, const Array<NetworkClient*>& clients)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
const bool isClient = NetworkManager::IsClient();
|
||||
auto* peer = NetworkManager::Peer;
|
||||
NetworkMessage msg = peer->BeginSendMessage();
|
||||
@@ -415,37 +457,56 @@ void SendObjectSpawnMessage(const SpawnGroup& group, const Array<NetworkClient*>
|
||||
|
||||
// Setup clients that should receive this spawn message
|
||||
auto it = Objects.Find(obj->GetID());
|
||||
auto& item = it->Item;
|
||||
const auto& item = it->Item;
|
||||
BuildCachedTargets(clients, item.TargetClientIds);
|
||||
}
|
||||
msg.WriteStructure(msgData);
|
||||
for (SpawnItem* e : group.Items)
|
||||
{
|
||||
ScriptingObject* obj = e->Object.Get();
|
||||
auto it = Objects.Find(obj->GetID());
|
||||
auto& item = it->Item;
|
||||
|
||||
// Add object into spawn message
|
||||
NetworkMessageObjectSpawnItem msgDataItem;
|
||||
msgDataItem.ObjectId = item.ObjectId;
|
||||
msgDataItem.ParentId = item.ParentId;
|
||||
// Network Peer has fixed size of messages so split spawn message into parts if there are too many objects to fit at once
|
||||
msgData.OwnerSpawnId = ++SpawnId;
|
||||
msgData.UseParts = msg.BufferSize - msg.Position < group.Items.Count() * sizeof(NetworkMessageObjectSpawnItem);
|
||||
msg.WriteStructure(msgData);
|
||||
if (msgData.UseParts)
|
||||
{
|
||||
if (isClient)
|
||||
peer->EndSendMessage(NetworkChannelType::Reliable, msg);
|
||||
else
|
||||
peer->EndSendMessage(NetworkChannelType::Reliable, msg, CachedTargets);
|
||||
|
||||
// Send spawn items in separate parts
|
||||
NetworkMessageObjectSpawnPart msgDataPart;
|
||||
msgDataPart.OwnerClientId = msgData.OwnerClientId;
|
||||
msgDataPart.OwnerSpawnId = msgData.OwnerSpawnId;
|
||||
uint16 itemIndex = 0;
|
||||
constexpr uint32 spawnItemMaxSize = sizeof(uint16) + sizeof(NetworkMessageObjectSpawnItem); // Index + Data
|
||||
while (itemIndex < msgData.ItemsCount)
|
||||
{
|
||||
// Remap local client object ids into server ids
|
||||
IdsRemappingTable.KeyOf(msgDataItem.ObjectId, &msgDataItem.ObjectId);
|
||||
IdsRemappingTable.KeyOf(msgDataItem.ParentId, &msgDataItem.ParentId);
|
||||
msg = peer->BeginSendMessage();
|
||||
msg.WriteStructure(msgDataPart);
|
||||
|
||||
// Write as many items as possible into this message
|
||||
while (msg.Position + spawnItemMaxSize <= msg.BufferSize && itemIndex < msgData.ItemsCount)
|
||||
{
|
||||
msg.WriteUInt16(itemIndex);
|
||||
SetupObjectSpawnMessageItem(group.Items[itemIndex], msg);
|
||||
itemIndex++;
|
||||
}
|
||||
|
||||
if (isClient)
|
||||
peer->EndSendMessage(NetworkChannelType::Reliable, msg);
|
||||
else
|
||||
peer->EndSendMessage(NetworkChannelType::Reliable, msg, CachedTargets);
|
||||
}
|
||||
msgDataItem.PrefabObjectID = Guid::Empty;
|
||||
auto* objScene = ScriptingObject::Cast<SceneObject>(obj);
|
||||
if (objScene && objScene->HasPrefabLink())
|
||||
msgDataItem.PrefabObjectID = objScene->GetPrefabObjectID();
|
||||
GetNetworkName(msgDataItem.ObjectTypeName, obj->GetType().Fullname);
|
||||
msg.WriteStructure(msgDataItem);
|
||||
}
|
||||
if (isClient)
|
||||
peer->EndSendMessage(NetworkChannelType::Reliable, msg);
|
||||
else
|
||||
peer->EndSendMessage(NetworkChannelType::Reliable, msg, CachedTargets);
|
||||
{
|
||||
// Send all spawn items within the spawn message
|
||||
for (SpawnItem* e : group.Items)
|
||||
SetupObjectSpawnMessageItem(e, msg);
|
||||
if (isClient)
|
||||
peer->EndSendMessage(NetworkChannelType::Reliable, msg);
|
||||
else
|
||||
peer->EndSendMessage(NetworkChannelType::Reliable, msg, CachedTargets);
|
||||
}
|
||||
}
|
||||
|
||||
void SendObjectRoleMessage(const NetworkReplicatedObject& item, const NetworkClient* excludedClient = nullptr)
|
||||
@@ -655,6 +716,235 @@ void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, b
|
||||
DirtyObjectImpl(item, obj);
|
||||
}
|
||||
|
||||
void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const NetworkMessageObjectSpawnItem* msgDataItems)
|
||||
{
|
||||
ScopeLock lock(ObjectsLock);
|
||||
|
||||
// Check if that object has been already spawned
|
||||
auto& rootItem = msgDataItems[0];
|
||||
NetworkReplicatedObject* root = ResolveObject(rootItem.ObjectId, rootItem.ParentId, rootItem.ObjectTypeName);
|
||||
if (root)
|
||||
{
|
||||
// Object already exists locally so just synchronize the ownership (and mark as spawned)
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
NetworkReplicatedObject* e = ResolveObject(msgDataItem.ObjectId, msgDataItem.ParentId, msgDataItem.ObjectTypeName);
|
||||
auto& item = *e;
|
||||
item.Spawned = true;
|
||||
if (NetworkManager::IsClient())
|
||||
{
|
||||
// Server always knows the best so update ownership of the existing object
|
||||
item.OwnerClientId = msgData.OwnerClientId;
|
||||
if (item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
{
|
||||
if (Hierarchy)
|
||||
Hierarchy->AddObject(item.Object);
|
||||
item.Role = NetworkObjectRole::Replicated;
|
||||
}
|
||||
}
|
||||
else if (item.OwnerClientId != msgData.OwnerClientId)
|
||||
{
|
||||
// Other client spawned object with a different owner
|
||||
// TODO: send reply message to inform about proper object ownership that client
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Recreate object locally (spawn only root)
|
||||
Actor* prefabInstance = nullptr;
|
||||
Array<ScriptingObject*> objects;
|
||||
if (msgData.PrefabId.IsValid())
|
||||
{
|
||||
const NetworkReplicatedObject* parent = ResolveObject(rootItem.ParentId);
|
||||
Actor* parentActor = parent && parent->Object && parent->Object->Is<Actor>() ? parent->Object.As<Actor>() : nullptr;
|
||||
if (parentActor && parentActor->GetPrefabID() == msgData.PrefabId)
|
||||
{
|
||||
// Reuse parent object as prefab instance
|
||||
prefabInstance = parentActor;
|
||||
}
|
||||
else if ((parentActor = Scripting::TryFindObject<Actor>(rootItem.ParentId)))
|
||||
{
|
||||
// Try to find that spawned prefab (eg. prefab with networked script was spawned before so now we need to link it)
|
||||
for (Actor* child : parentActor->Children)
|
||||
{
|
||||
if (child->GetPrefabID() == msgData.PrefabId)
|
||||
{
|
||||
if (Objects.Contains(child->GetID()))
|
||||
{
|
||||
ScriptingObject* obj = FindPrefabObject(child, rootItem.PrefabObjectID);
|
||||
if (Objects.Contains(obj->GetID()))
|
||||
{
|
||||
// Other instance with already spawned network object
|
||||
obj = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reuse already spawned object within a parent
|
||||
prefabInstance = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!prefabInstance)
|
||||
{
|
||||
// Spawn prefab
|
||||
auto prefab = (Prefab*)LoadAsset(msgData.PrefabId, Prefab::TypeInitializer);
|
||||
if (!prefab)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find prefab {}", msgData.PrefabId.ToString());
|
||||
return;
|
||||
}
|
||||
prefabInstance = PrefabManager::SpawnPrefab(prefab, nullptr, nullptr);
|
||||
if (!prefabInstance)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", msgData.PrefabId.ToString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve objects from prefab instance
|
||||
objects.Resize(msgData.ItemsCount);
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
ScriptingObject* obj = FindPrefabObject(prefabInstance, msgDataItem.PrefabObjectID);
|
||||
if (!obj)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} in prefab {}", msgDataItem.PrefabObjectID.ToString(), msgData.PrefabId.ToString());
|
||||
Delete(prefabInstance);
|
||||
return;
|
||||
}
|
||||
objects[i] = obj;
|
||||
}
|
||||
}
|
||||
else if (msgData.ItemsCount == 1)
|
||||
{
|
||||
// Spawn object
|
||||
const ScriptingTypeHandle objectType = Scripting::FindScriptingType(rootItem.ObjectTypeName);
|
||||
ScriptingObject* obj = ScriptingObject::NewObject(objectType);
|
||||
if (!obj)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(rootItem.ObjectTypeName));
|
||||
return;
|
||||
}
|
||||
objects.Add(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Spawn objects
|
||||
objects.Resize(msgData.ItemsCount);
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
const ScriptingTypeHandle objectType = Scripting::FindScriptingType(msgDataItem.ObjectTypeName);
|
||||
ScriptingObject* obj = ScriptingObject::NewObject(objectType);
|
||||
if (!obj)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(msgDataItem.ObjectTypeName));
|
||||
for (ScriptingObject* e : objects)
|
||||
Delete(e);
|
||||
return;
|
||||
}
|
||||
objects[i] = obj;
|
||||
if (i != 0)
|
||||
{
|
||||
// Link hierarchy of spawned objects before calling any networking code for them
|
||||
if (auto sceneObject = ScriptingObject::Cast<SceneObject>(obj))
|
||||
{
|
||||
Actor* parent = nullptr;
|
||||
for (int32 j = 0; j < i; j++)
|
||||
{
|
||||
if (msgDataItems[j].ObjectId == msgDataItem.ParentId)
|
||||
{
|
||||
parent = ScriptingObject::Cast<Actor>(objects[j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (parent)
|
||||
sceneObject->SetParent(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add all newly spawned objects
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
ScriptingObject* obj = objects[i];
|
||||
if (!obj->IsRegistered())
|
||||
obj->RegisterObject();
|
||||
const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId);
|
||||
|
||||
// Add object to the list
|
||||
NetworkReplicatedObject item;
|
||||
item.Object = obj;
|
||||
item.AsNetworkObject = ScriptingObject::ToInterface<INetworkObject>(obj);
|
||||
item.ObjectId = obj->GetID();
|
||||
item.ParentId = parent ? parent->ObjectId : Guid::Empty;
|
||||
item.OwnerClientId = msgData.OwnerClientId;
|
||||
item.Role = NetworkObjectRole::Replicated;
|
||||
if (item.OwnerClientId == NetworkManager::LocalClientId)
|
||||
{
|
||||
// Upgrade ownership automatically (eg. server spawned object that local client should own)
|
||||
item.Role = NetworkObjectRole::OwnedAuthoritative;
|
||||
}
|
||||
item.Spawned = true;
|
||||
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Add new object {}:{}, parent {}:{}", item.ToString(), obj->GetType().ToString(), item.ParentId.ToString(), parent ? parent->Object->GetType().ToString() : String::Empty);
|
||||
Objects.Add(MoveTemp(item));
|
||||
if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
Hierarchy->AddObject(obj);
|
||||
|
||||
// Boost future lookups by using indirection
|
||||
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Remap object ID={} into object {}:{}", msgDataItem.ObjectId, item.ToString(), obj->GetType().ToString());
|
||||
IdsRemappingTable.Add(msgDataItem.ObjectId, item.ObjectId);
|
||||
}
|
||||
|
||||
// Spawn all newly spawned objects (ensure to have valid ownership hierarchy set before spawning object)
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
ScriptingObject* obj = objects[i];
|
||||
auto it = Objects.Find(obj->GetID());
|
||||
auto& item = it->Item;
|
||||
const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId);
|
||||
|
||||
// Automatic parenting for scene objects
|
||||
auto sceneObject = ScriptingObject::Cast<SceneObject>(obj);
|
||||
if (sceneObject)
|
||||
{
|
||||
if (parent && parent->Object.Get() && parent->Object->Is<Actor>())
|
||||
sceneObject->SetParent(parent->Object.As<Actor>());
|
||||
else if (auto* parentActor = Scripting::TryFindObject<Actor>(msgDataItem.ParentId))
|
||||
sceneObject->SetParent(parentActor);
|
||||
else if (msgDataItem.ParentId.IsValid())
|
||||
{
|
||||
#if USE_NETWORK_REPLICATOR_LOG
|
||||
// Ignore case when parent object in a message was a scene (eg. that is already unloaded on a client)
|
||||
AssetInfo assetInfo;
|
||||
if (!Content::GetAssetInfo(msgDataItem.ParentId, assetInfo) || assetInfo.TypeName != TEXT("FlaxEngine.SceneAsset"))
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else if (!parent && msgDataItem.ParentId.IsValid())
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString());
|
||||
}
|
||||
|
||||
if (item.AsNetworkObject)
|
||||
item.AsNetworkObject->OnNetworkSpawn();
|
||||
}
|
||||
|
||||
// TODO: if we're server then spawn this object further on other clients (use TargetClientIds for that object - eg. object spawned by client on client for certain set of other clients only)
|
||||
}
|
||||
|
||||
NetworkRpcParams::NetworkRpcParams(const NetworkStream* stream)
|
||||
: SenderId(stream->SenderId)
|
||||
{
|
||||
@@ -941,6 +1231,13 @@ bool NetworkReplicator::HasObject(const ScriptingObject* obj)
|
||||
return false;
|
||||
}
|
||||
|
||||
ScriptingObject* NetworkReplicator::ResolveForeignObject(Guid objectId)
|
||||
{
|
||||
if (const auto& object = ResolveObject(objectId))
|
||||
return object->Object.Get();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32 NetworkReplicator::GetObjectOwnerClientId(const ScriptingObject* obj)
|
||||
{
|
||||
uint32 id = NetworkManager::ServerClientId;
|
||||
@@ -1127,13 +1424,6 @@ bool NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHa
|
||||
rpc.Info = *info;
|
||||
rpc.ArgsData.Copy(Span<byte>(argsStream->GetBuffer(), argsStream->GetPosition()));
|
||||
rpc.Targets.Copy(targetIds);
|
||||
#if USE_EDITOR || !BUILD_RELEASE
|
||||
auto it = Objects.Find(obj->GetID());
|
||||
if (it == Objects.End())
|
||||
{
|
||||
LOG(Error, "Cannot invoke RPC method '{0}.{1}' on object '{2}' that is not registered in networking (use 'NetworkReplicator.AddObject').", type.ToString(), String(name), obj->GetID());
|
||||
}
|
||||
#endif
|
||||
ObjectsLock.Unlock();
|
||||
|
||||
// Check if skip local execution (eg. server rpc called from client or client rpc with specific targets)
|
||||
@@ -1388,7 +1678,7 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
||||
auto& e = ReplicationParts[i];
|
||||
if (e.PartsLeft > 0)
|
||||
{
|
||||
// TODO: remove replication items after some TTL to prevent memory leaks
|
||||
// TODO: remove replication items after some TTL to reduce memory usage
|
||||
continue;
|
||||
}
|
||||
ScriptingObject* obj = e.Object.Get();
|
||||
@@ -1408,13 +1698,15 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove items from SpawnParts after some TTL to reduce memory usage
|
||||
|
||||
// Replicate all owned networked objects with other clients or server
|
||||
if (!CachedReplicationResult)
|
||||
CachedReplicationResult = New<NetworkReplicationHierarchyUpdateResult>();
|
||||
CachedReplicationResult->Init();
|
||||
if (!isClient && NetworkManager::Clients.IsEmpty())
|
||||
if ((!isClient && NetworkManager::Clients.IsEmpty()) || NetworkManager::NetworkFPS < -ZeroTolerance)
|
||||
{
|
||||
// No need to update replication when nobody's around
|
||||
// No need to update replication when nobody's around or when replication is disabled
|
||||
}
|
||||
else if (Hierarchy)
|
||||
{
|
||||
@@ -1554,7 +1846,13 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
||||
continue;
|
||||
auto it = Objects.Find(obj->GetID());
|
||||
if (it == Objects.End())
|
||||
{
|
||||
#if USE_EDITOR || !BUILD_RELEASE
|
||||
if (!DespawnedObjects.Contains(obj->GetID()))
|
||||
LOG(Error, "Cannot invoke RPC method '{0}.{1}' on object '{2}' that is not registered in networking (use 'NetworkReplicator.AddObject').", e.Name.First.ToString(), String(e.Name.Second), obj->GetID());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
auto& item = it->Item;
|
||||
|
||||
// Send RPC message
|
||||
@@ -1649,234 +1947,60 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl
|
||||
PROFILE_CPU();
|
||||
NetworkMessageObjectSpawn msgData;
|
||||
event.Message.ReadStructure(msgData);
|
||||
auto* msgDataItems = (NetworkMessageObjectSpawnItem*)event.Message.SkipBytes(msgData.ItemsCount * sizeof(NetworkMessageObjectSpawnItem));
|
||||
if (msgData.ItemsCount == 0)
|
||||
return;
|
||||
ScopeLock lock(ObjectsLock);
|
||||
|
||||
// Check if that object has been already spawned
|
||||
auto& rootItem = msgDataItems[0];
|
||||
NetworkReplicatedObject* root = ResolveObject(rootItem.ObjectId, rootItem.ParentId, rootItem.ObjectTypeName);
|
||||
if (root)
|
||||
if (msgData.UseParts)
|
||||
{
|
||||
// Object already exists locally so just synchronize the ownership (and mark as spawned)
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
NetworkReplicatedObject* e = ResolveObject(msgDataItem.ObjectId, msgDataItem.ParentId, msgDataItem.ObjectTypeName);
|
||||
auto& item = *e;
|
||||
item.Spawned = true;
|
||||
if (NetworkManager::IsClient())
|
||||
{
|
||||
// Server always knows the best so update ownership of the existing object
|
||||
item.OwnerClientId = msgData.OwnerClientId;
|
||||
if (item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
{
|
||||
if (Hierarchy)
|
||||
Hierarchy->AddObject(item.Object);
|
||||
item.Role = NetworkObjectRole::Replicated;
|
||||
}
|
||||
}
|
||||
else if (item.OwnerClientId != msgData.OwnerClientId)
|
||||
{
|
||||
// Other client spawned object with a different owner
|
||||
// TODO: send reply message to inform about proper object ownership that client
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Recreate object locally (spawn only root)
|
||||
Actor* prefabInstance = nullptr;
|
||||
Array<ScriptingObject*> objects;
|
||||
if (msgData.PrefabId.IsValid())
|
||||
{
|
||||
const NetworkReplicatedObject* parent = ResolveObject(rootItem.ParentId);
|
||||
Actor* parentActor = parent && parent->Object && parent->Object->Is<Actor>() ? parent->Object.As<Actor>() : nullptr;
|
||||
if (parentActor && parentActor->GetPrefabID() == msgData.PrefabId)
|
||||
{
|
||||
// Reuse parent object as prefab instance
|
||||
prefabInstance = parentActor;
|
||||
}
|
||||
else if ((parentActor = Scripting::TryFindObject<Actor>(rootItem.ParentId)))
|
||||
{
|
||||
// Try to find that spawned prefab (eg. prefab with networked script was spawned before so now we need to link it)
|
||||
for (Actor* child : parentActor->Children)
|
||||
{
|
||||
if (child->GetPrefabID() == msgData.PrefabId)
|
||||
{
|
||||
if (Objects.Contains(child->GetID()))
|
||||
{
|
||||
ScriptingObject* obj = FindPrefabObject(child, rootItem.PrefabObjectID);
|
||||
if (Objects.Contains(obj->GetID()))
|
||||
{
|
||||
// Other instance with already spawned network object
|
||||
obj = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reuse already spawned object within a parent
|
||||
prefabInstance = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!prefabInstance)
|
||||
{
|
||||
// Spawn prefab
|
||||
auto prefab = (Prefab*)LoadAsset(msgData.PrefabId, Prefab::TypeInitializer);
|
||||
if (!prefab)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find prefab {}", msgData.PrefabId.ToString());
|
||||
return;
|
||||
}
|
||||
prefabInstance = PrefabManager::SpawnPrefab(prefab, nullptr, nullptr);
|
||||
if (!prefabInstance)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", msgData.PrefabId.ToString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve objects from prefab instance
|
||||
objects.Resize(msgData.ItemsCount);
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
ScriptingObject* obj = FindPrefabObject(prefabInstance, msgDataItem.PrefabObjectID);
|
||||
if (!obj)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} in prefab {}", msgDataItem.PrefabObjectID.ToString(), msgData.PrefabId.ToString());
|
||||
Delete(prefabInstance);
|
||||
return;
|
||||
}
|
||||
objects[i] = obj;
|
||||
}
|
||||
}
|
||||
else if (msgData.ItemsCount == 1)
|
||||
{
|
||||
// Spawn object
|
||||
const ScriptingTypeHandle objectType = Scripting::FindScriptingType(rootItem.ObjectTypeName);
|
||||
ScriptingObject* obj = ScriptingObject::NewObject(objectType);
|
||||
if (!obj)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(rootItem.ObjectTypeName));
|
||||
return;
|
||||
}
|
||||
objects.Add(obj);
|
||||
// Allocate spawn message parts collecting
|
||||
auto& parts = SpawnParts.AddOne();
|
||||
parts.MsgData = msgData;
|
||||
parts.Items.Resize(msgData.ItemsCount);
|
||||
for (auto& item : parts.Items)
|
||||
item.ObjectId = Guid::Empty; // Mark as not yet received
|
||||
}
|
||||
else
|
||||
{
|
||||
// Spawn objects
|
||||
objects.Resize(msgData.ItemsCount);
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
const ScriptingTypeHandle objectType = Scripting::FindScriptingType(msgDataItem.ObjectTypeName);
|
||||
ScriptingObject* obj = ScriptingObject::NewObject(objectType);
|
||||
if (!obj)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(msgDataItem.ObjectTypeName));
|
||||
for (ScriptingObject* e : objects)
|
||||
Delete(e);
|
||||
return;
|
||||
}
|
||||
objects[i] = obj;
|
||||
if (i != 0)
|
||||
{
|
||||
// Link hierarchy of spawned objects before calling any networking code for them
|
||||
if (auto sceneObject = ScriptingObject::Cast<SceneObject>(obj))
|
||||
{
|
||||
Actor* parent = nullptr;
|
||||
for (int32 j = 0; j < i; j++)
|
||||
{
|
||||
if (msgDataItems[j].ObjectId == msgDataItem.ParentId)
|
||||
{
|
||||
parent = ScriptingObject::Cast<Actor>(objects[j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (parent)
|
||||
sceneObject->SetParent(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto* msgDataItems = (NetworkMessageObjectSpawnItem*)event.Message.SkipBytes(msgData.ItemsCount * sizeof(NetworkMessageObjectSpawnItem));
|
||||
InvokeObjectSpawn(msgData, msgDataItems);
|
||||
}
|
||||
}
|
||||
|
||||
// Add all newly spawned objects
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
void NetworkInternal::OnNetworkMessageObjectSpawnPart(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
NetworkMessageObjectSpawnPart msgData;
|
||||
event.Message.ReadStructure(msgData);
|
||||
int32 spawnPartsIndex;
|
||||
for (spawnPartsIndex = 0; spawnPartsIndex < SpawnParts.Count(); spawnPartsIndex++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
ScriptingObject* obj = objects[i];
|
||||
if (!obj->IsRegistered())
|
||||
obj->RegisterObject();
|
||||
const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId);
|
||||
|
||||
// Add object to the list
|
||||
NetworkReplicatedObject item;
|
||||
item.Object = obj;
|
||||
item.AsNetworkObject = ScriptingObject::ToInterface<INetworkObject>(obj);
|
||||
item.ObjectId = obj->GetID();
|
||||
item.ParentId = parent ? parent->ObjectId : Guid::Empty;
|
||||
item.OwnerClientId = msgData.OwnerClientId;
|
||||
item.Role = NetworkObjectRole::Replicated;
|
||||
if (item.OwnerClientId == NetworkManager::LocalClientId)
|
||||
{
|
||||
// Upgrade ownership automatically (eg. server spawned object that local client should own)
|
||||
item.Role = NetworkObjectRole::OwnedAuthoritative;
|
||||
}
|
||||
item.Spawned = true;
|
||||
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Add new object {}:{}, parent {}:{}", item.ToString(), obj->GetType().ToString(), item.ParentId.ToString(), parent ? parent->Object->GetType().ToString() : String::Empty);
|
||||
Objects.Add(MoveTemp(item));
|
||||
if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
Hierarchy->AddObject(obj);
|
||||
|
||||
// Boost future lookups by using indirection
|
||||
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Remap object ID={} into object {}:{}", msgDataItem.ObjectId, item.ToString(), obj->GetType().ToString());
|
||||
IdsRemappingTable.Add(msgDataItem.ObjectId, item.ObjectId);
|
||||
// Find spawn parts container that matches this spawn message (unique pair of sender and id assigned by sender)
|
||||
const auto& e = SpawnParts.Get()[spawnPartsIndex];
|
||||
if (e.MsgData.OwnerClientId == msgData.OwnerClientId && e.MsgData.OwnerSpawnId == msgData.OwnerSpawnId)
|
||||
break;
|
||||
}
|
||||
|
||||
// Spawn all newly spawned objects (ensure to have valid ownership hierarchy set before spawning object)
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
if (spawnPartsIndex >= SpawnParts.Count())
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
ScriptingObject* obj = objects[i];
|
||||
auto it = Objects.Find(obj->GetID());
|
||||
auto& item = it->Item;
|
||||
const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId);
|
||||
// Invalid part or data, ignore it
|
||||
return;
|
||||
}
|
||||
auto& spawnParts = SpawnParts.Get()[spawnPartsIndex];
|
||||
|
||||
// Automatic parenting for scene objects
|
||||
auto sceneObject = ScriptingObject::Cast<SceneObject>(obj);
|
||||
if (sceneObject)
|
||||
{
|
||||
if (parent && parent->Object.Get() && parent->Object->Is<Actor>())
|
||||
sceneObject->SetParent(parent->Object.As<Actor>());
|
||||
else if (auto* parentActor = Scripting::TryFindObject<Actor>(msgDataItem.ParentId))
|
||||
sceneObject->SetParent(parentActor);
|
||||
else if (msgDataItem.ParentId.IsValid())
|
||||
{
|
||||
#if USE_NETWORK_REPLICATOR_LOG
|
||||
// Ignore case when parent object in a message was a scene (eg. that is already unloaded on a client)
|
||||
AssetInfo assetInfo;
|
||||
if (!Content::GetAssetInfo(msgDataItem.ParentId, assetInfo) || assetInfo.TypeName != TEXT("FlaxEngine.SceneAsset"))
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else if (!parent && msgDataItem.ParentId.IsValid())
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString());
|
||||
}
|
||||
|
||||
if (item.AsNetworkObject)
|
||||
item.AsNetworkObject->OnNetworkSpawn();
|
||||
// Read all items from this part
|
||||
constexpr uint32 spawnItemMaxSize = sizeof(uint16) + sizeof(NetworkMessageObjectSpawnItem); // Index + Data
|
||||
while (event.Message.Position + spawnItemMaxSize <= event.Message.BufferSize)
|
||||
{
|
||||
const uint16 itemIndex = event.Message.ReadUInt16();
|
||||
event.Message.ReadStructure(spawnParts.Items[itemIndex]);
|
||||
}
|
||||
|
||||
// TODO: if we're server then spawn this object further on other clients (use TargetClientIds for that object - eg. object spawned by client on client for certain set of other clients only)
|
||||
// Invoke spawning if we've got all items
|
||||
for (auto& e : spawnParts.Items)
|
||||
{
|
||||
if (!e.ObjectId.IsValid())
|
||||
return;
|
||||
}
|
||||
InvokeObjectSpawn(spawnParts.MsgData, spawnParts.Items.Get());
|
||||
SpawnParts.RemoveAt(spawnPartsIndex);
|
||||
}
|
||||
|
||||
void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)
|
||||
|
||||
@@ -116,6 +116,13 @@ public:
|
||||
/// <param name="obj">The network object.</param>
|
||||
/// <returns>True if object exists in networking, otherwise false.</returns>
|
||||
API_FUNCTION() static bool HasObject(const ScriptingObject* obj);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves foreign Guid into a local ScriptingObject
|
||||
/// </summary>
|
||||
/// <param name="objectId">The Guid of a foreign object.</param>
|
||||
/// <returns>Object if managed to resolve, otherwise null.</returns>
|
||||
API_FUNCTION() static ScriptingObject* ResolveForeignObject(Guid objectId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Client Id of the network object owner.
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
#include "Engine/Particles/Types.h"
|
||||
#include "Engine/Particles/ParticlesSimulation.h"
|
||||
#include "Engine/Particles/ParticlesData.h"
|
||||
#include "Engine/Core/Types/CommonValue.h"
|
||||
#include "Engine/Core/Math/Vector4.h"
|
||||
#include "Engine/Core/Types/BaseTypes.h"
|
||||
|
||||
class ParticleEffect;
|
||||
|
||||
|
||||
@@ -40,6 +40,31 @@ const Array<WheeledVehicle::Wheel>& WheeledVehicle::GetWheels() const
|
||||
|
||||
void WheeledVehicle::SetWheels(const Array<Wheel>& value)
|
||||
{
|
||||
#if WITH_VEHICLE
|
||||
// Don't recreate whole vehicle when some wheel properties are only changed (eg. suspension)
|
||||
if (_actor && _vehicle && _wheels.Count() == value.Count() && _wheelsData.Count() == value.Count())
|
||||
{
|
||||
bool softUpdate = true;
|
||||
for (int32 wheelIndex = 0; wheelIndex < value.Count(); wheelIndex++)
|
||||
{
|
||||
auto& oldWheel = _wheels.Get()[wheelIndex];
|
||||
auto& newWheel = value.Get()[wheelIndex];
|
||||
if (oldWheel.Type != newWheel.Type ||
|
||||
Math::NotNearEqual(oldWheel.SuspensionForceOffset, newWheel.SuspensionForceOffset) ||
|
||||
oldWheel.Collider != newWheel.Collider)
|
||||
{
|
||||
softUpdate = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (softUpdate)
|
||||
{
|
||||
_wheels = value;
|
||||
PhysicsBackend::UpdateVehicleWheels(this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
_wheels = value;
|
||||
Setup();
|
||||
}
|
||||
@@ -51,6 +76,10 @@ WheeledVehicle::EngineSettings WheeledVehicle::GetEngine() const
|
||||
|
||||
void WheeledVehicle::SetEngine(const EngineSettings& value)
|
||||
{
|
||||
#if WITH_VEHICLE
|
||||
if (_vehicle)
|
||||
PhysicsBackend::SetVehicleEngine(_vehicle, &value);
|
||||
#endif
|
||||
_engine = value;
|
||||
}
|
||||
|
||||
@@ -61,6 +90,10 @@ WheeledVehicle::DifferentialSettings WheeledVehicle::GetDifferential() const
|
||||
|
||||
void WheeledVehicle::SetDifferential(const DifferentialSettings& value)
|
||||
{
|
||||
#if WITH_VEHICLE
|
||||
if (_vehicle)
|
||||
PhysicsBackend::SetVehicleDifferential(_vehicle, &value);
|
||||
#endif
|
||||
_differential = value;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <ThirdParty/PhysX/extensions/PxFixedJoint.h>
|
||||
#include <ThirdParty/PhysX/extensions/PxSphericalJoint.h>
|
||||
#if WITH_VEHICLE
|
||||
#include "Engine/Core/Collections/Sorting.h"
|
||||
#include "Engine/Physics/Actors/WheeledVehicle.h"
|
||||
#include <ThirdParty/PhysX/vehicle/PxVehicleSDK.h>
|
||||
#include <ThirdParty/PhysX/vehicle/PxVehicleUpdate.h>
|
||||
@@ -1001,7 +1002,7 @@ void PhysicsBackend::EndSimulateScene(void* scene)
|
||||
int32 wheelsCount = 0;
|
||||
for (auto wheelVehicle : scenePhysX->WheelVehicles)
|
||||
{
|
||||
if (!wheelVehicle->IsActiveInHierarchy())
|
||||
if (!wheelVehicle->IsActiveInHierarchy() || !wheelVehicle->GetEnableSimulation())
|
||||
continue;
|
||||
auto drive = (PxVehicleWheels*)wheelVehicle->_vehicle;
|
||||
ASSERT(drive);
|
||||
@@ -1216,7 +1217,7 @@ void PhysicsBackend::EndSimulateScene(void* scene)
|
||||
for (int32 i = 0, ii = 0; i < scenePhysX->WheelVehicles.Count(); i++)
|
||||
{
|
||||
auto wheelVehicle = scenePhysX->WheelVehicles[i];
|
||||
if (!wheelVehicle->IsActiveInHierarchy())
|
||||
if (!wheelVehicle->IsActiveInHierarchy() || !wheelVehicle->GetEnableSimulation())
|
||||
continue;
|
||||
auto drive = (PxVehicleWheels*)scenePhysX->WheelVehicles[ii]->_vehicle;
|
||||
auto& perVehicle = WheelVehiclesResultsPerVehicle[ii];
|
||||
@@ -1237,7 +1238,7 @@ void PhysicsBackend::EndSimulateScene(void* scene)
|
||||
for (int32 i = 0, ii = 0; i < scenePhysX->WheelVehicles.Count(); i++)
|
||||
{
|
||||
auto wheelVehicle = scenePhysX->WheelVehicles[i];
|
||||
if (!wheelVehicle->IsActiveInHierarchy())
|
||||
if (!wheelVehicle->IsActiveInHierarchy() || !wheelVehicle->GetEnableSimulation())
|
||||
continue;
|
||||
auto drive = WheelVehiclesCache[ii];
|
||||
auto& perVehicle = WheelVehiclesResultsPerVehicle[ii];
|
||||
@@ -2630,8 +2631,20 @@ int32 PhysicsBackend::MoveController(void* controller, void* shape, const Vector
|
||||
|
||||
#if WITH_VEHICLE
|
||||
|
||||
bool SortWheels(WheeledVehicle::Wheel const& a, WheeledVehicle::Wheel const& b)
|
||||
{
|
||||
return (int32)a.Type < (int32)b.Type;
|
||||
}
|
||||
|
||||
void* PhysicsBackend::CreateVehicle(WheeledVehicle* actor)
|
||||
{
|
||||
// TODO: handle PxVehicleDrive4WWheelOrder internally rather than sorting wheels directly on the vehicle
|
||||
if (actor->_driveType == WheeledVehicle::DriveTypes::Drive4W)
|
||||
{
|
||||
// Drive4W requires wheels to match order from PxVehicleDrive4WWheelOrder enum
|
||||
Sorting::QuickSort(actor->_wheels.Get(), actor->_wheels.Count(), SortWheels);
|
||||
}
|
||||
|
||||
// Get wheels
|
||||
Array<WheeledVehicle::Wheel*, FixedAllocation<PX_MAX_NB_WHEELS>> wheels;
|
||||
for (auto& wheel : actor->_wheels)
|
||||
@@ -2676,10 +2689,7 @@ void* PhysicsBackend::CreateVehicle(WheeledVehicle* actor)
|
||||
// Initialize wheels simulation data
|
||||
PxVec3 offsets[PX_MAX_NB_WHEELS];
|
||||
for (int32 i = 0; i < wheels.Count(); i++)
|
||||
{
|
||||
auto& wheel = *wheels[i];
|
||||
offsets[i] = C2P(wheel.Collider->GetLocalPosition());
|
||||
}
|
||||
offsets[i] = C2P(wheels[i]->Collider->GetLocalPosition());
|
||||
PxF32 sprungMasses[PX_MAX_NB_WHEELS];
|
||||
const float mass = actorPhysX->getMass();
|
||||
// TODO: get gravityDirection from scenePhysX->Scene->getGravity()
|
||||
@@ -2923,12 +2933,163 @@ void PhysicsBackend::DestroyVehicle(void* vehicle, int32 driveType)
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsBackend::UpdateVehicleWheels(WheeledVehicle* actor)
|
||||
{
|
||||
auto drive = (PxVehicleWheels*)actor->_vehicle;
|
||||
PxVehicleWheelsSimData* wheelsSimData = &drive->mWheelsSimData;
|
||||
for (uint32 i = 0; i < wheelsSimData->getNbWheels(); i++)
|
||||
{
|
||||
auto& wheel = actor->_wheels[i];
|
||||
|
||||
// Update suspension data
|
||||
PxVehicleSuspensionData suspensionData = wheelsSimData->getSuspensionData(i);
|
||||
const float suspensionFrequency = 7.0f;
|
||||
suspensionData.mMaxCompression = wheel.SuspensionMaxRaise;
|
||||
suspensionData.mMaxDroop = wheel.SuspensionMaxDrop;
|
||||
suspensionData.mSpringStrength = Math::Square(suspensionFrequency) * suspensionData.mSprungMass;
|
||||
suspensionData.mSpringDamperRate = wheel.SuspensionDampingRate * 2.0f * Math::Sqrt(suspensionData.mSpringStrength * suspensionData.mSprungMass);
|
||||
wheelsSimData->setSuspensionData(i, suspensionData);
|
||||
|
||||
// Update tire data
|
||||
PxVehicleTireData tire;
|
||||
int32 tireIndex = WheelTireTypes.Find(wheel.TireFrictionScale);
|
||||
if (tireIndex == -1)
|
||||
{
|
||||
// New tire type
|
||||
tireIndex = WheelTireTypes.Count();
|
||||
WheelTireTypes.Add(wheel.TireFrictionScale);
|
||||
WheelTireFrictionsDirty = true;
|
||||
}
|
||||
tire.mType = tireIndex;
|
||||
tire.mLatStiffX = wheel.TireLateralMax;
|
||||
tire.mLatStiffY = wheel.TireLateralStiffness;
|
||||
tire.mLongitudinalStiffnessPerUnitGravity = wheel.TireLongitudinalStiffness;
|
||||
wheelsSimData->setTireData(i, tire);
|
||||
|
||||
// Update wheel data
|
||||
PxVehicleWheelData wheelData;
|
||||
wheelData.mMass = wheel.Mass;
|
||||
wheelData.mRadius = wheel.Radius;
|
||||
wheelData.mWidth = wheel.Width;
|
||||
wheelData.mMOI = 0.5f * wheelData.mMass * Math::Square(wheelData.mRadius);
|
||||
wheelData.mDampingRate = M2ToCm2(wheel.DampingRate);
|
||||
wheelData.mMaxSteer = wheel.MaxSteerAngle * DegreesToRadians;
|
||||
wheelData.mMaxBrakeTorque = M2ToCm2(wheel.MaxBrakeTorque);
|
||||
wheelData.mMaxHandBrakeTorque = M2ToCm2(wheel.MaxHandBrakeTorque);
|
||||
wheelsSimData->setWheelData(i, wheelData);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsBackend::SetVehicleEngine(void* vehicle, const void* value)
|
||||
{
|
||||
auto drive = (PxVehicleDrive*)vehicle;
|
||||
auto& engine = *(const WheeledVehicle::EngineSettings*)value;
|
||||
switch (drive->getVehicleType())
|
||||
{
|
||||
case PxVehicleTypes::eDRIVE4W:
|
||||
{
|
||||
auto drive4W = (PxVehicleDrive4W*)drive;
|
||||
PxVehicleDriveSimData4W& driveSimData = drive4W->mDriveSimData;
|
||||
PxVehicleEngineData engineData;
|
||||
engineData.mMOI = M2ToCm2(engine.MOI);
|
||||
engineData.mPeakTorque = M2ToCm2(engine.MaxTorque);
|
||||
engineData.mMaxOmega = RpmToRadPerS(engine.MaxRotationSpeed);
|
||||
engineData.mDampingRateFullThrottle = M2ToCm2(0.15f);
|
||||
engineData.mDampingRateZeroThrottleClutchEngaged = M2ToCm2(2.0f);
|
||||
engineData.mDampingRateZeroThrottleClutchDisengaged = M2ToCm2(0.35f);
|
||||
driveSimData.setEngineData(engineData);
|
||||
break;
|
||||
}
|
||||
case PxVehicleTypes::eDRIVENW:
|
||||
{
|
||||
auto drive4W = (PxVehicleDriveNW*)drive;
|
||||
PxVehicleDriveSimDataNW& driveSimData = drive4W->mDriveSimData;
|
||||
PxVehicleEngineData engineData;
|
||||
engineData.mMOI = M2ToCm2(engine.MOI);
|
||||
engineData.mPeakTorque = M2ToCm2(engine.MaxTorque);
|
||||
engineData.mMaxOmega = RpmToRadPerS(engine.MaxRotationSpeed);
|
||||
engineData.mDampingRateFullThrottle = M2ToCm2(0.15f);
|
||||
engineData.mDampingRateZeroThrottleClutchEngaged = M2ToCm2(2.0f);
|
||||
engineData.mDampingRateZeroThrottleClutchDisengaged = M2ToCm2(0.35f);
|
||||
driveSimData.setEngineData(engineData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsBackend::SetVehicleDifferential(void* vehicle, const void* value)
|
||||
{
|
||||
auto drive = (PxVehicleDrive*)vehicle;
|
||||
auto& differential = *(const WheeledVehicle::DifferentialSettings*)value;
|
||||
switch (drive->getVehicleType())
|
||||
{
|
||||
case PxVehicleTypes::eDRIVE4W:
|
||||
{
|
||||
auto drive4W = (PxVehicleDrive4W*)drive;
|
||||
PxVehicleDriveSimData4W& driveSimData = drive4W->mDriveSimData;
|
||||
PxVehicleDifferential4WData differential4WData;
|
||||
differential4WData.mType = (PxVehicleDifferential4WData::Enum)differential.Type;
|
||||
differential4WData.mFrontRearSplit = differential.FrontRearSplit;
|
||||
differential4WData.mFrontLeftRightSplit = differential.FrontLeftRightSplit;
|
||||
differential4WData.mRearLeftRightSplit = differential.RearLeftRightSplit;
|
||||
differential4WData.mCentreBias = differential.CentreBias;
|
||||
differential4WData.mFrontBias = differential.FrontBias;
|
||||
differential4WData.mRearBias = differential.RearBias;
|
||||
driveSimData.setDiffData(differential4WData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsBackend::SetVehicleGearbox(void* vehicle, const void* value)
|
||||
{
|
||||
auto drive = (PxVehicleDrive*)vehicle;
|
||||
auto& gearbox = *(const WheeledVehicle::GearboxSettings*)value;
|
||||
drive->mDriveDynData.setUseAutoGears(gearbox.AutoGear);
|
||||
drive->mDriveDynData.setAutoBoxSwitchTime(Math::Max(gearbox.SwitchTime, 0.0f));
|
||||
switch (drive->getVehicleType())
|
||||
{
|
||||
case PxVehicleTypes::eDRIVE4W:
|
||||
{
|
||||
auto drive4W = (PxVehicleDrive4W*)drive;
|
||||
PxVehicleDriveSimData4W& driveSimData = drive4W->mDriveSimData;
|
||||
|
||||
// Gears
|
||||
PxVehicleGearsData gears;
|
||||
gears.mSwitchTime = Math::Max(gearbox.SwitchTime, 0.0f);
|
||||
driveSimData.setGearsData(gears);
|
||||
|
||||
// Auto Box
|
||||
PxVehicleAutoBoxData autoBox;
|
||||
driveSimData.setAutoBoxData(autoBox);
|
||||
|
||||
// Clutch
|
||||
PxVehicleClutchData clutch;
|
||||
clutch.mStrength = M2ToCm2(gearbox.ClutchStrength);
|
||||
driveSimData.setClutchData(clutch);
|
||||
break;
|
||||
}
|
||||
case PxVehicleTypes::eDRIVENW:
|
||||
{
|
||||
auto drive4W = (PxVehicleDriveNW*)drive;
|
||||
PxVehicleDriveSimDataNW& driveSimData = drive4W->mDriveSimData;
|
||||
|
||||
// Gears
|
||||
PxVehicleGearsData gears;
|
||||
gears.mSwitchTime = Math::Max(gearbox.SwitchTime, 0.0f);
|
||||
driveSimData.setGearsData(gears);
|
||||
|
||||
// Auto Box
|
||||
PxVehicleAutoBoxData autoBox;
|
||||
driveSimData.setAutoBoxData(autoBox);
|
||||
|
||||
// Clutch
|
||||
PxVehicleClutchData clutch;
|
||||
clutch.mStrength = M2ToCm2(gearbox.ClutchStrength);
|
||||
driveSimData.setClutchData(clutch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 PhysicsBackend::GetVehicleTargetGear(void* vehicle)
|
||||
|
||||
@@ -245,6 +245,9 @@ public:
|
||||
// Vehicles
|
||||
static void* CreateVehicle(class WheeledVehicle* actor);
|
||||
static void DestroyVehicle(void* vehicle, int32 driveType);
|
||||
static void UpdateVehicleWheels(WheeledVehicle* actor);
|
||||
static void SetVehicleEngine(void* vehicle, const void* value);
|
||||
static void SetVehicleDifferential(void* vehicle, const void* value);
|
||||
static void SetVehicleGearbox(void* vehicle, const void* value);
|
||||
static int32 GetVehicleTargetGear(void* vehicle);
|
||||
static void SetVehicleTargetGear(void* vehicle, int32 value);
|
||||
|
||||
@@ -732,6 +732,18 @@ void PhysicsBackend::DestroyVehicle(void* vehicle, int32 driveType)
|
||||
{
|
||||
}
|
||||
|
||||
void PhysicsBackend::UpdateVehicleWheels(WheeledVehicle* actor)
|
||||
{
|
||||
}
|
||||
|
||||
void PhysicsBackend::SetVehicleEngine(void* vehicle, const void* value)
|
||||
{
|
||||
}
|
||||
|
||||
void PhysicsBackend::SetVehicleDifferential(void* vehicle, const void* value)
|
||||
{
|
||||
}
|
||||
|
||||
void PhysicsBackend::SetVehicleGearbox(void* vehicle, const void* value)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -445,6 +445,15 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the window to be borderless or not and to be fullscreen.
|
||||
/// </summary>
|
||||
/// <param name="isBorderless">Whether or not to have borders on window.</param>
|
||||
/// <param name="maximized">Whether or not to make the borderless window fullscreen (maximize to cover whole screen).</param>
|
||||
API_FUNCTION() virtual void SetBorderless(bool isBorderless, bool maximized = false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores the window state before minimizing or maximizing.
|
||||
/// </summary>
|
||||
|
||||
@@ -2191,6 +2191,7 @@ bool LinuxPlatform::Init()
|
||||
|
||||
// Initialize "X11 keycode" -> "Flax KeyboardKeys" map
|
||||
KeyCodeMap.Resize(desc->max_key_code + 1);
|
||||
Platform::MemoryClear(KeyCodeMap.Get(), KeyCodeMap.Count() * sizeof(KeyboardKeys));
|
||||
XkbFreeNames(desc, XkbKeyNamesMask, 1);
|
||||
X11::XkbFreeKeyboard(desc, 0, 1);
|
||||
for (int32 keyIdx = (int32)KeyboardKeys::None; keyIdx < MAX_uint8; keyIdx++)
|
||||
|
||||
@@ -202,8 +202,7 @@ void GetDragDropData(const MacWindow* window, id<NSDraggingInfo> sender, Float2&
|
||||
{
|
||||
NSRect frame = [(NSWindow*)window->GetNativePtr() frame];
|
||||
NSPoint point = [sender draggingLocation];
|
||||
Float2 titleSize = GetWindowTitleSize(window);
|
||||
mousePos = Float2(point.x, frame.size.height - point.y) - titleSize;
|
||||
mousePos = Float2(point.x, frame.size.height - point.y) * MacPlatform::ScreenScale - GetWindowTitleSize(window);
|
||||
NSPasteboard* pasteboard = [sender draggingPasteboard];
|
||||
if ([[pasteboard types] containsObject:NSPasteboardTypeString])
|
||||
{
|
||||
|
||||
@@ -316,7 +316,8 @@ bool WindowsFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const Strin
|
||||
if (SUCCEEDED(SHCreateItemFromParsingName(initialDirectory.Get(), NULL, IID_PPV_ARGS(&defaultFolder))))
|
||||
fd->SetFolder(defaultFolder);
|
||||
|
||||
if (SUCCEEDED(fd->Show(parentWindow->GetHWND())))
|
||||
HWND hwndOwner = parentWindow ? parentWindow->GetHWND() : NULL;
|
||||
if (SUCCEEDED(fd->Show(hwndOwner)))
|
||||
{
|
||||
ComPtr<IShellItem> si;
|
||||
if (SUCCEEDED(fd->GetResult(&si)))
|
||||
|
||||
@@ -260,6 +260,75 @@ void WindowsWindow::Maximize()
|
||||
_isDuringMaximize = false;
|
||||
}
|
||||
|
||||
void WindowsWindow::SetBorderless(bool isBorderless, bool maximized)
|
||||
{
|
||||
ASSERT(HasHWND());
|
||||
|
||||
if (IsFullscreen())
|
||||
SetIsFullscreen(false);
|
||||
|
||||
// Fixes issue of borderless window not going full screen
|
||||
if (IsMaximized())
|
||||
Restore();
|
||||
|
||||
_settings.HasBorder = !isBorderless;
|
||||
|
||||
BringToFront();
|
||||
|
||||
if (isBorderless)
|
||||
{
|
||||
LONG lStyle = GetWindowLong(_handle, GWL_STYLE);
|
||||
lStyle &= ~(WS_THICKFRAME | WS_SYSMENU | WS_OVERLAPPED | WS_BORDER | WS_CAPTION);
|
||||
lStyle |= WS_POPUP;
|
||||
lStyle |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
|
||||
#if WINDOWS_USE_NEW_BORDER_LESS
|
||||
if (_settings.IsRegularWindow)
|
||||
style |= WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_SYSMENU | WS_THICKFRAME | WS_GROUP;
|
||||
#elif WINDOWS_USE_NEWER_BORDER_LESS
|
||||
if (_settings.IsRegularWindow)
|
||||
lStyle |= WS_THICKFRAME | WS_SYSMENU;
|
||||
#endif
|
||||
|
||||
SetWindowLong(_handle, GWL_STYLE, lStyle);
|
||||
SetWindowPos(_handle, HWND_TOP, 0, 0,0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
|
||||
if (maximized)
|
||||
{
|
||||
ShowWindow(_handle, SW_SHOWMAXIMIZED);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowWindow(_handle, SW_SHOW);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LONG lStyle = GetWindowLong(_handle, GWL_STYLE);
|
||||
lStyle &= ~(WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
|
||||
if (_settings.AllowMaximize)
|
||||
lStyle |= WS_MAXIMIZEBOX;
|
||||
if (_settings.AllowMinimize)
|
||||
lStyle |= WS_MINIMIZEBOX;
|
||||
if (_settings.HasSizingFrame)
|
||||
lStyle |= WS_THICKFRAME;
|
||||
lStyle |= WS_OVERLAPPED | WS_SYSMENU | WS_BORDER | WS_CAPTION;
|
||||
|
||||
SetWindowLong(_handle, GWL_STYLE, lStyle);
|
||||
SetWindowPos(_handle, nullptr, 0, 0, (int)_settings.Size.X, (int)_settings.Size.Y, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
|
||||
if (maximized)
|
||||
{
|
||||
Maximize();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowWindow(_handle, SW_SHOW);
|
||||
}
|
||||
}
|
||||
|
||||
CheckForWindowResize();
|
||||
}
|
||||
|
||||
void WindowsWindow::Restore()
|
||||
{
|
||||
ASSERT(HasHWND());
|
||||
|
||||
@@ -100,6 +100,7 @@ public:
|
||||
void Hide() override;
|
||||
void Minimize() override;
|
||||
void Maximize() override;
|
||||
void SetBorderless(bool isBorderless, bool maximized = false) override;
|
||||
void Restore() override;
|
||||
bool IsClosed() const override;
|
||||
bool IsForegroundWindow() const override;
|
||||
|
||||
14
Source/Engine/Scripting/Internal/ManagedDictionary.cpp
Normal file
14
Source/Engine/Scripting/Internal/ManagedDictionary.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "ManagedDictionary.h"
|
||||
|
||||
Dictionary<ManagedDictionary::KeyValueType, MTypeObject*> ManagedDictionary::CachedDictionaryTypes;
|
||||
#if !USE_MONO_AOT
|
||||
ManagedDictionary::MakeGenericTypeThunk ManagedDictionary::MakeGenericType;
|
||||
ManagedDictionary::CreateInstanceThunk ManagedDictionary::CreateInstance;
|
||||
ManagedDictionary::AddDictionaryItemThunk ManagedDictionary::AddDictionaryItem;
|
||||
ManagedDictionary::GetDictionaryKeysThunk ManagedDictionary::GetDictionaryKeys;
|
||||
#else
|
||||
MMethod* ManagedDictionary::MakeGenericType;
|
||||
MMethod* ManagedDictionary::CreateInstance;
|
||||
MMethod* ManagedDictionary::AddDictionaryItem;
|
||||
MMethod* ManagedDictionary::GetDictionaryKeys;
|
||||
#endif
|
||||
@@ -12,17 +12,96 @@
|
||||
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MException.h"
|
||||
#include "Engine/Scripting/Internal/StdTypesContainer.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
|
||||
/// <summary>
|
||||
/// Utility interop between C++ and C# for Dictionary collection.
|
||||
/// </summary>
|
||||
struct FLAXENGINE_API ManagedDictionary
|
||||
{
|
||||
public:
|
||||
struct KeyValueType
|
||||
{
|
||||
MType* keyType;
|
||||
MType* valueType;
|
||||
|
||||
bool operator==(const KeyValueType& other) const
|
||||
{
|
||||
return keyType == other.keyType && valueType == other.valueType;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
static Dictionary<KeyValueType, MTypeObject*> CachedDictionaryTypes;
|
||||
|
||||
#if !USE_MONO_AOT
|
||||
typedef MTypeObject* (*MakeGenericTypeThunk)(MObject* instance, MTypeObject* genericType, MArray* genericArgs, MObject** exception);
|
||||
static MakeGenericTypeThunk MakeGenericType;
|
||||
|
||||
typedef MObject* (*CreateInstanceThunk)(MObject* instance, MTypeObject* type, void* arr, MObject** exception);
|
||||
static CreateInstanceThunk CreateInstance;
|
||||
|
||||
typedef void (*AddDictionaryItemThunk)(MObject* instance, MObject* dictionary, MObject* key, MObject* value, MObject** exception);
|
||||
static AddDictionaryItemThunk AddDictionaryItem;
|
||||
|
||||
typedef MArray* (*GetDictionaryKeysThunk)(MObject* instance, MObject* dictionary, MObject** exception);
|
||||
static GetDictionaryKeysThunk GetDictionaryKeys;
|
||||
#else
|
||||
static MMethod* MakeGenericType;
|
||||
static MMethod* CreateInstance;
|
||||
static MMethod* AddDictionaryItem;
|
||||
static MMethod* GetDictionaryKeys;
|
||||
#endif
|
||||
|
||||
public:
|
||||
MObject* Instance;
|
||||
|
||||
ManagedDictionary(MObject* instance = nullptr)
|
||||
{
|
||||
Instance = instance;
|
||||
|
||||
#if !USE_MONO_AOT
|
||||
// Cache the thunks of the dictionary helper methods
|
||||
if (MakeGenericType == nullptr)
|
||||
{
|
||||
MClass* scriptingClass = Scripting::GetStaticClass();
|
||||
CHECK(scriptingClass);
|
||||
|
||||
MMethod* makeGenericTypeMethod = scriptingClass->GetMethod("MakeGenericType", 2);
|
||||
CHECK(makeGenericTypeMethod);
|
||||
MakeGenericType = (MakeGenericTypeThunk)makeGenericTypeMethod->GetThunk();
|
||||
|
||||
MMethod* createInstanceMethod = StdTypesContainer::Instance()->ActivatorClass->GetMethod("CreateInstance", 2);
|
||||
CHECK(createInstanceMethod);
|
||||
CreateInstance = (CreateInstanceThunk)createInstanceMethod->GetThunk();
|
||||
|
||||
MMethod* addDictionaryItemMethod = scriptingClass->GetMethod("AddDictionaryItem", 3);
|
||||
CHECK(addDictionaryItemMethod);
|
||||
AddDictionaryItem = (AddDictionaryItemThunk)addDictionaryItemMethod->GetThunk();
|
||||
|
||||
MMethod* getDictionaryKeysItemMethod = scriptingClass->GetMethod("GetDictionaryKeys", 1);
|
||||
CHECK(getDictionaryKeysItemMethod);
|
||||
GetDictionaryKeys = (GetDictionaryKeysThunk)getDictionaryKeysItemMethod->GetThunk();
|
||||
}
|
||||
#else
|
||||
if (MakeGenericType == nullptr)
|
||||
{
|
||||
MClass* scriptingClass = Scripting::GetStaticClass();
|
||||
CHECK(scriptingClass);
|
||||
|
||||
MakeGenericType = scriptingClass->GetMethod("MakeGenericType", 2);
|
||||
CHECK(MakeGenericType);
|
||||
|
||||
CreateInstance = StdTypesContainer::Instance()->ActivatorClass->GetMethod("CreateInstance", 2);
|
||||
CHECK(CreateInstance);
|
||||
|
||||
AddDictionaryItem = scriptingClass->GetMethod("AddDictionaryItem", 3);
|
||||
CHECK(AddDictionaryItem);
|
||||
|
||||
GetDictionaryKeys = scriptingClass->GetMethod("GetDictionaryKeys", 1);
|
||||
CHECK(GetDictionaryKeys);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename KeyType, typename ValueType>
|
||||
@@ -76,10 +155,11 @@ struct FLAXENGINE_API ManagedDictionary
|
||||
|
||||
static MTypeObject* GetClass(MType* keyType, MType* valueType)
|
||||
{
|
||||
MClass* scriptingClass = Scripting::GetStaticClass();
|
||||
CHECK_RETURN(scriptingClass, nullptr);
|
||||
MMethod* makeGenericMethod = scriptingClass->GetMethod("MakeGenericType", 2);
|
||||
CHECK_RETURN(makeGenericMethod, nullptr);
|
||||
// Check if the generic type was generated earlier
|
||||
KeyValueType cacheKey = { keyType, valueType };
|
||||
MTypeObject* dictionaryType;
|
||||
if (CachedDictionaryTypes.TryGet(cacheKey, dictionaryType))
|
||||
return dictionaryType;
|
||||
|
||||
MTypeObject* genericType = MUtils::GetType(StdTypesContainer::Instance()->DictionaryClass);
|
||||
#if USE_NETCORE
|
||||
@@ -91,18 +171,23 @@ struct FLAXENGINE_API ManagedDictionary
|
||||
genericArgsPtr[0] = INTERNAL_TYPE_GET_OBJECT(keyType);
|
||||
genericArgsPtr[1] = INTERNAL_TYPE_GET_OBJECT(valueType);
|
||||
|
||||
MObject* exception = nullptr;
|
||||
#if !USE_MONO_AOT
|
||||
dictionaryType = MakeGenericType(nullptr, genericType, genericArgs, &exception);
|
||||
#else
|
||||
void* params[2];
|
||||
params[0] = genericType;
|
||||
params[1] = genericArgs;
|
||||
MObject* exception = nullptr;
|
||||
MObject* dictionaryType = makeGenericMethod->Invoke(nullptr, params, &exception);
|
||||
dictionaryType = (MTypeObject*)MakeGenericType->Invoke(nullptr, params, &exception);
|
||||
#endif
|
||||
if (exception)
|
||||
{
|
||||
MException ex(exception);
|
||||
ex.Log(LogType::Error, TEXT(""));
|
||||
return nullptr;
|
||||
}
|
||||
return (MTypeObject*)dictionaryType;
|
||||
CachedDictionaryTypes.Add(cacheKey, dictionaryType);
|
||||
return dictionaryType;
|
||||
}
|
||||
|
||||
static ManagedDictionary New(MType* keyType, MType* valueType)
|
||||
@@ -112,16 +197,15 @@ struct FLAXENGINE_API ManagedDictionary
|
||||
if (!dictionaryType)
|
||||
return result;
|
||||
|
||||
MClass* scriptingClass = Scripting::GetStaticClass();
|
||||
CHECK_RETURN(scriptingClass, result);
|
||||
MMethod* createMethod = StdTypesContainer::Instance()->ActivatorClass->GetMethod("CreateInstance", 2);
|
||||
CHECK_RETURN(createMethod, result);
|
||||
|
||||
MObject* exception = nullptr;
|
||||
#if !USE_MONO_AOT
|
||||
MObject* instance = CreateInstance(nullptr, dictionaryType, nullptr, &exception);
|
||||
#else
|
||||
void* params[2];
|
||||
params[0] = dictionaryType;
|
||||
params[1] = nullptr;
|
||||
MObject* instance = createMethod->Invoke(nullptr, params, &exception);
|
||||
MObject* instance = CreateInstance->Invoke(nullptr, params, &exception);
|
||||
#endif
|
||||
if (exception)
|
||||
{
|
||||
MException ex(exception);
|
||||
@@ -136,16 +220,17 @@ struct FLAXENGINE_API ManagedDictionary
|
||||
void Add(MObject* key, MObject* value)
|
||||
{
|
||||
CHECK(Instance);
|
||||
MClass* scriptingClass = Scripting::GetStaticClass();
|
||||
CHECK(scriptingClass);
|
||||
MMethod* addDictionaryItemMethod = scriptingClass->GetMethod("AddDictionaryItem", 3);
|
||||
CHECK(addDictionaryItemMethod);
|
||||
|
||||
MObject* exception = nullptr;
|
||||
#if !USE_MONO_AOT
|
||||
AddDictionaryItem(nullptr, Instance, key, value, &exception);
|
||||
#else
|
||||
void* params[3];
|
||||
params[0] = Instance;
|
||||
params[1] = key;
|
||||
params[2] = value;
|
||||
MObject* exception = nullptr;
|
||||
addDictionaryItemMethod->Invoke(Instance, params, &exception);
|
||||
AddDictionaryItem->Invoke(Instance, params, &exception);
|
||||
#endif
|
||||
if (exception)
|
||||
{
|
||||
MException ex(exception);
|
||||
@@ -156,13 +241,13 @@ struct FLAXENGINE_API ManagedDictionary
|
||||
MArray* GetKeys() const
|
||||
{
|
||||
CHECK_RETURN(Instance, nullptr);
|
||||
MClass* scriptingClass = Scripting::GetStaticClass();
|
||||
CHECK_RETURN(scriptingClass, nullptr);
|
||||
MMethod* getDictionaryKeysMethod = scriptingClass->GetMethod("GetDictionaryKeys", 1);
|
||||
CHECK_RETURN(getDictionaryKeysMethod, nullptr);
|
||||
#if !USE_MONO_AOT
|
||||
return GetDictionaryKeys(nullptr, Instance, nullptr);
|
||||
#else
|
||||
void* params[1];
|
||||
params[0] = Instance;
|
||||
return (MArray*)getDictionaryKeysMethod->Invoke( nullptr, params, nullptr);
|
||||
return (MArray*)GetDictionaryKeys->Invoke(nullptr, params, nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
MObject* GetValue(MObject* key) const
|
||||
@@ -177,4 +262,11 @@ struct FLAXENGINE_API ManagedDictionary
|
||||
}
|
||||
};
|
||||
|
||||
inline uint32 GetHash(const ManagedDictionary::KeyValueType& other)
|
||||
{
|
||||
uint32 hash = ::GetHash((void*)other.keyType);
|
||||
CombineHash(hash, ::GetHash((void*)other.valueType));
|
||||
return hash;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -179,6 +179,18 @@ namespace FlaxEngine
|
||||
Internal_Destroy(GetUnmanagedPtr(obj), timeLeft);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the specified object and clears the reference variable.
|
||||
/// The object obj will be destroyed immediately.
|
||||
/// If obj is a Script it will be removed from the Actor and deleted.
|
||||
/// If obj is an Actor it will be removed from the Scene and deleted as well as all its Scripts and all children of the Actor.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to destroy.</param>
|
||||
public static void DestroyNow(Object obj)
|
||||
{
|
||||
Internal_DestroyNow(GetUnmanagedPtr(obj));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the specified object and clears the reference variable.
|
||||
/// The object obj will be destroyed now or after the time specified in seconds from now.
|
||||
@@ -316,6 +328,9 @@ namespace FlaxEngine
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_Destroy", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
|
||||
internal static partial void Internal_Destroy(IntPtr obj, float timeLeft);
|
||||
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_DestroyNow", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
|
||||
internal static partial void Internal_DestroyNow(IntPtr obj);
|
||||
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_GetTypeName", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
|
||||
internal static partial string Internal_GetTypeName(IntPtr obj);
|
||||
|
||||
|
||||
@@ -878,7 +878,7 @@ MMethod* MClass::GetMethod(const char* name, int32 numParams) const
|
||||
GetMethods();
|
||||
for (int32 i = 0; i < _methods.Count(); i++)
|
||||
{
|
||||
if (_methods[i]->GetName() == name && _methods[i]->GetParametersCount() == numParams)
|
||||
if (_methods[i]->GetParametersCount() == numParams && _methods[i]->GetName() == name)
|
||||
return _methods[i];
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
@@ -681,6 +681,12 @@ DEFINE_INTERNAL_CALL(void) ObjectInternal_Destroy(ScriptingObject* obj, float ti
|
||||
obj->DeleteObject(timeLeft, useGameTime);
|
||||
}
|
||||
|
||||
DEFINE_INTERNAL_CALL(void) ObjectInternal_DestroyNow(ScriptingObject* obj)
|
||||
{
|
||||
if (obj)
|
||||
obj->DeleteObjectNow();
|
||||
}
|
||||
|
||||
DEFINE_INTERNAL_CALL(MString*) ObjectInternal_GetTypeName(ScriptingObject* obj)
|
||||
{
|
||||
INTERNAL_CALL_CHECK_RETURN(obj, nullptr);
|
||||
@@ -777,6 +783,7 @@ public:
|
||||
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_ManagedInstanceCreated", &ObjectInternal_ManagedInstanceCreated);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_ManagedInstanceDeleted", &ObjectInternal_ManagedInstanceDeleted);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_Destroy", &ObjectInternal_Destroy);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_DestroyNow", &ObjectInternal_DestroyNow);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_GetTypeName", &ObjectInternal_GetTypeName);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_FindObject", &ObjectInternal_FindObject);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_TryFindObject", &ObjectInternal_TryFindObject);
|
||||
|
||||
@@ -1125,42 +1125,67 @@ bool TerrainPatch::InitializeHeightMap()
|
||||
|
||||
float* TerrainPatch::GetHeightmapData()
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.GetHeightmapData");
|
||||
|
||||
if (_cachedHeightMap.HasItems())
|
||||
return _cachedHeightMap.Get();
|
||||
|
||||
PROFILE_CPU_NAMED("Terrain.GetHeightmapData");
|
||||
|
||||
CacheHeightData();
|
||||
|
||||
return _cachedHeightMap.Get();
|
||||
}
|
||||
|
||||
void TerrainPatch::ClearHeightmapCache()
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.ClearHeightmapCache");
|
||||
_cachedHeightMap.Clear();
|
||||
}
|
||||
|
||||
byte* TerrainPatch::GetHolesMaskData()
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.GetHolesMaskData");
|
||||
|
||||
if (_cachedHolesMask.HasItems())
|
||||
return _cachedHolesMask.Get();
|
||||
|
||||
PROFILE_CPU_NAMED("Terrain.GetHolesMaskData");
|
||||
|
||||
CacheHeightData();
|
||||
|
||||
return _cachedHolesMask.Get();
|
||||
}
|
||||
|
||||
void TerrainPatch::ClearHolesMaskCache()
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.ClearHolesMaskCache");
|
||||
_cachedHolesMask.Clear();
|
||||
}
|
||||
|
||||
Color32* TerrainPatch::GetSplatMapData(int32 index)
|
||||
{
|
||||
ASSERT(index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT);
|
||||
|
||||
PROFILE_CPU_NAMED("Terrain.GetSplatMapData");
|
||||
|
||||
if (_cachedSplatMap[index].HasItems())
|
||||
return _cachedSplatMap[index].Get();
|
||||
|
||||
PROFILE_CPU_NAMED("Terrain.GetSplatMapData");
|
||||
|
||||
CacheSplatData();
|
||||
|
||||
return _cachedSplatMap[index].Get();
|
||||
}
|
||||
|
||||
void TerrainPatch::ClearSplatMapCache()
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.ClearSplatMapCache");
|
||||
_cachedSplatMap->Clear();
|
||||
}
|
||||
|
||||
void TerrainPatch::ClearCache()
|
||||
{
|
||||
ClearHeightmapCache();
|
||||
ClearHolesMaskCache();
|
||||
ClearSplatMapCache();
|
||||
}
|
||||
|
||||
void TerrainPatch::CacheHeightData()
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.CacheHeightData");
|
||||
|
||||
@@ -229,12 +229,22 @@ public:
|
||||
/// <returns>The heightmap data.</returns>
|
||||
float* GetHeightmapData();
|
||||
|
||||
/// <summary>
|
||||
/// Clears cache of the heightmap data.
|
||||
/// </summary>
|
||||
void ClearHeightmapCache();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw pointer to the holes mask data.
|
||||
/// </summary>
|
||||
/// <returns>The holes mask data.</returns>
|
||||
byte* GetHolesMaskData();
|
||||
|
||||
/// <summary>
|
||||
/// Clears cache of the holes mask data.
|
||||
/// </summary>
|
||||
void ClearHolesMaskCache();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw pointer to the splat map data.
|
||||
/// </summary>
|
||||
@@ -242,6 +252,16 @@ public:
|
||||
/// <returns>The splat map data.</returns>
|
||||
Color32* GetSplatMapData(int32 index);
|
||||
|
||||
/// <summary>
|
||||
/// Clears cache of the splat map data.
|
||||
/// </summary>
|
||||
void ClearSplatMapCache();
|
||||
|
||||
/// <summary>
|
||||
/// Clears all caches.
|
||||
/// </summary>
|
||||
void ClearCache();
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the terrain patch heightmap with the given samples.
|
||||
/// </summary>
|
||||
|
||||
@@ -80,21 +80,27 @@ namespace FlaxEngine.GUI
|
||||
public Color BackgroundColorSelected { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the border.
|
||||
/// Gets or sets whether the button has a border.
|
||||
/// </summary>
|
||||
[EditorDisplay("Border Style"), EditorOrder(2010), ExpandGroups]
|
||||
public bool HasBorder { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the border.
|
||||
/// </summary>
|
||||
[EditorDisplay("Border Style"), EditorOrder(2011), ExpandGroups]
|
||||
public Color BorderColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the border color when button is highlighted.
|
||||
/// </summary>
|
||||
[EditorDisplay("Border Style"), EditorOrder(2011)]
|
||||
[EditorDisplay("Border Style"), EditorOrder(2012)]
|
||||
public Color BorderColorHighlighted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the border color when button is selected.
|
||||
/// </summary>
|
||||
[EditorDisplay("Border Style"), EditorOrder(2012)]
|
||||
[EditorDisplay("Border Style"), EditorOrder(2013)]
|
||||
public Color BorderColorSelected { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -245,7 +251,8 @@ namespace FlaxEngine.GUI
|
||||
BackgroundBrush.Draw(clientRect, backgroundColor);
|
||||
else
|
||||
Render2D.FillRectangle(clientRect, backgroundColor);
|
||||
Render2D.DrawRectangle(clientRect, borderColor);
|
||||
if (HasBorder)
|
||||
Render2D.DrawRectangle(clientRect, borderColor);
|
||||
|
||||
// Draw text
|
||||
Render2D.DrawText(_font?.GetFont(), TextMaterial, _text, clientRect, textColor, TextAlignment.Center, TextAlignment.Center);
|
||||
|
||||
@@ -10,6 +10,48 @@ namespace FlaxEngine.GUI
|
||||
/// <seealso cref="FlaxEngine.GUI.Control" />
|
||||
public class ProgressBar : ContainerControl
|
||||
{
|
||||
/// <summary>
|
||||
/// The method used to effect the bar.
|
||||
/// </summary>
|
||||
public enum BarMethod
|
||||
{
|
||||
/// <summary>
|
||||
/// Stretch the bar.
|
||||
/// </summary>
|
||||
Stretch,
|
||||
|
||||
/// <summary>
|
||||
/// Clip the bar.
|
||||
/// </summary>
|
||||
Clip,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The origin to move the progress bar to.
|
||||
/// </summary>
|
||||
public enum BarOrigin
|
||||
{
|
||||
/// <summary>
|
||||
/// Move the bar horizontally to the left.
|
||||
/// </summary>
|
||||
HorizontalLeft,
|
||||
|
||||
/// <summary>
|
||||
/// Move the bar horizontally to the right.
|
||||
/// </summary>
|
||||
HorizontalRight,
|
||||
|
||||
/// <summary>
|
||||
/// Move the bar vertically up.
|
||||
/// </summary>
|
||||
VerticalTop,
|
||||
|
||||
/// <summary>
|
||||
/// Move the bar vertically down.
|
||||
/// </summary>
|
||||
VerticalBottom,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value.
|
||||
/// </summary>
|
||||
@@ -41,6 +83,18 @@ namespace FlaxEngine.GUI
|
||||
/// </summary>
|
||||
public bool UseSmoothing => !Mathf.IsZero(SmoothingScale);
|
||||
|
||||
/// <summary>
|
||||
/// The method used to effect the bar.
|
||||
/// </summary>
|
||||
[EditorOrder(41), Tooltip("The method used to effect the bar.")]
|
||||
public BarMethod Method = BarMethod.Stretch;
|
||||
|
||||
/// <summary>
|
||||
/// The origin or where the bar decreases to.
|
||||
/// </summary>
|
||||
[EditorOrder(42), Tooltip("The origin or where the bar decreases to.")]
|
||||
public BarOrigin Origin = BarOrigin.HorizontalLeft;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum value.
|
||||
/// </summary>
|
||||
@@ -168,12 +222,44 @@ namespace FlaxEngine.GUI
|
||||
float progressNormalized = (_current - _minimum) / _maximum;
|
||||
if (progressNormalized > 0.001f)
|
||||
{
|
||||
var barRect = new Rectangle(0, 0, Width * progressNormalized, Height);
|
||||
BarMargin.ShrinkRectangle(ref barRect);
|
||||
if (BarBrush != null)
|
||||
BarBrush.Draw(barRect, BarColor);
|
||||
else
|
||||
Render2D.FillRectangle(barRect, BarColor);
|
||||
Rectangle barRect = new Rectangle(0, 0, Width * progressNormalized, Height);
|
||||
switch (Origin)
|
||||
{
|
||||
case BarOrigin.HorizontalLeft:
|
||||
break;
|
||||
case BarOrigin.HorizontalRight:
|
||||
barRect = new Rectangle(Width - Width * progressNormalized, 0, Width * progressNormalized, Height);
|
||||
break;
|
||||
case BarOrigin.VerticalTop:
|
||||
barRect = new Rectangle(0, 0, Width, Height * progressNormalized);
|
||||
break;
|
||||
case BarOrigin.VerticalBottom:
|
||||
barRect = new Rectangle(0, Height - Height * progressNormalized, Width, Height * progressNormalized);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
switch (Method)
|
||||
{
|
||||
case BarMethod.Stretch:
|
||||
BarMargin.ShrinkRectangle(ref barRect);
|
||||
if (BarBrush != null)
|
||||
BarBrush.Draw(barRect, BarColor);
|
||||
else
|
||||
Render2D.FillRectangle(barRect, BarColor);
|
||||
break;
|
||||
case BarMethod.Clip:
|
||||
var rect = new Rectangle(0, 0, Width, Height);
|
||||
BarMargin.ShrinkRectangle(ref rect);
|
||||
Render2D.PushClip(ref barRect);
|
||||
if (BarBrush != null)
|
||||
BarBrush.Draw(rect, BarColor);
|
||||
else
|
||||
Render2D.FillRectangle(rect, BarColor);
|
||||
Render2D.PopClip();
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1596,7 +1596,9 @@ namespace Flax.Build.Bindings
|
||||
toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>(Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target)) : null");
|
||||
toNativeContent.Append($"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(); }}");
|
||||
freeContents2.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
|
||||
//freeContents2.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(); }}");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1116,7 +1116,7 @@ namespace Flax.Build.Bindings
|
||||
var signatureEnd = contents.Length;
|
||||
if (useSeparateImpl)
|
||||
{
|
||||
// Write declarion only, function definition wil be put in the end of the file
|
||||
// Write declaration only, function definition wil be put in the end of the file
|
||||
CppContentsEnd.AppendFormat("{0} {2}::{1}(", returnValueType, functionInfo.UniqueName, callerName);
|
||||
CppContentsEnd.Append(contents.ToString(signatureStart, signatureEnd - signatureStart));
|
||||
contents.Append(';').AppendLine();
|
||||
@@ -2016,7 +2016,7 @@ namespace Flax.Build.Bindings
|
||||
var indent = " ";
|
||||
if (useSeparateImpl)
|
||||
{
|
||||
// Write declarion only, function definition wil be put in the end of the file
|
||||
// Write declaration only, function definition wil be put in the end of the file
|
||||
CppContentsEnd.AppendFormat("void {1}::{0}_ManagedBind(", eventInfo.Name, internalTypeName);
|
||||
var sig = contents.ToString(signatureStart, contents.Length - signatureStart);
|
||||
CppContentsEnd.Append(contents.ToString(signatureStart, contents.Length - signatureStart));
|
||||
@@ -2027,6 +2027,8 @@ namespace Flax.Build.Bindings
|
||||
contents.AppendLine().Append(indent).Append('{').AppendLine();
|
||||
if (buildData.Toolchain?.Compiler == TargetCompiler.MSVC)
|
||||
contents.Append(indent).AppendLine($" MSVC_FUNC_EXPORT(\"{classTypeNameManaged}::Internal_{eventInfo.Name}_Bind\")"); // Export generated function binding under the C# name
|
||||
if (!eventInfo.IsStatic)
|
||||
contents.Append(indent).Append(" if (__obj == nullptr) return;").AppendLine();
|
||||
contents.Append(indent).Append(" Function<void(");
|
||||
for (var i = 0; i < paramsCount; i++)
|
||||
{
|
||||
|
||||
@@ -255,11 +255,13 @@ namespace Flax.Build
|
||||
#endif
|
||||
if (buildOptions.ScriptingAPI.IgnoreMissingDocumentationWarnings)
|
||||
args.Add("-nowarn:1591");
|
||||
#if USE_NETCORE
|
||||
|
||||
// Optimizations prevent debugging, only enable in release builds
|
||||
args.Add(buildData.Configuration == TargetConfiguration.Release ? "/optimize+" : "/optimize-");
|
||||
#else
|
||||
args.Add(buildData.Configuration == TargetConfiguration.Debug ? "/optimize-" : "/optimize+");
|
||||
var optimize = buildData.Configuration == TargetConfiguration.Release;
|
||||
if (buildData.TargetOptions.ScriptingAPI.Optimization.HasValue)
|
||||
optimize = buildData.TargetOptions.ScriptingAPI.Optimization.Value;
|
||||
args.Add(optimize ? "/optimize+" : "/optimize-");
|
||||
#if !USE_NETCORE
|
||||
args.Add(string.Format("/reference:\"{0}mscorlib.dll\"", referenceAssemblies));
|
||||
#endif
|
||||
args.Add(string.Format("/out:\"{0}\"", outputFile));
|
||||
|
||||
@@ -185,7 +185,7 @@ namespace Flax.Build.NativeCpp
|
||||
public string DepsFolder => Path.Combine(Globals.EngineRoot, "Source", "Platforms", Platform.Target.ToString(), "Binaries", "ThirdParty", Architecture.ToString());
|
||||
|
||||
/// <summary>
|
||||
/// The scripting API building options.
|
||||
/// The C# scripting API building options.
|
||||
/// </summary>
|
||||
public struct ScriptingAPIOptions
|
||||
{
|
||||
@@ -224,6 +224,11 @@ namespace Flax.Build.NativeCpp
|
||||
/// </summary>
|
||||
public CSharpNullableReferences CSharpNullableReferences = CSharpNullableReferences.Disable;
|
||||
|
||||
/// <summary>
|
||||
/// Enable code optimization.
|
||||
/// </summary>
|
||||
public bool? Optimization;
|
||||
|
||||
public ScriptingAPIOptions()
|
||||
{
|
||||
}
|
||||
@@ -232,13 +237,19 @@ namespace Flax.Build.NativeCpp
|
||||
/// Adds the other options into this.
|
||||
/// </summary>
|
||||
/// <param name="other">The other.</param>
|
||||
public void Add(ScriptingAPIOptions other)
|
||||
public void Add(ScriptingAPIOptions other, bool addBuildOptions = true)
|
||||
{
|
||||
Defines.AddRange(other.Defines);
|
||||
SystemReferences.AddRange(other.SystemReferences);
|
||||
FileReferences.AddRange(other.FileReferences);
|
||||
Analyzers.AddRange(other.Analyzers);
|
||||
IgnoreMissingDocumentationWarnings |= other.IgnoreMissingDocumentationWarnings;
|
||||
|
||||
if (addBuildOptions)
|
||||
{
|
||||
if (other.Optimization.HasValue)
|
||||
Optimization |= other.Optimization;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -403,7 +403,7 @@ namespace Flax.Build
|
||||
moduleOptions.PrivateIncludePaths.AddRange(dependencyOptions.PublicIncludePaths);
|
||||
moduleOptions.Libraries.AddRange(dependencyOptions.Libraries);
|
||||
moduleOptions.DelayLoadLibraries.AddRange(dependencyOptions.DelayLoadLibraries);
|
||||
moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI);
|
||||
moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI, false);
|
||||
moduleOptions.ExternalModules.AddRange(dependencyOptions.ExternalModules);
|
||||
}
|
||||
}
|
||||
@@ -418,7 +418,7 @@ namespace Flax.Build
|
||||
moduleOptions.PublicIncludePaths.AddRange(dependencyOptions.PublicIncludePaths);
|
||||
moduleOptions.Libraries.AddRange(dependencyOptions.Libraries);
|
||||
moduleOptions.DelayLoadLibraries.AddRange(dependencyOptions.DelayLoadLibraries);
|
||||
moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI);
|
||||
moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI, false);
|
||||
moduleOptions.ExternalModules.AddRange(dependencyOptions.ExternalModules);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using Flax.Build.NativeCpp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -107,7 +108,7 @@ namespace Flax.Build.Projects.VisualStudio
|
||||
vcProjectFileContent.AppendLine(" <ResolveNuGetPackages>false</ResolveNuGetPackages>");
|
||||
vcProjectFileContent.AppendLine(" <VCTargetsPath Condition=\"$(Configuration.Contains('Linux'))\">./</VCTargetsPath>");
|
||||
vcProjectFileContent.AppendLine(" </PropertyGroup>");
|
||||
|
||||
|
||||
// Default properties
|
||||
vcProjectFileContent.AppendLine(" <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />");
|
||||
|
||||
@@ -317,12 +318,31 @@ namespace Flax.Build.Projects.VisualStudio
|
||||
vcFiltersFileContent.AppendLine(" </ItemGroup>");
|
||||
|
||||
// IntelliSense information
|
||||
|
||||
var additionalOptions = new List<string>();
|
||||
switch (project.Configurations[0].TargetBuildOptions.CompileEnv.CppVersion)
|
||||
{
|
||||
case CppVersion.Cpp14:
|
||||
additionalOptions.Add("/std:c++14");
|
||||
break;
|
||||
case CppVersion.Cpp17:
|
||||
additionalOptions.Add("/std:c++17");
|
||||
break;
|
||||
case CppVersion.Cpp20:
|
||||
additionalOptions.Add("/std:c++20");
|
||||
break;
|
||||
case CppVersion.Latest:
|
||||
additionalOptions.Add("/std:c++latest");
|
||||
break;
|
||||
}
|
||||
|
||||
vcProjectFileContent.AppendLine(" <PropertyGroup>");
|
||||
vcProjectFileContent.AppendLine(string.Format(" <NMakePreprocessorDefinitions>$(NMakePreprocessorDefinitions){0}</NMakePreprocessorDefinitions>", (project.Defines.Count > 0 ? (";" + string.Join(";", project.Defines)) : "")));
|
||||
vcProjectFileContent.AppendLine(string.Format(" <NMakeIncludeSearchPath>$(NMakeIncludeSearchPath){0}</NMakeIncludeSearchPath>", (project.SearchPaths.Length > 0 ? (";" + string.Join(";", project.SearchPaths)) : "")));
|
||||
vcProjectFileContent.AppendLine(" <NMakeForcedIncludes>$(NMakeForcedIncludes)</NMakeForcedIncludes>");
|
||||
vcProjectFileContent.AppendLine(" <NMakeAssemblySearchPath>$(NMakeAssemblySearchPath)</NMakeAssemblySearchPath>");
|
||||
vcProjectFileContent.AppendLine(" <NMakeForcedUsingAssemblies>$(NMakeForcedUsingAssemblies)</NMakeForcedUsingAssemblies>");
|
||||
vcProjectFileContent.AppendLine(string.Format(" <AdditionalOptions>{0}</AdditionalOptions>", string.Join(" ", additionalOptions)));
|
||||
vcProjectFileContent.AppendLine(" </PropertyGroup>");
|
||||
|
||||
foreach (var platform in platforms)
|
||||
|
||||
Reference in New Issue
Block a user