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