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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,7 +11,7 @@ namespace FlaxEngine.Gizmo;
/// <summary>
/// Class for adding viewport rubber band selection.
/// </summary>
public class ViewportRubberBandSelector
public sealed class ViewportRubberBandSelector
{
private bool _isMosueCaptured;
private bool _isRubberBandSpanning;
@@ -38,7 +38,7 @@ public class ViewportRubberBandSelector
/// <returns>True if selection started, otherwise false.</returns>
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;
return true;
@@ -90,7 +90,7 @@ public class ViewportRubberBandSelector
_rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero);
_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.Height = mousePosition.Y - _cachedStartingMousePosition.Y;
@@ -162,8 +162,8 @@ public class ViewportRubberBandSelector
projection.Init(_owner.Viewport);
foreach (var node in nodes)
{
// Check for custom can select code
if (!node.CanSelectActorNodeWithSelector())
// Skip actors that cannot be selected
if (!node.CanSelectInViewport)
continue;
var a = node.Actor;
@@ -232,30 +232,15 @@ public class ViewportRubberBandSelector
}
/// <summary>
/// Used to draw the rubber band. Begins render 2D.
/// Draws the ruber band during owner viewport UI drawing.
/// </summary>
/// <param name="context">The GPU Context.</param>
/// <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()
public void Draw()
{
if (!_isRubberBandSpanning)
return;
Render2D.FillRectangle(_rubberBandRect, Style.Current.Selection);
Render2D.DrawRectangle(_rubberBandRect, Style.Current.SelectionBorder);
var style = Style.Current;
Render2D.FillRectangle(_rubberBandRect, style.Selection);
Render2D.DrawRectangle(_rubberBandRect, style.SelectionBorder);
}
/// <summary>

View File

@@ -21,6 +21,7 @@ namespace FlaxEditor.Modules
private bool _enableEvents;
private bool _isDuringFastSetup;
private bool _rebuildFlag;
private bool _rebuildInitFlag;
private int _itemsCreated;
private int _itemsDeleted;
private readonly HashSet<MainContentTreeNode> _dirtyNodes = new HashSet<MainContentTreeNode>();
@@ -61,7 +62,7 @@ namespace FlaxEditor.Modules
public event Action WorkspaceModified;
/// <summary>
/// Occurs when workspace has will be rebuilt.
/// Occurs when workspace will be rebuilt.
/// </summary>
public event Action WorkspaceRebuilding;
@@ -88,6 +89,9 @@ namespace FlaxEditor.Modules
// Register AssetItems serialization helper (serialize ref ID only)
FlaxEngine.Json.JsonSerializer.Settings.Converters.Add(new AssetItemConverter());
ScriptsBuilder.ScriptsReload += OnScriptsReload;
ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd;
}
private void OnContentAssetDisposing(Asset asset)
@@ -817,6 +821,7 @@ namespace FlaxEditor.Modules
Profiler.BeginEvent("ContentDatabase.Rebuild");
var startTime = Platform.TimeSeconds;
_rebuildFlag = false;
_rebuildInitFlag = false;
_enableEvents = false;
// Load all folders
@@ -1230,8 +1235,6 @@ namespace FlaxEditor.Modules
LoadProjects(Game.Project);
}
RebuildInternal();
Editor.ContentImporting.ImportFileEnd += (obj, failed) =>
{
var path = obj.ResultUrl;
@@ -1239,6 +1242,15 @@ namespace FlaxEditor.Modules
FlaxEngine.Scripting.InvokeOnUpdate(() => OnImportFileDone(path));
};
_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)
@@ -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 />
public override void OnUpdate()
{
@@ -1340,6 +1398,8 @@ namespace FlaxEditor.Modules
public override void OnExit()
{
FlaxEngine.Content.AssetDisposing -= OnContentAssetDisposing;
ScriptsBuilder.ScriptsReload -= OnScriptsReload;
ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd;
// Disable events
_enableEvents = false;

View File

@@ -391,6 +391,20 @@ namespace FlaxEditor.Modules
public override void OnInit()
{
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 />
@@ -451,6 +465,7 @@ namespace FlaxEditor.Modules
/// <inheritdoc />
public override void OnExit()
{
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
EndWorker();
}
}

View File

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

View File

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

View File

@@ -5,10 +5,12 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using FlaxEditor.Content;
using FlaxEditor.GUI.Dialogs;
using FlaxEditor.GUI.Docking;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEditor.Windows.Profiler;
@@ -39,6 +41,7 @@ namespace FlaxEditor.Modules
public DockState DockState;
public DockPanel DockedTo;
public int DockedTabIndex;
public float? SplitterValue = null;
public bool SelectOnShow = false;
@@ -48,6 +51,8 @@ namespace FlaxEditor.Modules
public Float2 FloatSize;
public Float2 FloatPosition;
public Guid AssetItemID;
// Constructor, to allow for default values
public WindowRestoreData()
{
@@ -803,43 +808,64 @@ namespace FlaxEditor.Modules
Level.SceneSaving += OnSceneSaving;
Level.SceneUnloaded += OnSceneUnloaded;
Level.SceneUnloading += OnSceneUnloading;
ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd;
Editor.ContentDatabase.WorkspaceRebuilt += OnWorkspaceRebuilt;
Editor.StateMachine.StateChanged += OnEditorStateChanged;
}
internal void AddToRestore(AssetEditorWindow win)
{
AddToRestore(win, win.GetType(), new WindowRestoreData
{
AssetItemID = win.Item.ID,
});
}
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
var constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
if (constructor == null || type.IsGenericType)
return;
var winData = new WindowRestoreData();
var panel = win.Window.ParentDockPanel;
var panel = win.ParentDockPanel;
// Ensure that this window is only selected following recompilation
// if it was the active tab in its dock panel. Otherwise, there is a
// risk of interrupting the user's workflow by potentially selecting
// background tabs.
winData.SelectOnShow = panel.SelectedTab == win.Window;
if (panel is FloatWindowDockPanel)
var window = win.RootWindow.Window;
winData.SelectOnShow = panel.SelectedTab == win;
winData.DockedTabIndex = 0;
if (panel is FloatWindowDockPanel && window != null && panel.TabsCount == 1)
{
winData.DockState = DockState.Float;
var window = win.Window.RootWindow.Window;
winData.FloatPosition = window.Position;
winData.FloatSize = window.ClientSize;
winData.Maximize = window.IsMaximized;
winData.Minimize = window.IsMinimized;
winData.DockedTo = panel;
}
else
{
for (int i = 0; i < panel.Tabs.Count; i++)
{
if (panel.Tabs[i] == win)
{
winData.DockedTabIndex = i;
break;
}
}
if (panel.TabsCount > 1)
{
winData.DockState = DockState.DockFill;
winData.DockedTo = panel;
}else
}
else
{
winData.DockState = panel.TryGetDockState(out var splitterValue);
winData.DockedTo = panel.ParentDockPanel;
@@ -851,38 +877,93 @@ namespace FlaxEditor.Modules
_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];
try
{
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);
if (type != null)
var ctor = type.GetConstructor(new Type[] { typeof(Editor), typeof(AssetItem) });
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);
win.Show(winData.DockState, winData.DockedTo, winData.SelectOnShow, winData.SplitterValue);
if (winData.DockState == DockState.Float)
var window = win.RootWindow.Window;
window.Position = winData.FloatPosition;
if (winData.Maximize)
{
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;
}
window.Maximize();
}
else if (winData.Minimize)
{
window.Minimize();
}
else
{
window.ClientSize = winData.FloatSize;
}
// Update panel reference in other windows docked to this panel
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));
}
}
// Restored windows stole the focus from Editor
if (_restoreWindows.Count > 0)
Editor.Instance.Windows.MainWindow.Focus();
_restoreWindows.Clear();
}
@@ -1048,7 +1134,7 @@ namespace FlaxEditor.Modules
Level.SceneSaving -= OnSceneSaving;
Level.SceneUnloaded -= OnSceneUnloaded;
Level.SceneUnloading -= OnSceneUnloading;
ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd;
Editor.ContentDatabase.WorkspaceRebuilt -= OnWorkspaceRebuilt;
Editor.StateMachine.StateChanged -= OnEditorStateChanged;
// Close main window

View File

@@ -192,7 +192,7 @@ namespace FlaxEditor.SceneGraph
GetAllChildActorNodes(nodes);
return nodes.ToArray();
}
/// <summary>
/// Get all nested actor nodes under this actor node.
/// </summary>
@@ -213,12 +213,18 @@ namespace FlaxEditor.SceneGraph
}
/// <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>
/// <returns>True if the actor node can be selected</returns>
public virtual bool CanSelectActorNodeWithSelector()
public virtual bool CanSelectInViewport
{
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>

View File

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

View File

@@ -169,7 +169,6 @@ namespace FlaxEditor.SceneGraph.Actors
var actor = new BoxCollider
{
StaticFlags = staticModelNode.Actor.StaticFlags,
Transform = staticModelNode.Actor.Transform,
};
staticModelNode.Root.Spawn(actor, staticModelNode.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
{
StaticFlags = staticModelNode.Actor.StaticFlags,
Transform = staticModelNode.Actor.Transform,
};
staticModelNode.Root.Spawn(actor, staticModelNode.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
{
StaticFlags = staticModelNode.Actor.StaticFlags,
Transform = staticModelNode.Actor.Transform,
Size = new Float3(100.0f, 100.0f, 1.0f),
};
staticModelNode.Root.Spawn(actor, staticModelNode.Actor);
@@ -203,7 +200,6 @@ namespace FlaxEditor.SceneGraph.Actors
var actor = new CapsuleCollider
{
StaticFlags = staticModelNode.Actor.StaticFlags,
Transform = staticModelNode.Actor.Transform,
Radius = 25.0f,
Height = 50.0f,
};
@@ -220,7 +216,6 @@ namespace FlaxEditor.SceneGraph.Actors
var actor = new MeshCollider
{
StaticFlags = staticModelNode.Actor.StaticFlags,
Transform = staticModelNode.Actor.Transform,
CollisionData = collisionData,
};
staticModelNode.Root.Spawn(actor, staticModelNode.Actor);

View File

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

View File

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

View File

@@ -164,10 +164,21 @@ namespace FlaxEditor.States
{
Assert.AreEqual(Guid.Empty, _lastSceneFromRequest, "Invalid state.");
// Bind events
Level.SceneLoaded += OnSceneEvent;
Level.SceneLoadError += OnSceneEvent;
Level.SceneUnloaded += OnSceneEvent;
// Bind events, only bind loading event and error if re-loading the same scene to avoid issues.
if (_scenesToUnload.Count == 1 && _scenesToLoad.Count == 1)
{
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
for (int i = 0; i < _scenesToUnload.Count; i++)

View File

@@ -161,6 +161,8 @@ namespace FlaxEditor.Surface
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
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)
: base(owner, onSave, undo, CreateStyle())
{
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
}
private static SurfaceStyle CreateStyle()
@@ -35,6 +36,11 @@ namespace FlaxEditor.Surface
return style;
}
private void OnScriptsReloadBegin()
{
_nodesCache.Clear();
}
private static void DrawBox(Box box)
{
var rect = new Rectangle(Float2.Zero, box.Size);
@@ -186,6 +192,7 @@ namespace FlaxEditor.Surface
{
if (IsDisposing)
return;
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
_nodesCache.Wait();
base.OnDestroy();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,14 +7,11 @@ using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEditor.Tools;
using FlaxEditor.Viewport.Modes;
using FlaxEditor.Windows;
using FlaxEngine;
using FlaxEngine.Gizmo;
using FlaxEngine.GUI;
using FlaxEngine.Tools;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport
@@ -26,10 +23,8 @@ namespace FlaxEditor.Viewport
public class MainEditorGizmoViewport : EditorGizmoViewport, IEditorPrimitivesOwner
{
private readonly Editor _editor;
private readonly ContextMenuButton _showGridButton;
private readonly ContextMenuButton _showNavigationButton;
private SelectionOutline _customSelectionOutline;
/// <summary>
@@ -218,7 +213,7 @@ namespace FlaxEditor.Viewport
TransformGizmo.ApplyTransformation += ApplyTransform;
TransformGizmo.Duplicate += _editor.SceneEditing.Duplicate;
Gizmos.Active = TransformGizmo;
// Add rubber band selector
_rubberBandSelector = new ViewportRubberBandSelector(this);
@@ -375,10 +370,7 @@ namespace FlaxEditor.Viewport
{
Gizmos[i].Draw(ref renderContext);
}
// Draw RubberBand for rect selection
_rubberBandSelector.Draw(context, target, targetDepth);
// Draw selected objects debug shapes and visuals
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 />
protected override void OrientViewport(ref Quaternion orientation)
{
@@ -609,7 +610,8 @@ namespace FlaxEditor.Viewport
base.OnMouseMove(location);
// 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);
}

View File

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

View File

@@ -268,8 +268,11 @@ namespace FlaxEditor.Windows.Assets
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)
Close();
}

View File

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

View File

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

View File

@@ -29,6 +29,7 @@ namespace FlaxEditor.Windows
private const string ProjectDataLastViewedFolder = "LastViewedFolder";
private bool _isWorkspaceDirty;
private string _workspaceRebuildLocation;
private string _lastViewedFolderBeforeReload;
private SplitPanel _split;
private Panel _contentViewPanel;
private Panel _contentTreePanel;
@@ -144,26 +145,6 @@ namespace FlaxEditor.Windows
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;
options.OptionsChanged += OnOptionsChanged;
@@ -1036,6 +1017,61 @@ namespace FlaxEditor.Windows
/// <inheritdoc />
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
_root = new RootContentTreeNode
@@ -1072,13 +1108,6 @@ namespace FlaxEditor.Windows
// Update UI layout
_isLayoutLocked = false;
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 />
@@ -1226,6 +1255,8 @@ namespace FlaxEditor.Windows
_viewDropdown = null;
Editor.Options.OptionsChanged -= OnOptionsChanged;
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd;
base.OnDestroy();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -150,6 +150,22 @@ namespace FlaxEditor.Windows
_searchBox.Clear();
_groupSearch.DisposeChildren();
_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()

View File

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

View File

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

View File

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

View File

@@ -203,7 +203,7 @@ void AnimationGraph::FindDependencies(AnimGraphBase* graph)
{
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>();
if (function)

View File

@@ -24,6 +24,7 @@
#include "Engine/Level/Scripts/ModelPrefab.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Utilities/RectPack.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "AssetsImportingManager.h"
@@ -171,6 +172,33 @@ bool SortMeshGroups(IGrouping<StringView, MeshData*> const& i1, IGrouping<String
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)
{
// Get import options
@@ -659,6 +687,8 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M
// Create prefab structure
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;
Actor* rootActor = nullptr;
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)
if (prefab)
{
rapidjson_flax::StringBuffer buffer;
for (Actor* a : nodeActors)
{
for (const auto& i : prefab->ObjectsCache)
@@ -761,33 +790,12 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M
continue;
// Preserve local changes made in the prefab
{
// 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);
}
CloneObject(jsonBuffer, o, a, true);
// 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;
}
}
@@ -808,12 +816,43 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M
{
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;
}
}
}
}
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
bool failed;

View File

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

View File

@@ -972,7 +972,49 @@ namespace FlaxEngine.Interop
}
[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
// Clear all caches which might hold references to assemblies in collectible ALC
@@ -990,24 +1032,86 @@ namespace FlaxEngine.Interop
handle.Free();
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();
foreach (var pair in classAttributesCacheCollectible)
pair.Value.Free();
classAttributesCacheCollectible.Clear();
ArrayFactory.marshalledTypes.Clear();
ArrayFactory.arrayTypes.Clear();
ArrayFactory.createArrayDelegates.Clear();
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
bool unloading = true;
scriptingAssemblyLoadContext.Unloading += (alc) => { unloading = false; };
scriptingAssemblyLoadContext.Unload();
scriptingAssemblyLoadContext.Resolving -= OnScriptingAssemblyLoadContextResolving;
while (unloading)
System.Threading.Thread.Sleep(1);
InitScriptingAssemblyLoadContext();
DelegateHelpers.InitMethods();
GC.Collect();
GC.WaitForPendingFinalizers();
#endif
}

View File

@@ -73,19 +73,6 @@ namespace FlaxEngine.Interop
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]
internal static unsafe void Init()
{
@@ -97,8 +84,6 @@ namespace FlaxEngine.Interop
System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
InitScriptingAssemblyLoadContext();
DelegateHelpers.InitMethods();
}
#if FLAX_EDITOR
@@ -1475,11 +1460,11 @@ namespace FlaxEngine.Interop
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);
private static ConcurrentDictionary<Type, Type> arrayTypes = new ConcurrentDictionary<Type, Type>(1, 3);
private static ConcurrentDictionary<Type, CreateArrayDelegate> createArrayDelegates = new ConcurrentDictionary<Type, CreateArrayDelegate>(1, 3);
internal static ConcurrentDictionary<Type, Type> marshalledTypes = new ConcurrentDictionary<Type, Type>(1, 3);
internal static ConcurrentDictionary<Type, Type> arrayTypes = new ConcurrentDictionary<Type, Type>(1, 3);
internal static ConcurrentDictionary<Type, CreateArrayDelegate> createArrayDelegates = new ConcurrentDictionary<Type, CreateArrayDelegate>(1, 3);
internal static Type GetMarshalledType(Type elementType)
{
@@ -1645,17 +1630,6 @@ namespace FlaxEngine.Interop
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>
/// Returns a static ManagedHandle to TypeHolder for given Type, and caches it if needed.
/// </summary>
@@ -1781,6 +1755,14 @@ namespace FlaxEngine.Interop
#endif
}
internal static void Release()
{
MakeNewCustomDelegateFunc = null;
#if FLAX_EDITOR
MakeNewCustomDelegateFuncCollectible = null;
#endif
}
internal static Type MakeNewCustomDelegate(Type[] parameters)
{
#if FLAX_EDITOR

View File

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

View File

@@ -174,8 +174,10 @@ public:
/// <summary>
/// Determines whether depth buffer is binded to the pipeline.
/// [Deprecated in v1.10]
/// </summary>
/// <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;
public:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -182,6 +182,8 @@ bool ScriptingService::Init()
return true;
}
MCore::CreateScriptingAssemblyLoadContext();
// Cache root domain
_rootDomain = MCore::GetRootDomain();
@@ -710,7 +712,8 @@ void Scripting::Reload(bool canTriggerSceneReload)
_hasGameModulesLoaded = false;
// 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
MCore::GC::Collect();

View File

@@ -170,6 +170,10 @@ namespace FlaxEngine
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
Localization.LocalizationChanged += OnLocalizationChanged;
#if FLAX_EDITOR
FlaxEditor.ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
FlaxEditor.ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd;
#endif
OnLocalizationChanged();
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()
{
// Invariant-globalization only (see InitHostfxr with Mono)
@@ -368,6 +385,10 @@ namespace FlaxEngine
MainThreadTaskScheduler.Dispose();
Json.JsonSerializer.Dispose();
#if FLAX_EDITOR
FlaxEditor.ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
FlaxEditor.ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd;
#endif
}
/// <summary>

View File

@@ -89,7 +89,11 @@ void SerializableScriptingObject::Deserialize(DeserializeStream& stream, ISerial
}
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)
, _id(params.ID)
{

View File

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

View File

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

View File

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

View File

@@ -176,7 +176,8 @@ namespace FlaxEngine.GUI
var font = imageBlock.Style.Font.GetFont();
if (font)
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);
imageBlock.Bounds.Width = width;
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
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
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);
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>
/// Updates the control cached bounds (based on anchors and offsets).
/// </summary>

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,5 +31,18 @@ namespace FlaxEngine.GUI
/// The custom tag.
/// </summary>
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;
/// <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>
[EditorOrder(30)]
public Color ShadowColor;

View File

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

View File

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

View File

@@ -314,7 +314,8 @@ namespace FlaxEngine
{
_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;
#define UICONTROL_INVOKE(event) \
if (HasManagedInstance()) \
auto* managed = GetManagedInstance(); \
if (managed) \
{ \
MObject* exception = nullptr; \
UIControl_##event->Invoke(GetManagedInstance(), nullptr, &exception); \
UIControl_##event->Invoke(managed, nullptr, &exception); \
if (exception) \
{ \
MException ex(exception); \

View File

@@ -35,38 +35,58 @@ namespace FlaxEngine
// Set value
_control = value;
if (_control == null)
return;
// Link the new one (events and parent)
if (_control != null)
// Setup control
var isDuringPlay = IsDuringPlay;
_blockEvents = true;
var container = _control as ContainerControl;
if (container != null)
{
// Setup control
_blockEvents = true;
var containerControl = _control as ContainerControl;
if (containerControl != null)
containerControl.UnlockChildrenRecursive();
_control.Visible = IsActive;
_control.Parent = GetParent();
_control.IndexInParent = OrderInParent;
_control.Location = new Float2(LocalPosition);
_control.LocationChanged += OnControlLocationChanged;
// Link children UI controls
if (containerControl != null)
if (isDuringPlay)
container.UnlockChildrenRecursive(); // Enable layout changes to any dynamically added UI
else
container.LockChildrenRecursive(); // Block layout changes during deserialization
}
_control.Visible = IsActive;
{
var parent = GetParent();
if (parent != null && !parent.IsLayoutLocked && !isDuringPlay)
{
var children = ChildrenCount;
var parent = Parent;
for (int i = 0; i < children; i++)
// Reparent but prevent layout if we're during pre-game setup (eg. deserialization) to avoid UI breaking during auto-layout resizing
parent.IsLayoutLocked = true;
_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;
if (child != null && child.HasControl && child != parent)
{
child.Control.Parent = containerControl;
}
child.Control.Parent = container;
}
}
}
// Refresh
_blockEvents = false;
// Refresh layout
_blockEvents = false;
if (isDuringPlay)
{
if (prevControl == null && _control.Parent != null)
_control.Parent.PerformLayout();
else
@@ -326,11 +346,13 @@ namespace FlaxEngine
{
if ((_control == null || _control.GetType() != controlType) && controlType != null)
{
// Create a new control
Control = (Control)Activator.CreateInstance(controlType);
}
if (_control != null)
{
// Populate control object with properties
Json.JsonSerializer.Deserialize(_control, json);
// Synchronize actor with control location
@@ -374,16 +396,32 @@ namespace FlaxEngine
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;
_control.Parent = GetParent();
_control.IndexInParent = OrderInParent;
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;
if (container != null)
{
//container.UnlockChildrenRecursive();
container.IsLayoutLocked = false; // Forces whole children tree lock/unlock sequence in PerformLayout
}
control.PerformLayout();
}
}

View File

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

View File

@@ -260,7 +260,11 @@ namespace Flax.Build
return true;
var rules = Builder.GenerateRulesAssembly();
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>