diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs
index 994d94280..0bd082c48 100644
--- a/Source/Editor/Editor.cs
+++ b/Source/Editor/Editor.cs
@@ -1583,7 +1583,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;
}
diff --git a/Source/Editor/Modules/SimulationModule.cs b/Source/Editor/Modules/SimulationModule.cs
index cc081c23c..72b197123 100644
--- a/Source/Editor/Modules/SimulationModule.cs
+++ b/Source/Editor/Modules/SimulationModule.cs
@@ -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();
}
+ ///
+ /// Delegates between playing game and playing scenes in editor based on the user's editor preference.
+ ///
+ 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;
+ }
+ }
+
///
/// Returns true if play mode has been requested.
///
@@ -76,7 +93,7 @@ namespace FlaxEditor.Modules
///
/// Requests start playing in editor.
///
- public void RequestStartPlay()
+ public void RequestStartPlayScenes()
{
if (Editor.StateMachine.IsEditMode)
{
@@ -89,6 +106,57 @@ namespace FlaxEditor.Modules
}
}
+ ///
+ /// Requests playing game start or stop in editor from the project's configured FirstScene.
+ ///
+ public void RequestPlayGameOrStopPlay()
+ {
+ if (Editor.StateMachine.IsPlayMode)
+ {
+ RequestStopPlay();
+ }
+ else
+ {
+ RequestStartPlayGame();
+ }
+ }
+
+ ///
+ /// Requests start playing in editor from the project's configured FirstScene.
+ ///
+ 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);
+ }
+
///
/// Requests stop playing in editor.
///
@@ -106,14 +174,14 @@ namespace FlaxEditor.Modules
}
///
- /// Requests the playing start or stop in editor.
+ /// Requests the playing scenes start or stop in editor.
///
- public void RequestPlayOrStopPlay()
+ public void RequestPlayScenesOrStopPlay()
{
if (Editor.StateMachine.IsPlayMode)
RequestStopPlay();
else
- RequestStartPlay();
+ RequestStartPlayScenes();
}
///
diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs
index 38c61942a..08ec09de8 100644
--- a/Source/Editor/Modules/UIModule.cs
+++ b/Source/Editor/Modules/UIModule.cs
@@ -39,7 +39,6 @@ namespace FlaxEditor.Modules
private bool _progressFailed;
ContextMenuSingleSelectGroup _numberOfClientsGroup = new ContextMenuSingleSelectGroup();
- 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();
@@ -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)
diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs
index 4f2b40514..e0a51622b 100644
--- a/Source/Editor/Options/InputOptions.cs
+++ b/Source/Editor/Options/InputOptions.cs
@@ -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);
diff --git a/Source/Editor/Undo/Actions/PasteActorsAction.cs b/Source/Editor/Undo/Actions/PasteActorsAction.cs
index c90e94189..8b0d040d8 100644
--- a/Source/Editor/Undo/Actions/PasteActorsAction.cs
+++ b/Source/Editor/Undo/Actions/PasteActorsAction.cs
@@ -138,6 +138,8 @@ namespace FlaxEditor.Actions
}
}
+ // Store previously looked up names and the results
+ Dictionary 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();
}
///
diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
index 9591915ab..faefef724 100644
--- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs
+++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
@@ -134,6 +134,8 @@ namespace FlaxEditor.Viewport
}
}
+ private bool _lockedFocus;
+ private double _lockedFocusOffset;
private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32);
private StaticModel _previewStaticModel;
private int _previewModelEntryIndex;
@@ -425,11 +427,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);
}
+ ///
+ 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));
+ }
+ }
+
///
/// Overrides the selection outline effect or restored the default one.
///
@@ -792,6 +826,23 @@ namespace FlaxEditor.Viewport
FocusSelection(ref orientation);
}
+ ///
+ /// Lock focus on the current selection gizmo.
+ ///
+ public void LockFocusSelection()
+ {
+ _lockedFocus = true;
+ }
+
+ ///
+ /// Unlock focus on the current selection.
+ ///
+ public void UnlockFocusSelection()
+ {
+ _lockedFocus = false;
+ _lockedFocusOffset = 0f;
+ }
+
///
/// Focuses the viewport on the current selection of the gizmo.
///
diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs
index 46c0f33f4..513542fdc 100644
--- a/Source/Editor/Windows/GameWindow.cs
+++ b/Source/Editor/Windows/GameWindow.cs
@@ -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);
}
diff --git a/Source/Editor/Windows/SceneEditorWindow.cs b/Source/Editor/Windows/SceneEditorWindow.cs
index f55ff4ee4..72ff3be33 100644
--- a/Source/Editor/Windows/SceneEditorWindow.cs
+++ b/Source/Editor/Windows/SceneEditorWindow.cs
@@ -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);
}
diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
index 82842376e..73ddbc41c 100644
--- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
+++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
@@ -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);
diff --git a/Source/Engine/Engine/NativeInterop.Marshallers.cs b/Source/Engine/Engine/NativeInterop.Marshallers.cs
index 0f7d238bc..3ea8a5faa 100644
--- a/Source/Engine/Engine/NativeInterop.Marshallers.cs
+++ b/Source/Engine/Engine/NativeInterop.Marshallers.cs
@@ -395,18 +395,17 @@ namespace FlaxEngine.Interop
{
if (managed is null)
return;
-
sourceArray = managed;
(managedHandle, managedArray) = ManagedArray.AllocatePooledArray(managed.Length);
}
public ReadOnlySpan GetManagedValuesSource() => sourceArray;
- public Span GetUnmanagedValuesDestination() => managedArray.ToSpan();
+ public Span GetUnmanagedValuesDestination() => managedArray != null ? managedArray.ToSpan() : Span.Empty;
public TUnmanagedElement* ToUnmanaged() => (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedHandle);
- public void Free() => managedArray.FreePooled();
+ public void Free() => managedArray?.FreePooled();
}
#if FLAX_EDITOR
diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp
index 22969c752..81411ed76 100644
--- a/Source/Engine/Input/Input.cpp
+++ b/Source/Engine/Input/Input.cpp
@@ -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;
diff --git a/Source/Engine/Navigation/NavMeshBoundsVolume.cpp b/Source/Engine/Navigation/NavMeshBoundsVolume.cpp
index 85a022c82..89e76a9bc 100644
--- a/Source/Engine/Navigation/NavMeshBoundsVolume.cpp
+++ b/Source/Engine/Navigation/NavMeshBoundsVolume.cpp
@@ -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()
diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp
index 023b5036a..66b03479f 100644
--- a/Source/Engine/Networking/NetworkReplicator.cpp
+++ b/Source/Engine/Networking/NetworkReplicator.cpp
@@ -941,6 +941,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;
diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h
index ecccc52cd..3806f8c9d 100644
--- a/Source/Engine/Networking/NetworkReplicator.h
+++ b/Source/Engine/Networking/NetworkReplicator.h
@@ -116,6 +116,13 @@ public:
/// The network object.
/// True if object exists in networking, otherwise false.
API_FUNCTION() static bool HasObject(const ScriptingObject* obj);
+
+ ///
+ /// Resolves foreign Guid into a local ScriptingObject
+ ///
+ /// The Guid of a foreign object.
+ /// Object if managed to resolve, otherwise null.
+ API_FUNCTION() static ScriptingObject* ResolveForeignObject(Guid objectId);
///
/// Gets the Client Id of the network object owner.
diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp
index 0434d8414..ab314ab72 100644
--- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp
+++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp
@@ -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++)
diff --git a/Source/Engine/Scripting/Internal/ManagedDictionary.cpp b/Source/Engine/Scripting/Internal/ManagedDictionary.cpp
new file mode 100644
index 000000000..7cb4d1cfb
--- /dev/null
+++ b/Source/Engine/Scripting/Internal/ManagedDictionary.cpp
@@ -0,0 +1,14 @@
+#include "ManagedDictionary.h"
+
+Dictionary 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
diff --git a/Source/Engine/Scripting/Internal/ManagedDictionary.h b/Source/Engine/Scripting/Internal/ManagedDictionary.h
index c42eb1d27..f957dfcec 100644
--- a/Source/Engine/Scripting/Internal/ManagedDictionary.h
+++ b/Source/Engine/Scripting/Internal/ManagedDictionary.h
@@ -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"
///
/// Utility interop between C++ and C# for Dictionary collection.
///
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 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
@@ -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
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
index d69b8dcbf..da94a0056 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
@@ -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