Merge remote-tracking branch 'origin/master' into 1.10

# Conflicts:
#	Source/Editor/Windows/Profiler/Memory.cs
This commit is contained in:
Wojtek Figat
2025-03-25 13:37:15 +01:00
94 changed files with 901 additions and 352 deletions

View File

@@ -79,6 +79,7 @@ namespace FlaxEditor.CustomEditors
_presenter = presenter; _presenter = presenter;
AnchorPreset = AnchorPresets.StretchAll; AnchorPreset = AnchorPresets.StretchAll;
Offsets = Margin.Zero; Offsets = Margin.Zero;
Pivot = Float2.Zero;
IsScrollable = true; IsScrollable = true;
} }
@@ -195,6 +196,15 @@ namespace FlaxEditor.CustomEditors
Presenter.AfterLayout?.Invoke(layout); Presenter.AfterLayout?.Invoke(layout);
} }
/// <inheritdoc />
protected override void Deinitialize()
{
Editor = null;
_overrideEditor = null;
base.Deinitialize();
}
/// <inheritdoc /> /// <inheritdoc />
protected override void OnModified() protected override void OnModified()
{ {

View File

@@ -205,7 +205,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (_linkedPrefabId != Guid.Empty) if (_linkedPrefabId != Guid.Empty)
{ {
_linkedPrefabId = Guid.Empty; _linkedPrefabId = Guid.Empty;
Editor.Instance.Prefabs.PrefabApplied -= OnPrefabApplying; Editor.Instance.Prefabs.PrefabApplying -= OnPrefabApplying;
Editor.Instance.Prefabs.PrefabApplied -= OnPrefabApplied; Editor.Instance.Prefabs.PrefabApplied -= OnPrefabApplied;
} }
} }

View File

@@ -56,6 +56,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
var bottomLeftCell = new VerticalPanel var bottomLeftCell = new VerticalPanel
{ {
Pivot = Float2.Zero,
Spacing = 0, Spacing = 0,
TopMargin = 0, TopMargin = 0,
BottomMargin = 0, BottomMargin = 0,

View File

@@ -1057,6 +1057,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
protected override void Deinitialize() protected override void Deinitialize()
{ {
_scriptToggles = null; _scriptToggles = null;
_scripts.Clear();
base.Deinitialize(); base.Deinitialize();
} }

View File

@@ -232,6 +232,7 @@ namespace FlaxEditor.CustomEditors.Editors
public void Setup(CollectionEditor editor, int index, bool canReorder = true) public void Setup(CollectionEditor editor, int index, bool canReorder = true)
{ {
Pivot = Float2.Zero;
HeaderHeight = 18; HeaderHeight = 18;
_canReorder = canReorder; _canReorder = canReorder;
EnableDropDownIcon = true; EnableDropDownIcon = true;
@@ -884,6 +885,11 @@ namespace FlaxEditor.CustomEditors.Editors
set => _pickerValidator.FileExtension = value; set => _pickerValidator.FileExtension = value;
} }
public DragAreaControl()
{
Pivot = Float2.Zero;
}
/// <inheritdoc /> /// <inheritdoc />
public override void Draw() public override void Draw()
{ {

View File

@@ -819,6 +819,15 @@ namespace FlaxEditor.CustomEditors.Editors
OnGroupsEnd(); OnGroupsEnd();
} }
/// <inheritdoc />
protected override void Deinitialize()
{
_visibleIfCaches = null;
_visibleIfPropertiesListsCache = null;
base.Deinitialize();
}
/// <inheritdoc /> /// <inheritdoc />
public override void Refresh() public override void Refresh()
{ {

View File

@@ -18,6 +18,7 @@ namespace FlaxEditor.CustomEditors.Elements
/// </summary> /// </summary>
public readonly DropPanel Panel = new DropPanel public readonly DropPanel Panel = new DropPanel
{ {
Pivot = Float2.Zero,
ArrowImageClosed = new SpriteBrush(Style.Current.ArrowRight), ArrowImageClosed = new SpriteBrush(Style.Current.ArrowRight),
ArrowImageOpened = new SpriteBrush(Style.Current.ArrowDown), ArrowImageOpened = new SpriteBrush(Style.Current.ArrowDown),
EnableDropDownIcon = true, EnableDropDownIcon = true,

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Elements namespace FlaxEditor.CustomEditors.Elements
@@ -13,7 +14,10 @@ namespace FlaxEditor.CustomEditors.Elements
/// <summary> /// <summary>
/// The panel. /// The panel.
/// </summary> /// </summary>
public readonly HorizontalPanel Panel = new HorizontalPanel(); public readonly HorizontalPanel Panel = new HorizontalPanel
{
Pivot = Float2.Zero,
};
/// <inheritdoc /> /// <inheritdoc />
public override ContainerControl ContainerControl => Panel; public override ContainerControl ContainerControl => Panel;

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Elements namespace FlaxEditor.CustomEditors.Elements
@@ -13,7 +14,10 @@ namespace FlaxEditor.CustomEditors.Elements
/// <summary> /// <summary>
/// The panel. /// The panel.
/// </summary> /// </summary>
public readonly VerticalPanel Panel = new VerticalPanel(); public readonly VerticalPanel Panel = new VerticalPanel
{
Pivot = Float2.Zero,
};
/// <inheritdoc /> /// <inheritdoc />
public override ContainerControl ContainerControl => Panel; public override ContainerControl ContainerControl => Panel;

View File

@@ -851,6 +851,11 @@ namespace FlaxEditor
{ {
LogWarning("Exception: " + ex.Message); LogWarning("Exception: " + ex.Message);
LogWarning(ex.StackTrace); LogWarning(ex.StackTrace);
if (ex.InnerException != null)
{
LogWarning("Inner exception:");
LogWarning(ex.InnerException);
}
} }
/// <summary> /// <summary>
@@ -1509,7 +1514,8 @@ namespace FlaxEditor
var win = Windows.GameWin?.Root; var win = Windows.GameWin?.Root;
if (win?.RootWindow is WindowRootControl root) if (win?.RootWindow is WindowRootControl root)
{ {
pos = Float2.Round(Windows.GameWin.Viewport.PointFromScreen(pos) * root.DpiScale); pos = Windows.GameWin.Viewport.PointFromScreen(pos);
pos = Float2.Round(pos);
} }
else else
{ {
@@ -1522,7 +1528,8 @@ namespace FlaxEditor
var win = Windows.GameWin?.Root; var win = Windows.GameWin?.Root;
if (win?.RootWindow is WindowRootControl root) if (win?.RootWindow is WindowRootControl root)
{ {
pos = Float2.Round(Windows.GameWin.Viewport.PointToScreen(pos / root.DpiScale)); pos = Windows.GameWin.Viewport.PointToScreen(pos);
pos = Float2.Round(pos);
} }
else else
{ {
@@ -1554,6 +1561,7 @@ namespace FlaxEditor
else else
result = gameWin.Viewport.Size; result = gameWin.Viewport.Size;
result *= root.DpiScale;
result = Float2.Round(result); result = Float2.Round(result);
} }
} }

View File

@@ -464,7 +464,7 @@ namespace FlaxEditor.GUI.Docking
{ {
base.Focus(); base.Focus();
SelectTab(); SelectTab(false);
BringToFront(); BringToFront();
} }

View File

@@ -294,6 +294,7 @@ namespace FlaxEditor.GUI
Parent = _scrollPanel, Parent = _scrollPanel,
AnchorPreset = AnchorPresets.HorizontalStretchTop, AnchorPreset = AnchorPresets.HorizontalStretchTop,
IsScrollable = true, IsScrollable = true,
Pivot = Float2.Zero,
}; };
} }

View File

@@ -833,6 +833,7 @@ namespace FlaxEditor.GUI.Timeline
{ {
AutoFocus = false, AutoFocus = false,
AnchorPreset = AnchorPresets.HorizontalStretchTop, AnchorPreset = AnchorPresets.HorizontalStretchTop,
Pivot = Float2.Zero,
Offsets = Margin.Zero, Offsets = Margin.Zero,
IsScrollable = true, IsScrollable = true,
BottomMargin = 40.0f, BottomMargin = 40.0f,

View File

@@ -35,7 +35,7 @@ namespace FlaxEditor.Gizmo
/// <inheritdoc /> /// <inheritdoc />
public EditorPrimitives() public EditorPrimitives()
{ {
Order = -100; Order = 100;
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -11,7 +11,7 @@ namespace FlaxEngine.Gizmo;
/// <summary> /// <summary>
/// Class for adding viewport rubber band selection. /// Class for adding viewport rubber band selection.
/// </summary> /// </summary>
public class ViewportRubberBandSelector public sealed class ViewportRubberBandSelector
{ {
private bool _isMosueCaptured; private bool _isMosueCaptured;
private bool _isRubberBandSpanning; private bool _isRubberBandSpanning;
@@ -38,7 +38,7 @@ public class ViewportRubberBandSelector
/// <returns>True if selection started, otherwise false.</returns> /// <returns>True if selection started, otherwise false.</returns>
public bool TryStartingRubberBandSelection() public bool TryStartingRubberBandSelection()
{ {
if (!_isRubberBandSpanning && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown) if (!_isRubberBandSpanning && _owner.Gizmos.Active != null && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown)
{ {
_tryStartRubberBand = true; _tryStartRubberBand = true;
return true; return true;
@@ -90,7 +90,7 @@ public class ViewportRubberBandSelector
_rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero); _rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero);
_tryStartRubberBand = false; _tryStartRubberBand = false;
} }
else if (_isRubberBandSpanning && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown) else if (_isRubberBandSpanning && _owner.Gizmos.Active != null && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown)
{ {
_rubberBandRect.Width = mousePosition.X - _cachedStartingMousePosition.X; _rubberBandRect.Width = mousePosition.X - _cachedStartingMousePosition.X;
_rubberBandRect.Height = mousePosition.Y - _cachedStartingMousePosition.Y; _rubberBandRect.Height = mousePosition.Y - _cachedStartingMousePosition.Y;
@@ -162,8 +162,8 @@ public class ViewportRubberBandSelector
projection.Init(_owner.Viewport); projection.Init(_owner.Viewport);
foreach (var node in nodes) foreach (var node in nodes)
{ {
// Check for custom can select code // Skip actors that cannot be selected
if (!node.CanSelectActorNodeWithSelector()) if (!node.CanSelectInViewport)
continue; continue;
var a = node.Actor; var a = node.Actor;
@@ -232,30 +232,15 @@ public class ViewportRubberBandSelector
} }
/// <summary> /// <summary>
/// Used to draw the rubber band. Begins render 2D. /// Draws the ruber band during owner viewport UI drawing.
/// </summary> /// </summary>
/// <param name="context">The GPU Context.</param> public void Draw()
/// <param name="target">The GPU texture target.</param>
/// <param name="targetDepth">The GPU texture target depth.</param>
public void Draw(GPUContext context, GPUTexture target, GPUTexture targetDepth)
{
// Draw RubberBand for rect selection
if (!_isRubberBandSpanning)
return;
Render2D.Begin(context, target, targetDepth);
Draw2D();
Render2D.End();
}
/// <summary>
/// Used to draw the rubber band. Use if already rendering 2D context.
/// </summary>
public void Draw2D()
{ {
if (!_isRubberBandSpanning) if (!_isRubberBandSpanning)
return; return;
Render2D.FillRectangle(_rubberBandRect, Style.Current.Selection); var style = Style.Current;
Render2D.DrawRectangle(_rubberBandRect, Style.Current.SelectionBorder); Render2D.FillRectangle(_rubberBandRect, style.Selection);
Render2D.DrawRectangle(_rubberBandRect, style.SelectionBorder);
} }
/// <summary> /// <summary>

View File

@@ -21,6 +21,7 @@ namespace FlaxEditor.Modules
private bool _enableEvents; private bool _enableEvents;
private bool _isDuringFastSetup; private bool _isDuringFastSetup;
private bool _rebuildFlag; private bool _rebuildFlag;
private bool _rebuildInitFlag;
private int _itemsCreated; private int _itemsCreated;
private int _itemsDeleted; private int _itemsDeleted;
private readonly HashSet<MainContentTreeNode> _dirtyNodes = new HashSet<MainContentTreeNode>(); private readonly HashSet<MainContentTreeNode> _dirtyNodes = new HashSet<MainContentTreeNode>();
@@ -61,7 +62,7 @@ namespace FlaxEditor.Modules
public event Action WorkspaceModified; public event Action WorkspaceModified;
/// <summary> /// <summary>
/// Occurs when workspace has will be rebuilt. /// Occurs when workspace will be rebuilt.
/// </summary> /// </summary>
public event Action WorkspaceRebuilding; public event Action WorkspaceRebuilding;
@@ -88,6 +89,9 @@ namespace FlaxEditor.Modules
// Register AssetItems serialization helper (serialize ref ID only) // Register AssetItems serialization helper (serialize ref ID only)
FlaxEngine.Json.JsonSerializer.Settings.Converters.Add(new AssetItemConverter()); FlaxEngine.Json.JsonSerializer.Settings.Converters.Add(new AssetItemConverter());
ScriptsBuilder.ScriptsReload += OnScriptsReload;
ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd;
} }
private void OnContentAssetDisposing(Asset asset) private void OnContentAssetDisposing(Asset asset)
@@ -817,6 +821,7 @@ namespace FlaxEditor.Modules
Profiler.BeginEvent("ContentDatabase.Rebuild"); Profiler.BeginEvent("ContentDatabase.Rebuild");
var startTime = Platform.TimeSeconds; var startTime = Platform.TimeSeconds;
_rebuildFlag = false; _rebuildFlag = false;
_rebuildInitFlag = false;
_enableEvents = false; _enableEvents = false;
// Load all folders // Load all folders
@@ -1230,8 +1235,6 @@ namespace FlaxEditor.Modules
LoadProjects(Game.Project); LoadProjects(Game.Project);
} }
RebuildInternal();
Editor.ContentImporting.ImportFileEnd += (obj, failed) => Editor.ContentImporting.ImportFileEnd += (obj, failed) =>
{ {
var path = obj.ResultUrl; var path = obj.ResultUrl;
@@ -1239,6 +1242,15 @@ namespace FlaxEditor.Modules
FlaxEngine.Scripting.InvokeOnUpdate(() => OnImportFileDone(path)); FlaxEngine.Scripting.InvokeOnUpdate(() => OnImportFileDone(path));
}; };
_enableEvents = true; _enableEvents = true;
_rebuildInitFlag = true;
}
/// <inheritdoc />
public override void OnEndInit()
{
// Handle init when project was loaded without scripts loading ()
if (_rebuildInitFlag)
RebuildInternal();
} }
private void OnImportFileDone(string path) private void OnImportFileDone(string path)
@@ -1313,6 +1325,52 @@ namespace FlaxEditor.Modules
} }
} }
private void OnScriptsReload()
{
var enabledEvents = _enableEvents;
_enableEvents = false;
_isDuringFastSetup = true;
var startItems = _itemsCreated;
foreach (var project in Projects)
{
if (project.Content != null)
{
//Dispose(project.Content.Folder);
for (int i = 0; i < project.Content.Folder.Children.Count; i++)
{
Dispose(project.Content.Folder.Children[i]);
i--;
}
}
if (project.Source != null)
{
//Dispose(project.Source.Folder);
for (int i = 0; i < project.Source.Folder.Children.Count; i++)
{
Dispose(project.Source.Folder.Children[i]);
i--;
}
}
}
List<ContentProxy> removeProxies = new List<ContentProxy>();
foreach (var proxy in Editor.Instance.ContentDatabase.Proxy)
{
if (proxy.GetType().IsCollectible)
removeProxies.Add(proxy);
}
foreach (var proxy in removeProxies)
RemoveProxy(proxy, false);
_isDuringFastSetup = false;
_enableEvents = enabledEvents;
}
private void OnScriptsReloadEnd()
{
RebuildInternal();
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnUpdate() public override void OnUpdate()
{ {
@@ -1340,6 +1398,8 @@ namespace FlaxEditor.Modules
public override void OnExit() public override void OnExit()
{ {
FlaxEngine.Content.AssetDisposing -= OnContentAssetDisposing; FlaxEngine.Content.AssetDisposing -= OnContentAssetDisposing;
ScriptsBuilder.ScriptsReload -= OnScriptsReload;
ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd;
// Disable events // Disable events
_enableEvents = false; _enableEvents = false;

View File

@@ -391,6 +391,20 @@ namespace FlaxEditor.Modules
public override void OnInit() public override void OnInit()
{ {
ImportFileEntry.RegisterDefaultTypes(); ImportFileEntry.RegisterDefaultTypes();
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
}
private void OnScriptsReloadBegin()
{
// Remove import file types from scripting assemblies
List<string> removeFileTypes = new List<string>();
foreach (var pair in ImportFileEntry.FileTypes)
{
if (pair.Value.Method.IsCollectible || (pair.Value.Target != null && pair.Value.Target.GetType().IsCollectible))
removeFileTypes.Add(pair.Key);
}
foreach (var fileType in removeFileTypes)
ImportFileEntry.FileTypes.Remove(fileType);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -451,6 +465,7 @@ namespace FlaxEditor.Modules
/// <inheritdoc /> /// <inheritdoc />
public override void OnExit() public override void OnExit()
{ {
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
EndWorker(); EndWorker();
} }
} }

View File

@@ -58,7 +58,7 @@ namespace FlaxEditor.Modules
: base(editor) : base(editor)
{ {
// After editor cache but before the windows // After editor cache but before the windows
InitOrder = -900; InitOrder = -800;
} }
/// <summary> /// <summary>

View File

@@ -160,7 +160,7 @@ namespace FlaxEditor.Modules
internal UIModule(Editor editor) internal UIModule(Editor editor)
: base(editor) : base(editor)
{ {
InitOrder = -90; InitOrder = -70;
VisjectSurfaceBackground = FlaxEngine.Content.LoadAsyncInternal<Texture>("Editor/VisjectSurface"); VisjectSurfaceBackground = FlaxEngine.Content.LoadAsyncInternal<Texture>("Editor/VisjectSurface");
ColorValueBox.ShowPickColorDialog += ShowPickColorDialog; ColorValueBox.ShowPickColorDialog += ShowPickColorDialog;
} }

View File

@@ -5,10 +5,12 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Xml; using System.Xml;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.GUI.Dialogs; using FlaxEditor.GUI.Dialogs;
using FlaxEditor.GUI.Docking;
using FlaxEditor.Windows; using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets; using FlaxEditor.Windows.Assets;
using FlaxEditor.Windows.Profiler; using FlaxEditor.Windows.Profiler;
@@ -39,6 +41,7 @@ namespace FlaxEditor.Modules
public DockState DockState; public DockState DockState;
public DockPanel DockedTo; public DockPanel DockedTo;
public int DockedTabIndex;
public float? SplitterValue = null; public float? SplitterValue = null;
public bool SelectOnShow = false; public bool SelectOnShow = false;
@@ -48,6 +51,8 @@ namespace FlaxEditor.Modules
public Float2 FloatSize; public Float2 FloatSize;
public Float2 FloatPosition; public Float2 FloatPosition;
public Guid AssetItemID;
// Constructor, to allow for default values // Constructor, to allow for default values
public WindowRestoreData() public WindowRestoreData()
{ {
@@ -803,43 +808,64 @@ namespace FlaxEditor.Modules
Level.SceneSaving += OnSceneSaving; Level.SceneSaving += OnSceneSaving;
Level.SceneUnloaded += OnSceneUnloaded; Level.SceneUnloaded += OnSceneUnloaded;
Level.SceneUnloading += OnSceneUnloading; Level.SceneUnloading += OnSceneUnloading;
ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; Editor.ContentDatabase.WorkspaceRebuilt += OnWorkspaceRebuilt;
Editor.StateMachine.StateChanged += OnEditorStateChanged; Editor.StateMachine.StateChanged += OnEditorStateChanged;
} }
internal void AddToRestore(AssetEditorWindow win)
{
AddToRestore(win, win.GetType(), new WindowRestoreData
{
AssetItemID = win.Item.ID,
});
}
internal void AddToRestore(CustomEditorWindow win) internal void AddToRestore(CustomEditorWindow win)
{ {
var type = win.GetType(); AddToRestore(win.Window, win.GetType(), new WindowRestoreData());
}
private void AddToRestore(EditorWindow win, Type type, WindowRestoreData winData)
{
// Validate if can restore type // Validate if can restore type
var constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); var constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
if (constructor == null || type.IsGenericType) if (constructor == null || type.IsGenericType)
return; return;
var winData = new WindowRestoreData(); var panel = win.ParentDockPanel;
var panel = win.Window.ParentDockPanel;
// Ensure that this window is only selected following recompilation // Ensure that this window is only selected following recompilation
// if it was the active tab in its dock panel. Otherwise, there is a // if it was the active tab in its dock panel. Otherwise, there is a
// risk of interrupting the user's workflow by potentially selecting // risk of interrupting the user's workflow by potentially selecting
// background tabs. // background tabs.
winData.SelectOnShow = panel.SelectedTab == win.Window; var window = win.RootWindow.Window;
if (panel is FloatWindowDockPanel) winData.SelectOnShow = panel.SelectedTab == win;
winData.DockedTabIndex = 0;
if (panel is FloatWindowDockPanel && window != null && panel.TabsCount == 1)
{ {
winData.DockState = DockState.Float; winData.DockState = DockState.Float;
var window = win.Window.RootWindow.Window;
winData.FloatPosition = window.Position; winData.FloatPosition = window.Position;
winData.FloatSize = window.ClientSize; winData.FloatSize = window.ClientSize;
winData.Maximize = window.IsMaximized; winData.Maximize = window.IsMaximized;
winData.Minimize = window.IsMinimized; winData.Minimize = window.IsMinimized;
winData.DockedTo = panel;
} }
else else
{ {
for (int i = 0; i < panel.Tabs.Count; i++)
{
if (panel.Tabs[i] == win)
{
winData.DockedTabIndex = i;
break;
}
}
if (panel.TabsCount > 1) if (panel.TabsCount > 1)
{ {
winData.DockState = DockState.DockFill; winData.DockState = DockState.DockFill;
winData.DockedTo = panel; winData.DockedTo = panel;
}else }
else
{ {
winData.DockState = panel.TryGetDockState(out var splitterValue); winData.DockState = panel.TryGetDockState(out var splitterValue);
winData.DockedTo = panel.ParentDockPanel; winData.DockedTo = panel.ParentDockPanel;
@@ -851,38 +877,93 @@ namespace FlaxEditor.Modules
_restoreWindows.Add(winData); _restoreWindows.Add(winData);
} }
private void OnScriptsReloadEnd() private void OnWorkspaceRebuilt()
{ {
for (int i = 0; i < _restoreWindows.Count; i++) // Go in reverse order to create floating Prefab windows first before docked windows
for (int i = _restoreWindows.Count - 1; i >= 0; i--)
{ {
var winData = _restoreWindows[i]; var winData = _restoreWindows[i];
try try
{ {
var assembly = Utils.GetAssemblyByName(winData.AssemblyName); var assembly = Utils.GetAssemblyByName(winData.AssemblyName);
if (assembly != null) if (assembly == null)
continue;
var type = assembly.GetType(winData.TypeName);
if (type == null)
continue;
if (type.IsAssignableTo(typeof(AssetEditorWindow)))
{ {
var type = assembly.GetType(winData.TypeName); var ctor = type.GetConstructor(new Type[] { typeof(Editor), typeof(AssetItem) });
if (type != null) var assetItem = Editor.ContentDatabase.FindAsset(winData.AssetItemID);
var win = (AssetEditorWindow)ctor.Invoke(new object[] { Editor.Instance, assetItem });
win.Show(winData.DockState, winData.DockState != DockState.Float ? winData.DockedTo : null, winData.SelectOnShow, winData.SplitterValue);
if (winData.DockState == DockState.Float)
{ {
var win = (CustomEditorWindow)Activator.CreateInstance(type); var window = win.RootWindow.Window;
win.Show(winData.DockState, winData.DockedTo, winData.SelectOnShow, winData.SplitterValue); window.Position = winData.FloatPosition;
if (winData.DockState == DockState.Float) if (winData.Maximize)
{ {
var window = win.Window.RootWindow.Window; window.Maximize();
window.Position = winData.FloatPosition; }
if (winData.Maximize) else if (winData.Minimize)
{ {
window.Maximize(); window.Minimize();
} }
else if (winData.Minimize) else
{ {
window.Minimize(); window.ClientSize = winData.FloatSize;
} }
else
{ // Update panel reference in other windows docked to this panel
window.ClientSize = winData.FloatSize; foreach (ref var otherData in CollectionsMarshal.AsSpan(_restoreWindows))
} {
if (otherData.DockedTo == winData.DockedTo)
otherData.DockedTo = win.ParentDockPanel;
}
}
var panel = win.ParentDockPanel;
int currentTabIndex = 0;
for (int pi = 0; pi < panel.TabsCount; pi++)
{
if (panel.Tabs[pi] == win)
{
currentTabIndex = pi;
break;
}
}
while (currentTabIndex > winData.DockedTabIndex)
{
win.ParentDockPanel.MoveTabLeft(currentTabIndex);
currentTabIndex--;
}
while (currentTabIndex < winData.DockedTabIndex)
{
win.ParentDockPanel.MoveTabRight(currentTabIndex);
currentTabIndex++;
}
}
else
{
var win = (CustomEditorWindow)Activator.CreateInstance(type);
win.Show(winData.DockState, winData.DockedTo, winData.SelectOnShow, winData.SplitterValue);
if (winData.DockState == DockState.Float)
{
var window = win.Window.RootWindow.Window;
window.Position = winData.FloatPosition;
if (winData.Maximize)
{
window.Maximize();
}
else if (winData.Minimize)
{
window.Minimize();
}
else
{
window.ClientSize = winData.FloatSize;
} }
} }
} }
@@ -893,6 +974,11 @@ namespace FlaxEditor.Modules
Editor.LogWarning(string.Format("Failed to restore window {0} (assembly: {1})", winData.TypeName, winData.AssemblyName)); Editor.LogWarning(string.Format("Failed to restore window {0} (assembly: {1})", winData.TypeName, winData.AssemblyName));
} }
} }
// Restored windows stole the focus from Editor
if (_restoreWindows.Count > 0)
Editor.Instance.Windows.MainWindow.Focus();
_restoreWindows.Clear(); _restoreWindows.Clear();
} }
@@ -1048,7 +1134,7 @@ namespace FlaxEditor.Modules
Level.SceneSaving -= OnSceneSaving; Level.SceneSaving -= OnSceneSaving;
Level.SceneUnloaded -= OnSceneUnloaded; Level.SceneUnloaded -= OnSceneUnloaded;
Level.SceneUnloading -= OnSceneUnloading; Level.SceneUnloading -= OnSceneUnloading;
ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; Editor.ContentDatabase.WorkspaceRebuilt -= OnWorkspaceRebuilt;
Editor.StateMachine.StateChanged -= OnEditorStateChanged; Editor.StateMachine.StateChanged -= OnEditorStateChanged;
// Close main window // Close main window

View File

@@ -192,7 +192,7 @@ namespace FlaxEditor.SceneGraph
GetAllChildActorNodes(nodes); GetAllChildActorNodes(nodes);
return nodes.ToArray(); return nodes.ToArray();
} }
/// <summary> /// <summary>
/// Get all nested actor nodes under this actor node. /// Get all nested actor nodes under this actor node.
/// </summary> /// </summary>
@@ -213,12 +213,18 @@ namespace FlaxEditor.SceneGraph
} }
/// <summary> /// <summary>
/// Whether an actor node can be selected with a selector. /// Whether an actor node can be selected with a selector inside editor viewport.
/// </summary> /// </summary>
/// <returns>True if the actor node can be selected</returns> public virtual bool CanSelectInViewport
public virtual bool CanSelectActorNodeWithSelector()
{ {
return Actor && Actor.HideFlags is not (HideFlags.DontSelect or HideFlags.FullyHidden) && Actor is not EmptyActor && IsActive; get
{
var actor = Actor;
return actor &&
actor.IsActiveInHierarchy &&
(actor.HideFlags & HideFlags.DontSelect) == HideFlags.None &&
actor.GetType() != typeof(EmptyActor);
}
} }
/// <summary> /// <summary>

View File

@@ -33,12 +33,6 @@ namespace FlaxEditor.SceneGraph.Actors
} }
} }
/// <inheritdoc />
public override bool CanSelectActorNodeWithSelector()
{
return false;
}
/// <summary> /// <summary>
/// Gets the scene. /// Gets the scene.
/// </summary> /// </summary>
@@ -53,6 +47,9 @@ namespace FlaxEditor.SceneGraph.Actors
{ {
} }
/// <inheritdoc />
public override bool CanSelectInViewport => false;
/// <inheritdoc /> /// <inheritdoc />
public override bool CanCreatePrefab => false; public override bool CanCreatePrefab => false;

View File

@@ -169,7 +169,6 @@ namespace FlaxEditor.SceneGraph.Actors
var actor = new BoxCollider var actor = new BoxCollider
{ {
StaticFlags = staticModelNode.Actor.StaticFlags, StaticFlags = staticModelNode.Actor.StaticFlags,
Transform = staticModelNode.Actor.Transform,
}; };
staticModelNode.Root.Spawn(actor, staticModelNode.Actor); staticModelNode.Root.Spawn(actor, staticModelNode.Actor);
createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor)); createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor));
@@ -180,7 +179,6 @@ namespace FlaxEditor.SceneGraph.Actors
var actor = new SphereCollider var actor = new SphereCollider
{ {
StaticFlags = staticModelNode.Actor.StaticFlags, StaticFlags = staticModelNode.Actor.StaticFlags,
Transform = staticModelNode.Actor.Transform,
}; };
staticModelNode.Root.Spawn(actor, staticModelNode.Actor); staticModelNode.Root.Spawn(actor, staticModelNode.Actor);
createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor)); createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor));
@@ -191,7 +189,6 @@ namespace FlaxEditor.SceneGraph.Actors
var actor = new BoxCollider var actor = new BoxCollider
{ {
StaticFlags = staticModelNode.Actor.StaticFlags, StaticFlags = staticModelNode.Actor.StaticFlags,
Transform = staticModelNode.Actor.Transform,
Size = new Float3(100.0f, 100.0f, 1.0f), Size = new Float3(100.0f, 100.0f, 1.0f),
}; };
staticModelNode.Root.Spawn(actor, staticModelNode.Actor); staticModelNode.Root.Spawn(actor, staticModelNode.Actor);
@@ -203,7 +200,6 @@ namespace FlaxEditor.SceneGraph.Actors
var actor = new CapsuleCollider var actor = new CapsuleCollider
{ {
StaticFlags = staticModelNode.Actor.StaticFlags, StaticFlags = staticModelNode.Actor.StaticFlags,
Transform = staticModelNode.Actor.Transform,
Radius = 25.0f, Radius = 25.0f,
Height = 50.0f, Height = 50.0f,
}; };
@@ -220,7 +216,6 @@ namespace FlaxEditor.SceneGraph.Actors
var actor = new MeshCollider var actor = new MeshCollider
{ {
StaticFlags = staticModelNode.Actor.StaticFlags, StaticFlags = staticModelNode.Actor.StaticFlags,
Transform = staticModelNode.Actor.Transform,
CollisionData = collisionData, CollisionData = collisionData,
}; };
staticModelNode.Root.Spawn(actor, staticModelNode.Actor); staticModelNode.Root.Spawn(actor, staticModelNode.Actor);

View File

@@ -80,9 +80,6 @@ namespace FlaxEditor.SceneGraph.Actors
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool CanSelectActorNodeWithSelector() public override bool CanSelectInViewport => base.CanSelectInViewport && Actor is UICanvas uiCanvas && uiCanvas.Is3D;
{
return Actor is UICanvas uiCanvas && uiCanvas.Is3D && base.CanSelectActorNodeWithSelector();
}
} }
} }

View File

@@ -42,29 +42,31 @@ namespace FlaxEditor.SceneGraph.Actors
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool CanSelectActorNodeWithSelector() public override bool CanSelectInViewport
{ {
// Check if control and skip if canvas is 2D get
if (Actor is not UIControl uiControl)
return false;
UICanvas canvas = null;
var controlParent = uiControl.Parent;
while (controlParent != null && controlParent is not Scene)
{ {
if (controlParent is UICanvas uiCanvas) // Check if control and skip if canvas is 2D
{ if (Actor is not UIControl uiControl)
canvas = uiCanvas;
break;
}
controlParent = controlParent.Parent;
}
if (canvas != null)
{
if (canvas.Is2D)
return false; return false;
UICanvas canvas = null;
var controlParent = uiControl.Parent;
while (controlParent != null && controlParent is not Scene)
{
if (controlParent is UICanvas uiCanvas)
{
canvas = uiCanvas;
break;
}
controlParent = controlParent.Parent;
}
if (canvas != null)
{
if (canvas.Is2D)
return false;
}
return base.CanSelectInViewport;
} }
return base.CanSelectActorNodeWithSelector();
} }
} }
} }

View File

@@ -469,6 +469,7 @@ namespace FlaxEditor.SceneGraph
{ {
ChildNodes[i].OnDispose(); ChildNodes[i].OnDispose();
} }
ChildNodes.Clear();
SceneGraphFactory.Nodes.Remove(ID); SceneGraphFactory.Nodes.Remove(ID);
} }

View File

@@ -164,10 +164,21 @@ namespace FlaxEditor.States
{ {
Assert.AreEqual(Guid.Empty, _lastSceneFromRequest, "Invalid state."); Assert.AreEqual(Guid.Empty, _lastSceneFromRequest, "Invalid state.");
// Bind events // Bind events, only bind loading event and error if re-loading the same scene to avoid issues.
Level.SceneLoaded += OnSceneEvent; if (_scenesToUnload.Count == 1 && _scenesToLoad.Count == 1)
Level.SceneLoadError += OnSceneEvent; {
Level.SceneUnloaded += OnSceneEvent; if (_scenesToLoad[0] == _scenesToUnload[0].ID)
{
Level.SceneLoaded += OnSceneEvent;
Level.SceneLoadError += OnSceneEvent;
}
}
else
{
Level.SceneLoaded += OnSceneEvent;
Level.SceneLoadError += OnSceneEvent;
Level.SceneUnloaded += OnSceneEvent;
}
// Push scenes changing requests // Push scenes changing requests
for (int i = 0; i < _scenesToUnload.Count; i++) for (int i = 0; i < _scenesToUnload.Count; i++)

View File

@@ -161,6 +161,8 @@ namespace FlaxEditor.Surface
private void OnScriptsReloadBegin() private void OnScriptsReloadBegin()
{ {
_nodesCache.Clear();
// Check if any of the nodes comes from the game scripts - those can be reloaded at runtime so prevent crashes // Check if any of the nodes comes from the game scripts - those can be reloaded at runtime so prevent crashes
bool hasTypeFromGameScripts = Editor.Instance.CodeEditing.AnimGraphNodes.HasTypeFromGameScripts; bool hasTypeFromGameScripts = Editor.Instance.CodeEditing.AnimGraphNodes.HasTypeFromGameScripts;

View File

@@ -24,6 +24,7 @@ namespace FlaxEditor.Surface
public BehaviorTreeSurface(IVisjectSurfaceOwner owner, Action onSave, FlaxEditor.Undo undo) public BehaviorTreeSurface(IVisjectSurfaceOwner owner, Action onSave, FlaxEditor.Undo undo)
: base(owner, onSave, undo, CreateStyle()) : base(owner, onSave, undo, CreateStyle())
{ {
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
} }
private static SurfaceStyle CreateStyle() private static SurfaceStyle CreateStyle()
@@ -35,6 +36,11 @@ namespace FlaxEditor.Surface
return style; return style;
} }
private void OnScriptsReloadBegin()
{
_nodesCache.Clear();
}
private static void DrawBox(Box box) private static void DrawBox(Box box)
{ {
var rect = new Rectangle(Float2.Zero, box.Size); var rect = new Rectangle(Float2.Zero, box.Size);
@@ -186,6 +192,7 @@ namespace FlaxEditor.Surface
{ {
if (IsDisposing) if (IsDisposing)
return; return;
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
_nodesCache.Wait(); _nodesCache.Wait();
base.OnDestroy(); base.OnDestroy();

View File

@@ -235,6 +235,7 @@ namespace FlaxEditor.Surface.ContextMenu
{ {
Parent = panel1, Parent = panel1,
AnchorPreset = AnchorPresets.HorizontalStretchTop, AnchorPreset = AnchorPresets.HorizontalStretchTop,
Pivot = Float2.Zero,
IsScrollable = true, IsScrollable = true,
}; };
_groupsPanel = panel2; _groupsPanel = panel2;
@@ -292,6 +293,7 @@ namespace FlaxEditor.Surface.ContextMenu
X = 8, X = 8,
Width = Width * 0.5f - 16, Width = Width * 0.5f - 16,
AutoSize = true, AutoSize = true,
Pivot = Float2.Zero,
}; };
_descriptionOutputPanel = new VerticalPanel() _descriptionOutputPanel = new VerticalPanel()
@@ -300,6 +302,7 @@ namespace FlaxEditor.Surface.ContextMenu
X = Width * 0.5f + 8, X = Width * 0.5f + 8,
Width = Width * 0.5f - 16, Width = Width * 0.5f - 16,
AutoSize = true, AutoSize = true,
Pivot = Float2.Zero,
}; };
} }

View File

@@ -43,6 +43,7 @@ namespace FlaxEditor.Surface.ContextMenu
/// <param name="archetype">The group archetype.</param> /// <param name="archetype">The group archetype.</param>
public VisjectCMGroup(VisjectCM cm, GroupArchetype archetype) public VisjectCMGroup(VisjectCM cm, GroupArchetype archetype)
{ {
Pivot = Float2.Zero;
ContextMenu = cm; ContextMenu = cm;
Archetypes.Add(archetype); Archetypes.Add(archetype);
Name = archetype.Name; Name = archetype.Name;

View File

@@ -415,6 +415,15 @@ namespace FlaxEditor.Surface
// Init drag handlers // Init drag handlers
DragHandlers.Add(_dragAssets = new DragAssets<DragDropEventArgs>(ValidateDragItem)); DragHandlers.Add(_dragAssets = new DragAssets<DragDropEventArgs>(ValidateDragItem));
DragHandlers.Add(_dragParameters = new DragNames<DragDropEventArgs>(SurfaceParameter.DragPrefix, ValidateDragParameter)); DragHandlers.Add(_dragParameters = new DragNames<DragDropEventArgs>(SurfaceParameter.DragPrefix, ValidateDragParameter));
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
}
private void OnScriptsReloadBegin()
{
_activeVisjectCM = null;
_cmPrimaryMenu?.Dispose();
_cmPrimaryMenu = null;
} }
/// <summary> /// <summary>
@@ -1023,6 +1032,8 @@ namespace FlaxEditor.Surface
_activeVisjectCM = null; _activeVisjectCM = null;
_cmPrimaryMenu?.Dispose(); _cmPrimaryMenu?.Dispose();
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
base.OnDestroy(); base.OnDestroy();
} }
} }

View File

@@ -62,6 +62,12 @@ namespace FlaxEditor.Surface
{ {
_supportsImplicitCastFromObjectToBoolean = true; _supportsImplicitCastFromObjectToBoolean = true;
DragHandlers.Add(_dragActors = new DragActors(ValidateDragActor)); DragHandlers.Add(_dragActors = new DragActors(ValidateDragActor));
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
}
private void OnScriptsReloadBegin()
{
_nodesCache.Clear();
} }
private bool ValidateDragActor(ActorNode actor) private bool ValidateDragActor(ActorNode actor)
@@ -631,6 +637,7 @@ namespace FlaxEditor.Surface
{ {
if (IsDisposing) if (IsDisposing)
return; return;
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
_nodesCache.Wait(); _nodesCache.Wait();
base.OnDestroy(); base.OnDestroy();

View File

@@ -352,6 +352,7 @@ namespace FlaxEditor.Tools.Foliage
{ {
AnchorPreset = AnchorPresets.HorizontalStretchTop, AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = new Margin(4, 4, 4, 0), Offsets = new Margin(4, 4, 4, 0),
Pivot = Float2.Zero,
IsScrollable = true, IsScrollable = true,
Parent = splitPanel.Panel1 Parent = splitPanel.Panel1
}; };

View File

@@ -204,6 +204,7 @@ namespace FlaxEditor.Tools.Foliage
{ {
AnchorPreset = AnchorPresets.HorizontalStretchTop, AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = new Margin(4, 4, 4, 0), Offsets = new Margin(4, 4, 4, 0),
Pivot = Float2.Zero,
IsScrollable = true, IsScrollable = true,
Parent = splitPanel.Panel1 Parent = splitPanel.Panel1
}; };

View File

@@ -1211,6 +1211,7 @@ namespace FlaxEditor.Utilities
{ {
Parent = panel1, Parent = panel1,
AnchorPreset = AnchorPresets.HorizontalStretchTop, AnchorPreset = AnchorPresets.HorizontalStretchTop,
Pivot = Float2.Zero,
IsScrollable = true, IsScrollable = true,
}; };
tree = new Tree(false) tree = new Tree(false)

View File

@@ -7,14 +7,11 @@ using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEditor.Tools;
using FlaxEditor.Viewport.Modes; using FlaxEditor.Viewport.Modes;
using FlaxEditor.Windows; using FlaxEditor.Windows;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.Gizmo; using FlaxEngine.Gizmo;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Tools;
using Object = FlaxEngine.Object; using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport namespace FlaxEditor.Viewport
@@ -26,10 +23,8 @@ namespace FlaxEditor.Viewport
public class MainEditorGizmoViewport : EditorGizmoViewport, IEditorPrimitivesOwner public class MainEditorGizmoViewport : EditorGizmoViewport, IEditorPrimitivesOwner
{ {
private readonly Editor _editor; private readonly Editor _editor;
private readonly ContextMenuButton _showGridButton; private readonly ContextMenuButton _showGridButton;
private readonly ContextMenuButton _showNavigationButton; private readonly ContextMenuButton _showNavigationButton;
private SelectionOutline _customSelectionOutline; private SelectionOutline _customSelectionOutline;
/// <summary> /// <summary>
@@ -218,7 +213,7 @@ namespace FlaxEditor.Viewport
TransformGizmo.ApplyTransformation += ApplyTransform; TransformGizmo.ApplyTransformation += ApplyTransform;
TransformGizmo.Duplicate += _editor.SceneEditing.Duplicate; TransformGizmo.Duplicate += _editor.SceneEditing.Duplicate;
Gizmos.Active = TransformGizmo; Gizmos.Active = TransformGizmo;
// Add rubber band selector // Add rubber band selector
_rubberBandSelector = new ViewportRubberBandSelector(this); _rubberBandSelector = new ViewportRubberBandSelector(this);
@@ -375,10 +370,7 @@ namespace FlaxEditor.Viewport
{ {
Gizmos[i].Draw(ref renderContext); Gizmos[i].Draw(ref renderContext);
} }
// Draw RubberBand for rect selection
_rubberBandSelector.Draw(context, target, targetDepth);
// Draw selected objects debug shapes and visuals // Draw selected objects debug shapes and visuals
if (DrawDebugDraw && (renderContext.View.Flags & ViewFlags.DebugDraw) == ViewFlags.DebugDraw) if (DrawDebugDraw && (renderContext.View.Flags & ViewFlags.DebugDraw) == ViewFlags.DebugDraw)
{ {
@@ -594,6 +586,15 @@ namespace FlaxEditor.Viewport
} }
} }
/// <inheritdoc />
public override void Draw()
{
base.Draw();
// Draw rubber band for rectangle selection
_rubberBandSelector.Draw();
}
/// <inheritdoc /> /// <inheritdoc />
protected override void OrientViewport(ref Quaternion orientation) protected override void OrientViewport(ref Quaternion orientation)
{ {
@@ -609,7 +610,8 @@ namespace FlaxEditor.Viewport
base.OnMouseMove(location); base.OnMouseMove(location);
// Don't allow rubber band selection when gizmo is controlling mouse, vertex painting mode, or cloth painting is enabled // Don't allow rubber band selection when gizmo is controlling mouse, vertex painting mode, or cloth painting is enabled
bool canStart = !((Gizmos.Active.IsControllingMouse || Gizmos.Active is VertexPaintingGizmo || Gizmos.Active is ClothPaintingGizmo) || IsControllingMouse || IsRightMouseButtonDown || IsAltKeyDown); bool canStart = !(IsControllingMouse || IsRightMouseButtonDown || IsAltKeyDown) &&
Gizmos.Active is TransformGizmo && !Gizmos.Active.IsControllingMouse;
_rubberBandSelector.TryCreateRubberBand(canStart, _viewMousePos, ViewFrustum); _rubberBandSelector.TryCreateRubberBand(canStart, _viewMousePos, ViewFrustum);
} }

View File

@@ -58,6 +58,8 @@ namespace FlaxEditor.Windows.Assets
InputActions.Add(options => options.Save, Save); InputActions.Add(options => options.Save, Save);
UpdateTitle(); UpdateTitle();
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
} }
/// <summary> /// <summary>
@@ -151,6 +153,8 @@ namespace FlaxEditor.Windows.Assets
/// <inheritdoc /> /// <inheritdoc />
public override void OnDestroy() public override void OnDestroy()
{ {
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
if (_item != null) if (_item != null)
{ {
// Ensure to remove linkage to the item // Ensure to remove linkage to the item
@@ -160,6 +164,15 @@ namespace FlaxEditor.Windows.Assets
base.OnDestroy(); base.OnDestroy();
} }
/// <inheritdoc />
protected virtual void OnScriptsReloadBegin()
{
if (!IsHidden)
{
Editor.Instance.Windows.AddToRestore(this);
}
}
#region IEditable Implementation #region IEditable Implementation
private bool _isEdited; private bool _isEdited;

View File

@@ -268,8 +268,11 @@ namespace FlaxEditor.Windows.Assets
UpdateKnowledge(); UpdateKnowledge();
} }
private void OnScriptsReloadBegin() /// <inheritdoc />
protected override void OnScriptsReloadBegin()
{ {
base.OnScriptsReloadBegin();
// TODO: impl hot-reload for BT to nicely refresh state (save asset, clear undo/properties, reload surface) // TODO: impl hot-reload for BT to nicely refresh state (save asset, clear undo/properties, reload surface)
Close(); Close();
} }

View File

@@ -124,8 +124,10 @@ namespace FlaxEditor.Windows.Assets
UpdateToolstrip(); UpdateToolstrip();
} }
private void OnScriptsReloadBegin() /// <inheritdoc />
protected override void OnScriptsReloadBegin()
{ {
base.OnScriptsReloadBegin();
Close(); Close();
} }
@@ -307,6 +309,20 @@ namespace FlaxEditor.Windows.Assets
base.OnAssetLoadFailed(); base.OnAssetLoadFailed();
} }
/// <inheritdoc />
public override void OnLostFocus()
{
base.OnLostFocus();
_optionsCM?.Dispose();
}
/// <inheritdoc />
public override void OnExit()
{
base.OnExit();
_optionsCM?.Dispose();
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnItemReimported(ContentItem item) public override void OnItemReimported(ContentItem item)
{ {
@@ -329,6 +345,7 @@ namespace FlaxEditor.Windows.Assets
_isRegisteredForScriptsReload = false; _isRegisteredForScriptsReload = false;
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
} }
_optionsCM?.Dispose();
_typeText = null; _typeText = null;
} }
} }

View File

@@ -198,7 +198,6 @@ namespace FlaxEditor.Windows.Assets
Editor.Prefabs.PrefabApplied += OnPrefabApplied; Editor.Prefabs.PrefabApplied += OnPrefabApplied;
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd;
// Setup input actions // Setup input actions
InputActions.Add(options => options.Undo, () => InputActions.Add(options => options.Undo, () =>
@@ -291,8 +290,10 @@ namespace FlaxEditor.Windows.Assets
return false; return false;
} }
private void OnScriptsReloadBegin() /// <inheritdoc />
protected override void OnScriptsReloadBegin()
{ {
base.OnScriptsReloadBegin();
_isScriptsReloading = true; _isScriptsReloading = true;
if (_asset == null || !_asset.IsLoaded) if (_asset == null || !_asset.IsLoaded)
@@ -320,19 +321,8 @@ namespace FlaxEditor.Windows.Assets
Graph.MainActor = null; Graph.MainActor = null;
_viewport.Prefab = null; _viewport.Prefab = null;
_undo?.Clear(); // TODO: maybe don't clear undo? _undo?.Clear(); // TODO: maybe don't clear undo?
}
private void OnScriptsReloadEnd() Close();
{
_isScriptsReloading = false;
if (_asset == null || !_asset.IsLoaded)
return;
// Restore
OnPrefabOpened();
_undo.Clear();
ClearEditedFlag();
} }
private void OnUndoEvent(IUndoAction action) private void OnUndoEvent(IUndoAction action)
@@ -555,7 +545,6 @@ namespace FlaxEditor.Windows.Assets
Editor.Prefabs.PrefabApplied -= OnPrefabApplied; Editor.Prefabs.PrefabApplied -= OnPrefabApplied;
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd;
_undo.Dispose(); _undo.Dispose();
Graph.Dispose(); Graph.Dispose();

View File

@@ -29,6 +29,7 @@ namespace FlaxEditor.Windows
private const string ProjectDataLastViewedFolder = "LastViewedFolder"; private const string ProjectDataLastViewedFolder = "LastViewedFolder";
private bool _isWorkspaceDirty; private bool _isWorkspaceDirty;
private string _workspaceRebuildLocation; private string _workspaceRebuildLocation;
private string _lastViewedFolderBeforeReload;
private SplitPanel _split; private SplitPanel _split;
private Panel _contentViewPanel; private Panel _contentViewPanel;
private Panel _contentTreePanel; private Panel _contentTreePanel;
@@ -144,26 +145,6 @@ namespace FlaxEditor.Windows
FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
// Content database events
editor.ContentDatabase.WorkspaceModified += () => _isWorkspaceDirty = true;
editor.ContentDatabase.ItemRemoved += OnContentDatabaseItemRemoved;
editor.ContentDatabase.WorkspaceRebuilding += () => { _workspaceRebuildLocation = SelectedNode?.Path; };
editor.ContentDatabase.WorkspaceRebuilt += () =>
{
var selected = Editor.ContentDatabase.Find(_workspaceRebuildLocation);
if (selected is ContentFolder selectedFolder)
{
_navigationUnlocked = false;
RefreshView(selectedFolder.Node);
_tree.Select(selectedFolder.Node);
UpdateItemsSearch();
_navigationUnlocked = true;
UpdateUI();
}
else
ShowRoot();
};
var options = Editor.Options; var options = Editor.Options;
options.OptionsChanged += OnOptionsChanged; options.OptionsChanged += OnOptionsChanged;
@@ -1036,6 +1017,61 @@ namespace FlaxEditor.Windows
/// <inheritdoc /> /// <inheritdoc />
public override void OnInit() public override void OnInit()
{
// Content database events
Editor.ContentDatabase.WorkspaceModified += () => _isWorkspaceDirty = true;
Editor.ContentDatabase.ItemRemoved += OnContentDatabaseItemRemoved;
Editor.ContentDatabase.WorkspaceRebuilding += () => { _workspaceRebuildLocation = SelectedNode?.Path; };
Editor.ContentDatabase.WorkspaceRebuilt += () =>
{
var selected = Editor.ContentDatabase.Find(_workspaceRebuildLocation);
if (selected is ContentFolder selectedFolder)
{
_navigationUnlocked = false;
RefreshView(selectedFolder.Node);
_tree.Select(selectedFolder.Node);
UpdateItemsSearch();
_navigationUnlocked = true;
UpdateUI();
}
else if (_root != null)
ShowRoot();
};
Refresh();
// Load last viewed folder
if (Editor.ProjectCache.TryGetCustomData(ProjectDataLastViewedFolder, out string lastViewedFolder))
{
if (Editor.ContentDatabase.Find(lastViewedFolder) is ContentFolder folder)
_tree.Select(folder.Node);
}
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd;
}
private void OnScriptsReloadBegin()
{
var lastViewedFolder = _tree.Selection.Count == 1 ? _tree.SelectedNode as ContentTreeNode : null;
_lastViewedFolderBeforeReload = lastViewedFolder?.Path ?? string.Empty;
_tree.RemoveChild(_root);
_root = null;
}
private void OnScriptsReloadEnd()
{
Refresh();
if (!string.IsNullOrEmpty(_lastViewedFolderBeforeReload))
{
if (Editor.ContentDatabase.Find(_lastViewedFolderBeforeReload) is ContentFolder folder)
_tree.Select(folder.Node);
}
}
private void Refresh()
{ {
// Setup content root node // Setup content root node
_root = new RootContentTreeNode _root = new RootContentTreeNode
@@ -1072,13 +1108,6 @@ namespace FlaxEditor.Windows
// Update UI layout // Update UI layout
_isLayoutLocked = false; _isLayoutLocked = false;
PerformLayout(); PerformLayout();
// Load last viewed folder
if (Editor.ProjectCache.TryGetCustomData(ProjectDataLastViewedFolder, out string lastViewedFolder))
{
if (Editor.ContentDatabase.Find(lastViewedFolder) is ContentFolder folder)
_tree.Select(folder.Node);
}
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -1226,6 +1255,8 @@ namespace FlaxEditor.Windows
_viewDropdown = null; _viewDropdown = null;
Editor.Options.OptionsChanged -= OnOptionsChanged; Editor.Options.OptionsChanged -= OnOptionsChanged;
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd;
base.OnDestroy(); base.OnDestroy();
} }

View File

@@ -390,6 +390,7 @@ namespace FlaxEditor.Windows
_entriesPanel = new VerticalPanel _entriesPanel = new VerticalPanel
{ {
AnchorPreset = AnchorPresets.HorizontalStretchTop, AnchorPreset = AnchorPresets.HorizontalStretchTop,
Pivot = Float2.Zero,
Offsets = Margin.Zero, Offsets = Margin.Zero,
IsScrollable = true, IsScrollable = true,
Parent = _split.Panel1, Parent = _split.Panel1,

View File

@@ -582,7 +582,7 @@ namespace FlaxEditor.Windows
private void OnOutputTextChanged() private void OnOutputTextChanged()
{ {
if (IsLayoutLocked) if (IsLayoutLocked || _output == null)
return; return;
_hScroll.Maximum = Mathf.Max(_output.TextSize.X, _hScroll.Minimum); _hScroll.Maximum = Mathf.Max(_output.TextSize.X, _hScroll.Minimum);

View File

@@ -162,11 +162,13 @@ namespace FlaxEditor.Windows
{ {
AnchorPreset = AnchorPresets.StretchAll, AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero, Offsets = Margin.Zero,
Pivot = Float2.Zero,
Parent = this, Parent = this,
}; };
var panel = new VerticalPanel var panel = new VerticalPanel
{ {
AnchorPreset = AnchorPresets.HorizontalStretchTop, AnchorPreset = AnchorPresets.HorizontalStretchTop,
Pivot = Float2.Zero,
Offsets = Margin.Zero, Offsets = Margin.Zero,
IsScrollable = true, IsScrollable = true,
Parent = scroll, Parent = scroll,
@@ -187,6 +189,7 @@ namespace FlaxEditor.Windows
var vp = new Panel var vp = new Panel
{ {
AnchorPreset = AnchorPresets.StretchAll, AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
Parent = this, Parent = this,
}; };
_addPluginProjectButton = new Button _addPluginProjectButton = new Button

View File

@@ -68,6 +68,7 @@ namespace FlaxEditor.Windows.Profiler
{ {
AnchorPreset = AnchorPresets.HorizontalStretchTop, AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = Margin.Zero, Offsets = Margin.Zero,
Pivot = Float2.Zero,
IsScrollable = true, IsScrollable = true,
Parent = panel, Parent = panel,
}; };

View File

@@ -88,6 +88,7 @@ namespace FlaxEditor.Windows.Profiler
{ {
AnchorPreset = AnchorPresets.HorizontalStretchTop, AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = Margin.Zero, Offsets = Margin.Zero,
Pivot = Float2.Zero,
IsScrollable = true, IsScrollable = true,
Parent = panel, Parent = panel,
}; };

View File

@@ -65,6 +65,7 @@ namespace FlaxEditor.Windows.Profiler
{ {
AnchorPreset = AnchorPresets.HorizontalStretchTop, AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = Margin.Zero, Offsets = Margin.Zero,
Pivot = Float2.Zero,
IsScrollable = true, IsScrollable = true,
Parent = panel, Parent = panel,
}; };

View File

@@ -2,6 +2,7 @@
#if USE_PROFILER #if USE_PROFILER
using System; using System;
using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
namespace FlaxEditor.Windows.Profiler namespace FlaxEditor.Windows.Profiler
@@ -29,6 +30,7 @@ namespace FlaxEditor.Windows.Profiler
{ {
AnchorPreset = AnchorPresets.HorizontalStretchTop, AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = Margin.Zero, Offsets = Margin.Zero,
Pivot = Float2.Zero,
IsScrollable = true, IsScrollable = true,
Parent = panel, Parent = panel,
}; };

View File

@@ -69,6 +69,7 @@ namespace FlaxEditor.Windows.Profiler
{ {
AnchorPreset = AnchorPresets.HorizontalStretchTop, AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = Margin.Zero, Offsets = Margin.Zero,
Pivot = Float2.Zero,
IsScrollable = true, IsScrollable = true,
Parent = panel, Parent = panel,
}; };

View File

@@ -63,6 +63,7 @@ namespace FlaxEditor.Windows.Profiler
{ {
AnchorPreset = AnchorPresets.HorizontalStretchTop, AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = Margin.Zero, Offsets = Margin.Zero,
Pivot = Float2.Zero,
IsScrollable = true, IsScrollable = true,
Parent = panel, Parent = panel,
}; };

View File

@@ -33,6 +33,7 @@ namespace FlaxEditor.Windows.Profiler
{ {
AnchorPreset = AnchorPresets.HorizontalStretchTop, AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = Margin.Zero, Offsets = Margin.Zero,
Pivot = Float2.Zero,
IsScrollable = true, IsScrollable = true,
Parent = panel, Parent = panel,
}; };

View File

@@ -33,6 +33,7 @@ namespace FlaxEditor.Windows.Profiler
{ {
AnchorPreset = AnchorPresets.HorizontalStretchTop, AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = Margin.Zero, Offsets = Margin.Zero,
Pivot = Float2.Zero,
IsScrollable = true, IsScrollable = true,
Parent = panel, Parent = panel,
}; };

View File

@@ -92,6 +92,7 @@ namespace FlaxEditor.Windows
Parent = this, Parent = this,
AnchorPreset = AnchorPresets.StretchAll, AnchorPreset = AnchorPresets.StretchAll,
Offset = Vector2.Zero, Offset = Vector2.Zero,
Pivot = Float2.Zero,
AutoSize = false, AutoSize = false,
Bounds = Rectangle.Empty Bounds = Rectangle.Empty
}; };

View File

@@ -150,6 +150,22 @@ namespace FlaxEditor.Windows
_searchBox.Clear(); _searchBox.Clear();
_groupSearch.DisposeChildren(); _groupSearch.DisposeChildren();
_groupSearch.PerformLayout(); _groupSearch.PerformLayout();
// Remove tabs
var tabs = new List<Tab>();
foreach (var child in _actorGroups.Children)
{
if (child is Tab tab)
{
if (tab.Text != "Search")
tabs.Add(tab);
}
}
foreach (var tab in tabs)
{
var group = _actorGroups.Children.Find(T => T == tab);
group.Dispose();
}
} }
private void OnScriptsReloadEnd() private void OnScriptsReloadEnd()

View File

@@ -73,7 +73,7 @@ void AnimationData::Swap(AnimationData& other)
Channels.Swap(other.Channels); Channels.Swap(other.Channels);
} }
void AnimationData::Dispose() void AnimationData::Release()
{ {
Name.Clear(); Name.Clear();
Duration = 0.0; Duration = 0.0;

View File

@@ -170,5 +170,5 @@ public:
/// <summary> /// <summary>
/// Releases data. /// Releases data.
/// </summary> /// </summary>
void Dispose(); void Release();
}; };

View File

@@ -738,7 +738,7 @@ void Animation::unload(bool isReloading)
Level::ScriptsReloadStart.Unbind<Animation, &Animation::OnScriptsReloadStart>(this); Level::ScriptsReloadStart.Unbind<Animation, &Animation::OnScriptsReloadStart>(this);
} }
#endif #endif
Data.Dispose(); Data.Release();
for (const auto& e : Events) for (const auto& e : Events)
{ {
for (const auto& k : e.Second.GetKeyframes()) for (const auto& k : e.Second.GetKeyframes())

View File

@@ -203,7 +203,7 @@ void AnimationGraph::FindDependencies(AnimGraphBase* graph)
{ {
for (const auto& node : graph->Nodes) for (const auto& node : graph->Nodes)
{ {
if (node.Type == GRAPH_NODE_MAKE_TYPE(9, 24)) if (node.Type == GRAPH_NODE_MAKE_TYPE(9, 24) && node.Assets.Count() > 0)
{ {
const auto function = node.Assets[0].As<AnimationGraphFunction>(); const auto function = node.Assets[0].As<AnimationGraphFunction>();
if (function) if (function)

View File

@@ -24,6 +24,7 @@
#include "Engine/Level/Scripts/ModelPrefab.h" #include "Engine/Level/Scripts/ModelPrefab.h"
#include "Engine/Platform/FileSystem.h" #include "Engine/Platform/FileSystem.h"
#include "Engine/Utilities/RectPack.h" #include "Engine/Utilities/RectPack.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerCPU.h"
#include "AssetsImportingManager.h" #include "AssetsImportingManager.h"
@@ -171,6 +172,33 @@ bool SortMeshGroups(IGrouping<StringView, MeshData*> const& i1, IGrouping<String
return i1.GetKey().Compare(i2.GetKey()) < 0; return i1.GetKey().Compare(i2.GetKey()) < 0;
} }
void CloneObject(rapidjson_flax::StringBuffer& buffer, SceneObject* src, SceneObject* dst, bool stripName = false)
{
// Serialize source
buffer.Clear();
CompactJsonWriter writer(buffer);
writer.StartObject();
const void* defaultInstance = src->GetType().GetDefaultInstance();
src->Serialize(writer, defaultInstance);
writer.EndObject();
// Parse json
rapidjson_flax::Document document;
document.Parse(buffer.GetString(), buffer.GetSize());
// Strip unwanted data
document.RemoveMember("ID");
document.RemoveMember("ParentID");
document.RemoveMember("PrefabID");
document.RemoveMember("PrefabObjectID");
if (stripName)
document.RemoveMember("Name");
// Deserialize destination
auto modifier = Cache::ISerializeModifier.Get();
dst->Deserialize(document, &*modifier);
}
CreateAssetResult ImportModel::Import(CreateAssetContext& context) CreateAssetResult ImportModel::Import(CreateAssetContext& context)
{ {
// Get import options // Get import options
@@ -659,6 +687,8 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M
// Create prefab structure // Create prefab structure
Dictionary<int32, Actor*> nodeToActor; Dictionary<int32, Actor*> nodeToActor;
Dictionary<Guid, SceneObject*> newPrefabObjects; // Maps prefab object id to the restored and linked object
rapidjson_flax::StringBuffer jsonBuffer;
Array<Actor*> nodeActors; Array<Actor*> nodeActors;
Actor* rootActor = nullptr; Actor* rootActor = nullptr;
for (int32 nodeIndex = 0; nodeIndex < data.Nodes.Count(); nodeIndex++) for (int32 nodeIndex = 0; nodeIndex < data.Nodes.Count(); nodeIndex++)
@@ -749,7 +779,6 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M
// Link with object from prefab (if reimporting) // Link with object from prefab (if reimporting)
if (prefab) if (prefab)
{ {
rapidjson_flax::StringBuffer buffer;
for (Actor* a : nodeActors) for (Actor* a : nodeActors)
{ {
for (const auto& i : prefab->ObjectsCache) for (const auto& i : prefab->ObjectsCache)
@@ -761,33 +790,12 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M
continue; continue;
// Preserve local changes made in the prefab // Preserve local changes made in the prefab
{ CloneObject(jsonBuffer, o, a, true);
// Serialize
buffer.Clear();
CompactJsonWriter writer(buffer);
writer.StartObject();
const void* defaultInstance = o->GetType().GetDefaultInstance();
o->Serialize(writer, defaultInstance);
writer.EndObject();
// Parse json
rapidjson_flax::Document document;
document.Parse(buffer.GetString(), buffer.GetSize());
// Strip unwanted data
document.RemoveMember("ID");
document.RemoveMember("ParentID");
document.RemoveMember("PrefabID");
document.RemoveMember("PrefabObjectID");
document.RemoveMember("Name");
// Deserialize object
auto modifier = Cache::ISerializeModifier.Get();
a->Deserialize(document, &*modifier);
}
// Mark as this object already exists in prefab so will be preserved when updating it // Mark as this object already exists in prefab so will be preserved when updating it
a->LinkPrefab(o->GetPrefabID(), o->GetPrefabObjectID()); const Guid prefabObjectId = o->GetPrefabObjectID();
a->LinkPrefab(o->GetPrefabID(), prefabObjectId);
newPrefabObjects.Add(prefabObjectId, a);
break; break;
} }
} }
@@ -808,12 +816,43 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M
{ {
if (i.Value->GetTypeHandle() == modelPrefabScript->GetTypeHandle()) if (i.Value->GetTypeHandle() == modelPrefabScript->GetTypeHandle())
{ {
modelPrefabScript->LinkPrefab(i.Value->GetPrefabID(), i.Value->GetPrefabObjectID()); const Guid prefabObjectId = i.Value->GetPrefabObjectID();
modelPrefabScript->LinkPrefab(i.Value->GetPrefabID(), prefabObjectId);
newPrefabObjects.Add(prefabObjectId, modelPrefabScript);
break; break;
} }
} }
} }
} }
if (prefab)
{
// Preserve existing objects added by user (eg. colliders, sfx, vfx, scripts)
for (const auto& i : prefab->ObjectsCache)
{
// Skip already restored objects
const Guid prefabObjectId = i.Key;
if (newPrefabObjects.ContainsKey(prefabObjectId))
continue;
SceneObject* defaultObject = i.Value;
// TODO: ignore objects that were imported previously but not now (eg. mesh was removed from source asset)
// Find parent to link
SceneObject* parent;
if (!newPrefabObjects.TryGet(defaultObject->GetParent()->GetPrefabObjectID(), parent))
continue;
// Duplicate object
SceneObject* restoredObject = (SceneObject*)Scripting::NewObject(defaultObject->GetTypeHandle());
if (!restoredObject)
continue;
CloneObject(jsonBuffer, defaultObject, restoredObject);
restoredObject->SetParent((Actor*)parent);
// Link with existing prefab instance
restoredObject->LinkPrefab(i.Value->GetPrefabID(), prefabObjectId);
newPrefabObjects.Add(prefabObjectId, restoredObject);
}
}
// Create prefab instead of native asset // Create prefab instead of native asset
bool failed; bool failed;

View File

@@ -176,6 +176,7 @@ namespace FlaxEngine.Interop
_managedHandle.Free(); _managedHandle.Free();
_unmanagedData = IntPtr.Zero; _unmanagedData = IntPtr.Zero;
} }
_arrayType = _elementType = null;
ManagedArrayPool.Put(this); ManagedArrayPool.Put(this);
} }
@@ -442,22 +443,25 @@ namespace FlaxEngine.Interop
/// <summary> /// <summary>
/// Tries to free all references to old weak handles so GC can collect them. /// Tries to free all references to old weak handles so GC can collect them.
/// </summary> /// </summary>
internal static void TryCollectWeakHandles() internal static void TryCollectWeakHandles(bool force = false)
{ {
if (weakHandleAccumulator < nextWeakPoolCollection) if (!force)
return; {
if (weakHandleAccumulator < nextWeakPoolCollection)
return;
nextWeakPoolCollection = weakHandleAccumulator + 1000; nextWeakPoolCollection = weakHandleAccumulator + 1000;
// Try to swap pools after garbage collection or whenever the pool gets too large // Try to swap pools after garbage collection or whenever the pool gets too large
var gc0CollectionCount = GC.CollectionCount(0); var gc0CollectionCount = GC.CollectionCount(0);
if (gc0CollectionCount < nextWeakPoolGCCollection && weakPool.Count < WeakPoolCollectionSizeThreshold) if (gc0CollectionCount < nextWeakPoolGCCollection && weakPool.Count < WeakPoolCollectionSizeThreshold)
return; return;
nextWeakPoolGCCollection = gc0CollectionCount + 1; nextWeakPoolGCCollection = gc0CollectionCount + 1;
// Prevent huge allocations from swapping the pools in the middle of the operation // Prevent huge allocations from swapping the pools in the middle of the operation
if (System.Diagnostics.Stopwatch.GetElapsedTime(lastWeakPoolCollectionTime).TotalMilliseconds < WeakPoolCollectionTimeThreshold) if (System.Diagnostics.Stopwatch.GetElapsedTime(lastWeakPoolCollectionTime).TotalMilliseconds < WeakPoolCollectionTimeThreshold)
return; return;
}
lastWeakPoolCollectionTime = System.Diagnostics.Stopwatch.GetTimestamp(); lastWeakPoolCollectionTime = System.Diagnostics.Stopwatch.GetTimestamp();
// Swap the pools and release the oldest pool for GC // Swap the pools and release the oldest pool for GC

View File

@@ -972,7 +972,49 @@ namespace FlaxEngine.Interop
} }
[UnmanagedCallersOnly] [UnmanagedCallersOnly]
internal static void ReloadScriptingAssemblyLoadContext() internal static void CreateScriptingAssemblyLoadContext()
{
#if FLAX_EDITOR
if (scriptingAssemblyLoadContext != null)
{
// Wait for previous ALC to finish unloading, track it without holding strong references to it
GCHandle weakRef = GCHandle.Alloc(scriptingAssemblyLoadContext, GCHandleType.WeakTrackResurrection);
scriptingAssemblyLoadContext = null;
#if false
// In case the ALC doesn't unload properly: https://learn.microsoft.com/en-us/dotnet/standard/assembly/unloadability#debug-unloading-issues
while (true)
#else
for (int attempts = 5; attempts > 0; attempts--)
#endif
{
GC.Collect();
GC.WaitForPendingFinalizers();
if (!IsHandleAlive(weakRef))
break;
System.Threading.Thread.Sleep(1);
}
if (IsHandleAlive(weakRef))
Debug.Logger.LogHandler.LogWrite(LogType.Warning, "Scripting AssemblyLoadContext was not unloaded.");
weakRef.Free();
static bool IsHandleAlive(GCHandle weakRef)
{
// Checking the target in scope somehow holds a reference to it...?
return weakRef.Target != null;
}
}
scriptingAssemblyLoadContext = new AssemblyLoadContext("Flax", isCollectible: true);
scriptingAssemblyLoadContext.Resolving += OnScriptingAssemblyLoadContextResolving;
#else
scriptingAssemblyLoadContext = new AssemblyLoadContext("Flax", isCollectible: false);
#endif
DelegateHelpers.InitMethods();
}
[UnmanagedCallersOnly]
internal static void UnloadScriptingAssemblyLoadContext()
{ {
#if FLAX_EDITOR #if FLAX_EDITOR
// Clear all caches which might hold references to assemblies in collectible ALC // Clear all caches which might hold references to assemblies in collectible ALC
@@ -990,24 +1032,86 @@ namespace FlaxEngine.Interop
handle.Free(); handle.Free();
propertyHandleCacheCollectible.Clear(); propertyHandleCacheCollectible.Clear();
foreach (var key in assemblyHandles.Keys.Where(x => x.IsCollectible))
assemblyHandles.Remove(key);
foreach (var key in assemblyOwnedNativeLibraries.Keys.Where(x => x.IsCollectible))
assemblyOwnedNativeLibraries.Remove(key);
_typeSizeCache.Clear(); _typeSizeCache.Clear();
foreach (var pair in classAttributesCacheCollectible) foreach (var pair in classAttributesCacheCollectible)
pair.Value.Free(); pair.Value.Free();
classAttributesCacheCollectible.Clear(); classAttributesCacheCollectible.Clear();
ArrayFactory.marshalledTypes.Clear();
ArrayFactory.arrayTypes.Clear();
ArrayFactory.createArrayDelegates.Clear();
FlaxEngine.Json.JsonSerializer.ResetCache(); FlaxEngine.Json.JsonSerializer.ResetCache();
DelegateHelpers.Release();
// Ensure both pools are empty
ManagedHandle.ManagedHandlePool.TryCollectWeakHandles(true);
ManagedHandle.ManagedHandlePool.TryCollectWeakHandles(true);
GC.Collect();
GC.WaitForPendingFinalizers();
{
// HACK: Workaround for TypeDescriptor holding references to collectible types (https://github.com/dotnet/runtime/issues/30656)
Type typeDescriptionProviderType = typeof(System.ComponentModel.TypeDescriptionProvider);
MethodInfo clearCacheMethod = typeDescriptionProviderType?.Assembly.GetType("System.ComponentModel.ReflectionCachesUpdateHandler")?.GetMethod("ClearCache");
if (clearCacheMethod != null)
clearCacheMethod.Invoke(null, new object[] { null });
Type TypeDescriptorType = typeof(System.ComponentModel.TypeDescriptor);
object s_providerTable = TypeDescriptorType?.GetField("s_providerTable", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null);
// Added in .NET runtime 8.0.10, used as the main locking object
object s_commonSyncObject = TypeDescriptorType?.GetField("s_commonSyncObject", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null);
if (s_commonSyncObject == null)
s_commonSyncObject = s_providerTable;
// Removed in .NET runtime 8.0.7
object s_internalSyncObject = TypeDescriptorType?.GetField("s_internalSyncObject", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null);
object s_defaultProviders = TypeDescriptorType?.GetField("s_defaultProviders", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null);
if (s_internalSyncObject != null && s_defaultProviders != null)
{
lock (s_internalSyncObject)
InvokeClear(s_defaultProviders);
}
// Replaces s_defaultProviders in 8.0.7
object s_defaultProviderInitialized = TypeDescriptorType?.GetField("s_defaultProviderInitialized", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null);
if (s_commonSyncObject != null && s_defaultProviderInitialized != null)
{
lock (s_commonSyncObject)
InvokeClear(s_defaultProviderInitialized);
}
object s_providerTypeTable = TypeDescriptorType?.GetField("s_providerTypeTable", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null);
if (s_providerTable != null && s_providerTypeTable != null)
{
lock (s_commonSyncObject)
InvokeClear(s_providerTypeTable);
InvokeClear(s_providerTable);
}
static void InvokeClear(object instance)
{
Type type = instance.GetType();
Assertions.Assert.IsTrue(type.Name == "ConcurrentDictionary`2" || type.Name == "Hashtable" || type.Name == "WeakHashtable");
type.GetMethods(BindingFlags.Instance | BindingFlags.Public).FirstOrDefault(x => x.Name == "Clear")?.Invoke(instance, Array.Empty<object>());
}
}
// Unload the ALC // Unload the ALC
bool unloading = true;
scriptingAssemblyLoadContext.Unloading += (alc) => { unloading = false; };
scriptingAssemblyLoadContext.Unload(); scriptingAssemblyLoadContext.Unload();
scriptingAssemblyLoadContext.Resolving -= OnScriptingAssemblyLoadContextResolving;
while (unloading) GC.Collect();
System.Threading.Thread.Sleep(1); GC.WaitForPendingFinalizers();
InitScriptingAssemblyLoadContext();
DelegateHelpers.InitMethods();
#endif #endif
} }

View File

@@ -73,19 +73,6 @@ namespace FlaxEngine.Interop
return nativeLibrary; return nativeLibrary;
} }
private static void InitScriptingAssemblyLoadContext()
{
#if FLAX_EDITOR
var isCollectible = true;
#else
var isCollectible = false;
#endif
scriptingAssemblyLoadContext = new AssemblyLoadContext("Flax", isCollectible);
#if FLAX_EDITOR
scriptingAssemblyLoadContext.Resolving += OnScriptingAssemblyLoadContextResolving;
#endif
}
[UnmanagedCallersOnly] [UnmanagedCallersOnly]
internal static unsafe void Init() internal static unsafe void Init()
{ {
@@ -97,8 +84,6 @@ namespace FlaxEngine.Interop
System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
InitScriptingAssemblyLoadContext();
DelegateHelpers.InitMethods();
} }
#if FLAX_EDITOR #if FLAX_EDITOR
@@ -1475,11 +1460,11 @@ namespace FlaxEngine.Interop
internal static class ArrayFactory internal static class ArrayFactory
{ {
private delegate Array CreateArrayDelegate(long size); internal delegate Array CreateArrayDelegate(long size);
private static ConcurrentDictionary<Type, Type> marshalledTypes = new ConcurrentDictionary<Type, Type>(1, 3); internal static ConcurrentDictionary<Type, Type> marshalledTypes = new ConcurrentDictionary<Type, Type>(1, 3);
private static ConcurrentDictionary<Type, Type> arrayTypes = new ConcurrentDictionary<Type, Type>(1, 3); internal 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 ConcurrentDictionary<Type, CreateArrayDelegate> createArrayDelegates = new ConcurrentDictionary<Type, CreateArrayDelegate>(1, 3);
internal static Type GetMarshalledType(Type elementType) internal static Type GetMarshalledType(Type elementType)
{ {
@@ -1645,17 +1630,6 @@ namespace FlaxEngine.Interop
return RegisterType(type, true).typeHolder; return RegisterType(type, true).typeHolder;
} }
internal static (TypeHolder typeHolder, ManagedHandle handle) GetTypeHolderAndManagedHandle(Type type)
{
if (managedTypes.TryGetValue(type, out (TypeHolder typeHolder, ManagedHandle handle) tuple))
return tuple;
#if FLAX_EDITOR
if (managedTypesCollectible.TryGetValue(type, out tuple))
return tuple;
#endif
return RegisterType(type, true);
}
/// <summary> /// <summary>
/// Returns a static ManagedHandle to TypeHolder for given Type, and caches it if needed. /// Returns a static ManagedHandle to TypeHolder for given Type, and caches it if needed.
/// </summary> /// </summary>
@@ -1781,6 +1755,14 @@ namespace FlaxEngine.Interop
#endif #endif
} }
internal static void Release()
{
MakeNewCustomDelegateFunc = null;
#if FLAX_EDITOR
MakeNewCustomDelegateFuncCollectible = null;
#endif
}
internal static Type MakeNewCustomDelegate(Type[] parameters) internal static Type MakeNewCustomDelegate(Type[] parameters)
{ {
#if FLAX_EDITOR #if FLAX_EDITOR

View File

@@ -74,7 +74,7 @@ Float2 Screen::ScreenToGameViewport(const Float2& screenPos)
return Editor::Managed->ScreenToGameViewport(screenPos); return Editor::Managed->ScreenToGameViewport(screenPos);
#else #else
auto win = Engine::MainWindow; auto win = Engine::MainWindow;
return win ? win->ScreenToClient(screenPos) : Float2::Minimum; return win ? win->ScreenToClient(screenPos) / win->GetDpiScale() : Float2::Minimum;
#endif #endif
} }
@@ -84,7 +84,7 @@ Float2 Screen::GameViewportToScreen(const Float2& viewportPos)
return Editor::Managed->GameViewportToScreen(viewportPos); return Editor::Managed->GameViewportToScreen(viewportPos);
#else #else
auto win = Engine::MainWindow; auto win = Engine::MainWindow;
return win ? win->ClientToScreen(viewportPos) : Float2::Minimum; return win ? win->ClientToScreen(viewportPos * win->GetDpiScale()) : Float2::Minimum;
#endif #endif
} }

View File

@@ -174,8 +174,10 @@ public:
/// <summary> /// <summary>
/// Determines whether depth buffer is binded to the pipeline. /// Determines whether depth buffer is binded to the pipeline.
/// [Deprecated in v1.10]
/// </summary> /// </summary>
/// <returns><c>true</c> if depth buffer is binded; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if depth buffer is binded; otherwise, <c>false</c>.</returns>
DEPRECATED("IsDepthBufferBinded has been deprecated and will be removed in ")
virtual bool IsDepthBufferBinded() = 0; virtual bool IsDepthBufferBinded() = 0;
public: public:

View File

@@ -34,8 +34,8 @@ void GUIMaterialShader::Bind(BindParameters& params)
auto materialData = reinterpret_cast<GUIMaterialShaderData*>(cb.Get()); auto materialData = reinterpret_cast<GUIMaterialShaderData*>(cb.Get());
cb = cb.Slice(sizeof(GUIMaterialShaderData)); cb = cb.Slice(sizeof(GUIMaterialShaderData));
int32 srv = 0; int32 srv = 0;
const auto ps = context->IsDepthBufferBinded() ? _cache.Depth : _cache.NoDepth;
auto customData = (Render2D::CustomData*)params.CustomData; auto customData = (Render2D::CustomData*)params.CustomData;
const auto ps = customData->UseDepthBuffer ? _cache.Depth : _cache.NoDepth;
// Setup parameters // Setup parameters
MaterialParameter::BindMeta bindMeta; MaterialParameter::BindMeta bindMeta;
@@ -83,26 +83,21 @@ void GUIMaterialShader::Unload()
bool GUIMaterialShader::Load() bool GUIMaterialShader::Load()
{ {
GPUPipelineState::Description psDesc0 = GPUPipelineState::Description::DefaultFullscreenTriangle; auto desc = GPUPipelineState::Description::DefaultFullscreenTriangle;
psDesc0.Wireframe = EnumHasAnyFlags(_info.FeaturesFlags, MaterialFeaturesFlags::Wireframe); desc.Wireframe = EnumHasAnyFlags(_info.FeaturesFlags, MaterialFeaturesFlags::Wireframe);
psDesc0.VS = _shader->GetVS("VS_GUI"); desc.VS = _shader->GetVS("VS_GUI");
psDesc0.PS = _shader->GetPS("PS_GUI"); desc.PS = _shader->GetPS("PS_GUI");
psDesc0.BlendMode = BlendingMode::AlphaBlend; desc.BlendMode = BlendingMode::AlphaBlend;
desc.DepthEnable = true;
psDesc0.DepthEnable = psDesc0.DepthWriteEnable = true;
_cache.Depth = GPUDevice::Instance->CreatePipelineState(); _cache.Depth = GPUDevice::Instance->CreatePipelineState();
_cache.NoDepth = GPUDevice::Instance->CreatePipelineState(); _cache.NoDepth = GPUDevice::Instance->CreatePipelineState();
bool failed = _cache.Depth->Init(desc);
bool failed = _cache.Depth->Init(psDesc0); desc.DepthEnable = false;
failed |= _cache.NoDepth->Init(desc);
psDesc0.DepthEnable = psDesc0.DepthWriteEnable = false;
failed |= _cache.NoDepth->Init(psDesc0);
if (failed) if (failed)
{ {
LOG(Warning, "Failed to create GUI material pipeline state."); LOG(Warning, "Failed to create GUI material pipeline state.");
return true; return true;
} }
return false; return false;
} }

View File

@@ -768,10 +768,7 @@ void Render2D::End()
IsScissorsRectEmpty = false; IsScissorsRectEmpty = false;
for (int32 i = 0; i < DrawCalls.Count(); i++) for (int32 i = 0; i < DrawCalls.Count(); i++)
{ {
// Peek draw call
const auto& drawCall = DrawCalls[i]; const auto& drawCall = DrawCalls[i];
// Check if cannot add element to the batching
if (batchSize != 0 && !CanBatchDrawCalls(DrawCalls[batchStart], drawCall)) if (batchSize != 0 && !CanBatchDrawCalls(DrawCalls[batchStart], drawCall))
{ {
// Flush batched elements // Flush batched elements
@@ -999,6 +996,7 @@ void DrawBatch(int32 startIndex, int32 count)
Render2D::CustomData customData; Render2D::CustomData customData;
customData.ViewProjection = ViewProjection; customData.ViewProjection = ViewProjection;
customData.ViewSize = Float2::One; customData.ViewSize = Float2::One;
customData.UseDepthBuffer = DepthBuffer != nullptr;
bindParams.CustomData = &customData; bindParams.CustomData = &customData;
material->Bind(bindParams); material->Bind(bindParams);
@@ -1035,6 +1033,7 @@ void DrawBatch(int32 startIndex, int32 count)
Render2D::CustomData customData; Render2D::CustomData customData;
customData.ViewProjection = ViewProjection; customData.ViewProjection = ViewProjection;
customData.ViewSize = Float2(d.AsMaterial.Width, d.AsMaterial.Height); customData.ViewSize = Float2(d.AsMaterial.Width, d.AsMaterial.Height);
customData.UseDepthBuffer = DepthBuffer != nullptr;
bindParams.CustomData = &customData; bindParams.CustomData = &customData;
material->Bind(bindParams); material->Bind(bindParams);

View File

@@ -55,6 +55,7 @@ API_CLASS(Static) class FLAXENGINE_API Render2D
{ {
Matrix ViewProjection; Matrix ViewProjection;
Float2 ViewSize; Float2 ViewSize;
bool UseDepthBuffer;
}; };
public: public:

View File

@@ -796,12 +796,8 @@ ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSp
// Mark as managed type // Mark as managed type
object->Flags |= ObjectFlags::IsManagedType; object->Flags |= ObjectFlags::IsManagedType;
// Initialize managed instance // Initialize managed instance (ScriptingObject ctor copies managed object handle)
if (params.Managed) if (!params.Managed)
{
object->SetManagedInstance((MObject*)params.Managed);
}
else
{ {
// Invoke managed ctor (to match C++ logic) // Invoke managed ctor (to match C++ logic)
object->CreateManaged(); object->CreateManaged();

View File

@@ -45,9 +45,16 @@ public:
/// </summary> /// </summary>
static void UnloadEngine(); static void UnloadEngine();
/// <summary>
/// Creates the assembly load context for assemblies used by Scripting.
/// </summary>
static void CreateScriptingAssemblyLoadContext();
#if USE_EDITOR #if USE_EDITOR
// Called by Scripting in a middle of hot-reload (after unloading modules but before loading them again). /// <summary>
static void ReloadScriptingAssemblyLoadContext(); /// Called by Scripting in a middle of hot-reload (after unloading modules but before loading them again).
/// </summary>
static void UnloadScriptingAssemblyLoadContext();
#endif #endif
public: public:

View File

@@ -330,9 +330,15 @@ void MCore::UnloadEngine()
ShutdownHostfxr(); ShutdownHostfxr();
} }
void MCore::CreateScriptingAssemblyLoadContext()
{
static void* CreateScriptingAssemblyLoadContextPtr = GetStaticMethodPointer(TEXT("CreateScriptingAssemblyLoadContext"));
CallStaticMethod<void>(CreateScriptingAssemblyLoadContextPtr);
}
#if USE_EDITOR #if USE_EDITOR
void MCore::ReloadScriptingAssemblyLoadContext() void MCore::UnloadScriptingAssemblyLoadContext()
{ {
// Clear any cached class attributes (see https://github.com/FlaxEngine/FlaxEngine/issues/1108) // Clear any cached class attributes (see https://github.com/FlaxEngine/FlaxEngine/issues/1108)
for (const auto& e : CachedClassHandles) for (const auto& e : CachedClassHandles)
@@ -377,8 +383,8 @@ void MCore::ReloadScriptingAssemblyLoadContext()
} }
} }
static void* ReloadScriptingAssemblyLoadContextPtr = GetStaticMethodPointer(TEXT("ReloadScriptingAssemblyLoadContext")); static void* UnloadScriptingAssemblyLoadContextPtr = GetStaticMethodPointer(TEXT("UnloadScriptingAssemblyLoadContext"));
CallStaticMethod<void>(ReloadScriptingAssemblyLoadContextPtr); CallStaticMethod<void>(UnloadScriptingAssemblyLoadContextPtr);
} }
#endif #endif

View File

@@ -715,9 +715,13 @@ void MCore::UnloadEngine()
#endif #endif
} }
void MCore::CreateScriptingAssemblyLoadContext()
{
}
#if USE_EDITOR #if USE_EDITOR
void MCore::ReloadScriptingAssemblyLoadContext() void MCore::UnloadScriptingAssemblyLoadContext()
{ {
} }

View File

@@ -58,9 +58,13 @@ void MCore::UnloadEngine()
MRootDomain = nullptr; MRootDomain = nullptr;
} }
void MCore::CreateScriptingAssemblyLoadContext()
{
}
#if USE_EDITOR #if USE_EDITOR
void MCore::ReloadScriptingAssemblyLoadContext() void MCore::UnloadScriptingAssemblyLoadContext()
{ {
} }

View File

@@ -182,6 +182,8 @@ bool ScriptingService::Init()
return true; return true;
} }
MCore::CreateScriptingAssemblyLoadContext();
// Cache root domain // Cache root domain
_rootDomain = MCore::GetRootDomain(); _rootDomain = MCore::GetRootDomain();
@@ -710,7 +712,8 @@ void Scripting::Reload(bool canTriggerSceneReload)
_hasGameModulesLoaded = false; _hasGameModulesLoaded = false;
// Release and create a new assembly load context for user assemblies // Release and create a new assembly load context for user assemblies
MCore::ReloadScriptingAssemblyLoadContext(); MCore::UnloadScriptingAssemblyLoadContext();
MCore::CreateScriptingAssemblyLoadContext();
// Give GC a try to cleanup old user objects and the other mess // Give GC a try to cleanup old user objects and the other mess
MCore::GC::Collect(); MCore::GC::Collect();

View File

@@ -170,6 +170,10 @@ namespace FlaxEngine
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
Localization.LocalizationChanged += OnLocalizationChanged; Localization.LocalizationChanged += OnLocalizationChanged;
#if FLAX_EDITOR
FlaxEditor.ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
FlaxEditor.ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd;
#endif
OnLocalizationChanged(); OnLocalizationChanged();
if (!Engine.IsEditor) if (!Engine.IsEditor)
@@ -178,6 +182,19 @@ namespace FlaxEngine
} }
} }
#if FLAX_EDITOR
private static void OnScriptsReloadBegin()
{
// Tooltip might hold references to scripting assemblies
Style.Current.SharedTooltip = null;
}
private static void OnScriptsReloadEnd()
{
Style.Current.SharedTooltip = new Tooltip();
}
#endif
private static void OnLocalizationChanged() private static void OnLocalizationChanged()
{ {
// Invariant-globalization only (see InitHostfxr with Mono) // Invariant-globalization only (see InitHostfxr with Mono)
@@ -368,6 +385,10 @@ namespace FlaxEngine
MainThreadTaskScheduler.Dispose(); MainThreadTaskScheduler.Dispose();
Json.JsonSerializer.Dispose(); Json.JsonSerializer.Dispose();
#if FLAX_EDITOR
FlaxEditor.ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
FlaxEditor.ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd;
#endif
} }
/// <summary> /// <summary>

View File

@@ -89,7 +89,11 @@ void SerializableScriptingObject::Deserialize(DeserializeStream& stream, ISerial
} }
ScriptingObject::ScriptingObject(const SpawnParams& params) ScriptingObject::ScriptingObject(const SpawnParams& params)
: _gcHandle(0) #if USE_NETCORE
: _gcHandle((MGCHandle)params.Managed)
#elif !COMPILE_WITHOUT_CSHARP
: _gcHandle(params.Managed ? MCore::GCHandle::New(params.Managed) : 0)
#endif
, _type(params.Type) , _type(params.Type)
, _id(params.ID) , _id(params.ID)
{ {

View File

@@ -473,6 +473,7 @@ namespace FlaxEngine.GUI
{ {
AnchorPreset = AnchorPresets.StretchAll, AnchorPreset = AnchorPresets.StretchAll,
BackgroundColor = Color.Transparent, BackgroundColor = Color.Transparent,
Pivot = Float2.Zero,
IsScrollable = true, IsScrollable = true,
AutoSize = true, AutoSize = true,
Parent = popup.MainPanel, Parent = popup.MainPanel,

View File

@@ -337,9 +337,7 @@ namespace FlaxEngine.GUI
size.X = _textSize.X + Margin.Width; size.X = _textSize.X + Margin.Width;
if (_autoHeight) if (_autoHeight)
size.Y = _textSize.Y + Margin.Height; size.Y = _textSize.Y + Margin.Height;
var pivotRelative = PivotRelative; Resize(ref size);
Size = size;
PivotRelative = pivotRelative;
} }
} }
} }

View File

@@ -233,13 +233,7 @@ namespace FlaxEngine.GUI
{ {
ref TextBlock textBlock = ref textBlocks[i]; ref TextBlock textBlock = ref textBlocks[i];
var textBlockSize = textBlock.Bounds.BottomRight - lineOrigin; var textBlockSize = textBlock.Bounds.BottomRight - lineOrigin;
var ascender = textBlock.Ascender; var ascender = textBlock.GetAscender();
//if (ascender <= 0)
{
var textBlockFont = textBlock.Style.Font.GetFont();
if (textBlockFont)
ascender = textBlockFont.Ascender;
}
lineAscender = Mathf.Max(lineAscender, ascender); lineAscender = Mathf.Max(lineAscender, ascender);
lineSize = Float2.Max(lineSize, textBlockSize); lineSize = Float2.Max(lineSize, textBlockSize);
} }
@@ -256,13 +250,7 @@ namespace FlaxEngine.GUI
case TextBlockStyle.Alignments.Baseline: case TextBlockStyle.Alignments.Baseline:
{ {
// Match the baseline of the line (use ascender) // Match the baseline of the line (use ascender)
var ascender = textBlock.Ascender; var ascender = textBlock.GetAscender();
if (ascender <= 0)
{
var textBlockFont = textBlock.Style.Font.GetFont();
if (textBlockFont)
ascender = textBlockFont.Ascender;
}
vOffset = lineAscender - ascender; vOffset = lineAscender - ascender;
textBlock.Bounds.Location.Y += vOffset; textBlock.Bounds.Location.Y += vOffset;
break; break;

View File

@@ -176,7 +176,8 @@ namespace FlaxEngine.GUI
var font = imageBlock.Style.Font.GetFont(); var font = imageBlock.Style.Font.GetFont();
if (font) if (font)
imageBlock.Bounds.Size = new Float2(font.Height); imageBlock.Bounds.Size = new Float2(font.Height);
imageBlock.Bounds.Size.X *= image.Size.X / image.Size.Y; // Keep original aspect ratio var imageSize = image.Size;
imageBlock.Bounds.Size.X *= imageSize.X / imageSize.Y; // Keep original aspect ratio
bool hasWidth = TryParseNumberTag(ref tag, "width", imageBlock.Bounds.Width, out var width); bool hasWidth = TryParseNumberTag(ref tag, "width", imageBlock.Bounds.Width, out var width);
imageBlock.Bounds.Width = width; imageBlock.Bounds.Width = width;
bool hasHeight = TryParseNumberTag(ref tag, "height", imageBlock.Bounds.Height, out var height); bool hasHeight = TryParseNumberTag(ref tag, "height", imageBlock.Bounds.Height, out var height);
@@ -185,9 +186,9 @@ namespace FlaxEngine.GUI
{ {
// Maintain aspect ratio after scaling by just width or height // Maintain aspect ratio after scaling by just width or height
if (hasHeight) if (hasHeight)
imageBlock.Bounds.Size.X = imageBlock.Bounds.Size.Y * image.Size.X / image.Size.Y; imageBlock.Bounds.Size.X = imageBlock.Bounds.Size.Y * imageSize.X / imageSize.Y;
else else
imageBlock.Bounds.Size.Y = imageBlock.Bounds.Size.X * image.Size.Y / image.Size.X; imageBlock.Bounds.Size.Y = imageBlock.Bounds.Size.X * imageSize.Y / imageSize.X;
} }
TryParseNumberTag(ref tag, "scale", 1.0f, out var scale); TryParseNumberTag(ref tag, "scale", 1.0f, out var scale);
imageBlock.Bounds.Size *= scale; imageBlock.Bounds.Size *= scale;

View File

@@ -419,6 +419,19 @@ namespace FlaxEngine.GUI
} }
} }
/// <summary>
/// Resizes the control based on where the pivot is rather than just the top-left.
/// </summary>
[NoAnimate]
public void Resize(ref Float2 value)
{
if (_bounds.Size.Equals(ref value))
return;
var bounds = new Rectangle(_bounds.Location, value);
bounds.Location += (_bounds.Size - value) * Pivot; // Pivot-relative resizing
SetBounds(ref bounds);
}
/// <summary> /// <summary>
/// Updates the control cached bounds (based on anchors and offsets). /// Updates the control cached bounds (based on anchors and offsets).
/// </summary> /// </summary>

View File

@@ -122,8 +122,6 @@ namespace FlaxEngine.GUI
if (_parent == value) if (_parent == value)
return; return;
Defocus();
Float2 oldParentSize; Float2 oldParentSize;
if (_parent != null) if (_parent != null)
{ {

View File

@@ -585,7 +585,8 @@ namespace FlaxEngine.GUI
_cachedHeight = height; _cachedHeight = height;
if (_animationProgress >= 1.0f && _isClosed) if (_animationProgress >= 1.0f && _isClosed)
y = minHeight; y = minHeight;
Height = Mathf.Max(minHeight, y); var size = new Float2(Width, Mathf.Max(minHeight, y));
Resize(ref size);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -78,7 +78,7 @@ namespace FlaxEngine.GUI
size.X = left + right; size.X = left + right;
if (!ControlChildSize) if (!ControlChildSize)
size.Y = maxHeight; size.Y = maxHeight;
Size = size; Resize(ref size);
} }
else if (_alignment != TextAlignment.Near && hasAnyLeft) else if (_alignment != TextAlignment.Near && hasAnyLeft)
{ {

View File

@@ -78,7 +78,7 @@ namespace FlaxEngine.GUI
size.Y = top + bottom; size.Y = top + bottom;
if (!ControlChildSize) if (!ControlChildSize)
size.X = maxWidth; size.X = maxWidth;
Size = size; Resize(ref size);
} }
else if (_alignment != TextAlignment.Near && hasAnyTop) else if (_alignment != TextAlignment.Near && hasAnyTop)
{ {

View File

@@ -31,5 +31,18 @@ namespace FlaxEngine.GUI
/// The custom tag. /// The custom tag.
/// </summary> /// </summary>
public object Tag; public object Tag;
internal float GetAscender()
{
float ascender = Ascender;
if (Mathf.IsZero(ascender))
{
// Use ascender from the font
var textBlockFont = Style.Font.GetFont();
if (textBlockFont)
ascender = textBlockFont.Ascender;
}
return ascender;
}
} }
} }

View File

@@ -79,7 +79,7 @@ namespace FlaxEngine.GUI
public Color Color; public Color Color;
/// <summary> /// <summary>
/// The text shadow color (tint and opacity). Set to transparent to disable shadow drawing. /// The text shadow color (tint and opacity). Transparent color disables shadow drawing.
/// </summary> /// </summary>
[EditorOrder(30)] [EditorOrder(30)]
public Color ShadowColor; public Color ShadowColor;

View File

@@ -120,6 +120,7 @@ namespace FlaxEngine.GUI
// Unlink // Unlink
IsLayoutLocked = true; IsLayoutLocked = true;
Parent = null; Parent = null;
_showTarget = null;
// Close window // Close window
if (_window) if (_window)

View File

@@ -23,11 +23,11 @@ MMethod* UICanvas_EndPlay = nullptr;
MMethod* UICanvas_ParentChanged = nullptr; MMethod* UICanvas_ParentChanged = nullptr;
#define UICANVAS_INVOKE(event) \ #define UICANVAS_INVOKE(event) \
auto instance = GetManagedInstance(); \ auto* managed = GetManagedInstance(); \
if (instance) \ if (managed) \
{ \ { \
MObject* exception = nullptr; \ MObject* exception = nullptr; \
UICanvas_##event->Invoke(instance, nullptr, &exception); \ UICanvas_##event->Invoke(managed, nullptr, &exception); \
if (exception) \ if (exception) \
{ \ { \
MException ex(exception); \ MException ex(exception); \

View File

@@ -314,7 +314,8 @@ namespace FlaxEngine
{ {
_guiRoot = new CanvasRootControl(this) _guiRoot = new CanvasRootControl(this)
{ {
IsLayoutLocked = false IsLayoutLocked = false,
Pivot = Float2.Zero,
}; };
} }

View File

@@ -22,10 +22,11 @@ MMethod* UIControl_BeginPlay = nullptr;
MMethod* UIControl_EndPlay = nullptr; MMethod* UIControl_EndPlay = nullptr;
#define UICONTROL_INVOKE(event) \ #define UICONTROL_INVOKE(event) \
if (HasManagedInstance()) \ auto* managed = GetManagedInstance(); \
if (managed) \
{ \ { \
MObject* exception = nullptr; \ MObject* exception = nullptr; \
UIControl_##event->Invoke(GetManagedInstance(), nullptr, &exception); \ UIControl_##event->Invoke(managed, nullptr, &exception); \
if (exception) \ if (exception) \
{ \ { \
MException ex(exception); \ MException ex(exception); \

View File

@@ -35,38 +35,58 @@ namespace FlaxEngine
// Set value // Set value
_control = value; _control = value;
if (_control == null)
return;
// Link the new one (events and parent) // Setup control
if (_control != null) var isDuringPlay = IsDuringPlay;
_blockEvents = true;
var container = _control as ContainerControl;
if (container != null)
{ {
// Setup control if (isDuringPlay)
_blockEvents = true; container.UnlockChildrenRecursive(); // Enable layout changes to any dynamically added UI
var containerControl = _control as ContainerControl; else
if (containerControl != null) container.LockChildrenRecursive(); // Block layout changes during deserialization
containerControl.UnlockChildrenRecursive(); }
_control.Visible = IsActive; _control.Visible = IsActive;
_control.Parent = GetParent(); {
_control.IndexInParent = OrderInParent; var parent = GetParent();
_control.Location = new Float2(LocalPosition); if (parent != null && !parent.IsLayoutLocked && !isDuringPlay)
_control.LocationChanged += OnControlLocationChanged;
// Link children UI controls
if (containerControl != null)
{ {
var children = ChildrenCount; // Reparent but prevent layout if we're during pre-game setup (eg. deserialization) to avoid UI breaking during auto-layout resizing
var parent = Parent; parent.IsLayoutLocked = true;
for (int i = 0; i < children; i++) _control.Parent = parent;
parent.IsLayoutLocked = false;
}
else
{
_control.Parent = parent;
}
}
_control.IndexInParent = OrderInParent;
_control.Location = new Float2(LocalPosition);
_control.LocationChanged += OnControlLocationChanged;
// Link children UI controls
if (container != null)
{
var children = ChildrenCount;
var parent = Parent;
for (int i = 0; i < children; i++)
{
var child = GetChild(i) as UIControl;
if (child != null && child.HasControl && child != parent)
{ {
var child = GetChild(i) as UIControl; child.Control.Parent = container;
if (child != null && child.HasControl && child != parent)
{
child.Control.Parent = containerControl;
}
} }
} }
}
// Refresh // Refresh layout
_blockEvents = false; _blockEvents = false;
if (isDuringPlay)
{
if (prevControl == null && _control.Parent != null) if (prevControl == null && _control.Parent != null)
_control.Parent.PerformLayout(); _control.Parent.PerformLayout();
else else
@@ -326,11 +346,13 @@ namespace FlaxEngine
{ {
if ((_control == null || _control.GetType() != controlType) && controlType != null) if ((_control == null || _control.GetType() != controlType) && controlType != null)
{ {
// Create a new control
Control = (Control)Activator.CreateInstance(controlType); Control = (Control)Activator.CreateInstance(controlType);
} }
if (_control != null) if (_control != null)
{ {
// Populate control object with properties
Json.JsonSerializer.Deserialize(_control, json); Json.JsonSerializer.Deserialize(_control, json);
// Synchronize actor with control location // Synchronize actor with control location
@@ -374,16 +396,32 @@ namespace FlaxEngine
internal void BeginPlay() internal void BeginPlay()
{ {
if (_control != null) var control = _control;
if (control == null)
return;
// Setup control
control.Visible = IsActive && control.Visible;
control.Parent = GetParent();
control.IndexInParent = OrderInParent;
// Setup navigation (all referenced controls are now loaded)
Internal_GetNavTargets(__unmanagedPtr, out UIControl up, out UIControl down, out UIControl left, out UIControl right);
control.NavTargetUp = up?.Control;
control.NavTargetDown = down?.Control;
control.NavTargetLeft = left?.Control;
control.NavTargetRight = right?.Control;
// Refresh layout (BeginPlay is called for parents first, then skip if already called by outer control)
var container = control as ContainerControl;
if (control.Parent == null || (container != null && container.IsLayoutLocked))
{ {
_control.Visible = IsActive && _control.Visible; if (container != null)
_control.Parent = GetParent(); {
_control.IndexInParent = OrderInParent; //container.UnlockChildrenRecursive();
Internal_GetNavTargets(__unmanagedPtr, out UIControl up, out UIControl down, out UIControl left, out UIControl right); container.IsLayoutLocked = false; // Forces whole children tree lock/unlock sequence in PerformLayout
_control.NavTargetUp = up?.Control; }
_control.NavTargetDown = down?.Control; control.PerformLayout();
_control.NavTargetLeft = left?.Control;
_control.NavTargetRight = right?.Control;
} }
} }

View File

@@ -364,8 +364,8 @@ void ShaderGenerator::ProcessGroupMath(Box* box, Node* node, Value& value)
// Fmod // Fmod
case 40: case 40:
{ {
Value v1 = tryGetValue(node->GetBox(0), Value::Zero); Value v1 = tryGetValue(node->GetBox(0), 0, Value::Zero);
Value v2 = tryGetValue(node->GetBox(1), Value::Zero); Value v2 = tryGetValue(node->GetBox(1), 1, Value::Zero);
value = writeFunction2(node, v1, v2, TEXT("fmod")); value = writeFunction2(node, v1, v2, TEXT("fmod"));
break; break;
} }

View File

@@ -260,7 +260,11 @@ namespace Flax.Build
return true; return true;
var rules = Builder.GenerateRulesAssembly(); var rules = Builder.GenerateRulesAssembly();
var target = rules.GetTarget(name); var target = rules.GetTarget(name);
return target == null || target.Modules.TrueForAll(x => !rules.GetModule(x).BuildNativeCode); return target == null || target.Modules.TrueForAll(moduleName =>
{
var module = rules.GetModule(moduleName);
return module != null && !module.BuildNativeCode;
});
} }
/// <summary> /// <summary>