diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax index 18f653f86..0e94c5c06 100644 --- a/Content/Shaders/GlobalSignDistanceField.flax +++ b/Content/Shaders/GlobalSignDistanceField.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c8aa181a814d69b15ffec6493a71a6f42ae816ce04f7803cff2d5073b4b3c4f -size 11790 +oid sha256:e075583620e62407503c73f52487c204ddcad421e80fa621aebd55af1cfb08d5 +size 11798 diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs index e1a3acdf6..259be104b 100644 --- a/Source/Editor/Content/GUI/ContentView.cs +++ b/Source/Editor/Content/GUI/ContentView.cs @@ -220,8 +220,9 @@ namespace FlaxEditor.Content.GUI // Remove references and unlink items for (int i = 0; i < _items.Count; i++) { - _items[i].Parent = null; - _items[i].RemoveReference(this); + var item = _items[i]; + item.Parent = null; + item.RemoveReference(this); } _items.Clear(); @@ -263,11 +264,12 @@ namespace FlaxEditor.Content.GUI // Add references and link items for (int i = 0; i < items.Count; i++) { - if (items[i].Visible) + var item = items[i]; + if (item.Visible && !_items.Contains(item)) { - items[i].Parent = this; - items[i].AddReference(this); - _items.Add(items[i]); + item.Parent = this; + item.AddReference(this); + _items.Add(item); } } if (selection != null) @@ -279,6 +281,8 @@ namespace FlaxEditor.Content.GUI // Sort items depending on sortMethod parameter _children.Sort(((control, control1) => { + if (control == null || control1 == null) + return 0; if (sortType == SortType.AlphabeticReverse) { if (control.CompareTo(control1) > 0) diff --git a/Source/Editor/Content/Items/ContentItem.cs b/Source/Editor/Content/Items/ContentItem.cs index 6bbdc0a51..604caa704 100644 --- a/Source/Editor/Content/Items/ContentItem.cs +++ b/Source/Editor/Content/Items/ContentItem.cs @@ -323,8 +323,6 @@ namespace FlaxEditor.Content /// The new path. internal virtual void UpdatePath(string value) { - Assert.AreNotEqual(Path, value); - // Set path Path = StringUtils.NormalizePath(value); FileName = System.IO.Path.GetFileName(value); diff --git a/Source/Editor/Content/Proxy/CubeTextureProxy.cs b/Source/Editor/Content/Proxy/CubeTextureProxy.cs index 0477ad18e..2dccc9e47 100644 --- a/Source/Editor/Content/Proxy/CubeTextureProxy.cs +++ b/Source/Editor/Content/Proxy/CubeTextureProxy.cs @@ -54,7 +54,7 @@ namespace FlaxEditor.Content /// public override bool CanDrawThumbnail(ThumbnailRequest request) { - return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Texture)request.Asset); + return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((CubeTexture)request.Asset); } /// diff --git a/Source/Editor/Content/Proxy/SceneProxy.cs b/Source/Editor/Content/Proxy/SceneProxy.cs index 78d88b440..004c2aed7 100644 --- a/Source/Editor/Content/Proxy/SceneProxy.cs +++ b/Source/Editor/Content/Proxy/SceneProxy.cs @@ -30,6 +30,12 @@ namespace FlaxEditor.Content return item is SceneItem; } + /// + public override bool AcceptsAsset(string typeName, string path) + { + return (typeName == Scene.AssetTypename || typeName == Scene.EditorPickerTypename) && path.EndsWith(FileExtension, StringComparison.OrdinalIgnoreCase); + } + /// public override bool CanCreate(ContentFolder targetLocation) { diff --git a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs index 160c783ed..1282e4daa 100644 --- a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs +++ b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs @@ -406,18 +406,16 @@ namespace FlaxEditor.Content.Thumbnails for (int i = 0; i < maxChecks; i++) { var request = _requests[i]; - try { if (request.IsReady) - { return request; - } } catch (Exception ex) { - Editor.LogWarning(ex); Editor.LogWarning($"Failed to prepare thumbnail rendering for {request.Item.ShortName}."); + Editor.LogWarning(ex); + _requests.RemoveAt(i--); } } @@ -515,7 +513,6 @@ namespace FlaxEditor.Content.Thumbnails for (int i = 0; i < checks; i++) { var request = _requests[i]; - try { if (request.IsReady) @@ -529,8 +526,9 @@ namespace FlaxEditor.Content.Thumbnails } catch (Exception ex) { - Editor.LogWarning(ex); Editor.LogWarning($"Failed to prepare thumbnail rendering for {request.Item.ShortName}."); + Editor.LogWarning(ex); + _requests.RemoveAt(i--); } } diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index c9bbc016e..8306475d1 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -131,7 +131,8 @@ bool DeployDataStep::Perform(CookingData& data) if (FileSystem::DirectoryExists(dstDotnet)) { String cachedData; - File::ReadAllText(dotnetCacheFilePath, cachedData); + if (FileSystem::FileExists(dotnetCacheFilePath)) + File::ReadAllText(dotnetCacheFilePath, cachedData); if (cachedData != dotnetCachedValue) { FileSystem::DeleteDirectory(dstDotnet); @@ -360,7 +361,7 @@ bool DeployDataStep::Perform(CookingData& data) data.AddRootEngineAsset(PRE_INTEGRATED_GF_ASSET_NAME); data.AddRootEngineAsset(SMAA_AREA_TEX); data.AddRootEngineAsset(SMAA_SEARCH_TEX); - if (data.Configuration != BuildConfiguration::Release) + if (!buildSettings.SkipDefaultFonts) data.AddRootEngineAsset(TEXT("Editor/Fonts/Roboto-Regular")); // Register custom assets (eg. plugins) diff --git a/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs b/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs index c6043e8b6..4053c6e16 100644 --- a/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs @@ -36,7 +36,7 @@ namespace FlaxEditor.CustomEditors.Dedicated return; var gizmos = gizmoOwner.Gizmos; _gizmoMode = new ClothPaintingGizmoMode(); - + var projectCache = Editor.Instance.ProjectCache; if (projectCache.TryGetCustomData("ClothGizmoPaintValue", out var cachedPaintValue)) _gizmoMode.PaintValue = JsonSerializer.Deserialize(cachedPaintValue); @@ -48,7 +48,7 @@ namespace FlaxEditor.CustomEditors.Dedicated _gizmoMode.BrushSize = JsonSerializer.Deserialize(cachedBrushSize); if (projectCache.TryGetCustomData("ClothGizmoBrushStrength", out var cachedBrushStrength)) _gizmoMode.BrushStrength = JsonSerializer.Deserialize(cachedBrushStrength); - + gizmos.AddMode(_gizmoMode); _prevMode = gizmos.ActiveMode; gizmos.ActiveMode = _gizmoMode; diff --git a/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs index a6c4e6623..8fb742b5e 100644 --- a/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs @@ -1,6 +1,13 @@ -using FlaxEditor.CustomEditors.Editors; +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using FlaxEditor.Actions; +using FlaxEditor.CustomEditors.Editors; +using FlaxEditor.GUI; +using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; +using System.Collections.Generic; namespace FlaxEditor.CustomEditors.Dedicated; @@ -10,6 +17,10 @@ namespace FlaxEditor.CustomEditors.Dedicated; [CustomEditor(typeof(MissingScript)), DefaultEditor] public class MissingScriptEditor : GenericEditor { + private DropPanel _dropPanel; + private Button _replaceScriptButton; + private CheckBox _shouldReplaceAllCheckbox; + /// public override void Initialize(LayoutElementsContainer layout) { @@ -18,9 +29,137 @@ public class MissingScriptEditor : GenericEditor base.Initialize(layout); return; } + _dropPanel = dropPanel; + _dropPanel.HeaderTextColor = Color.OrangeRed; - dropPanel.HeaderTextColor = Color.OrangeRed; + var replaceScriptPanel = new Panel + { + Parent = _dropPanel, + Height = 64, + }; + + _replaceScriptButton = new Button + { + Text = "Replace Script", + TooltipText = "Replaces the missing script with a given script type", + AnchorPreset = AnchorPresets.TopCenter, + Width = 240, + Height = 24, + X = -120, + Y = 0, + Parent = replaceScriptPanel, + }; + _replaceScriptButton.Clicked += OnReplaceScriptButtonClicked; + + var replaceAllLabel = new Label + { + Text = "Replace all matching missing scripts", + TooltipText = "Whether or not to apply this script change to all scripts missing the same type.", + AnchorPreset = AnchorPresets.BottomCenter, + Y = -34, + Parent = replaceScriptPanel, + }; + replaceAllLabel.X -= FlaxEngine.GUI.Style.Current.FontSmall.MeasureText(replaceAllLabel.Text).X; + + _shouldReplaceAllCheckbox = new CheckBox + { + TooltipText = replaceAllLabel.TooltipText, + AnchorPreset = AnchorPresets.BottomCenter, + Y = -34, + Parent = replaceScriptPanel, + }; + + float centerDifference = (_shouldReplaceAllCheckbox.Right - replaceAllLabel.Left) / 2; + replaceAllLabel.X += centerDifference; + _shouldReplaceAllCheckbox.X += centerDifference; base.Initialize(layout); } + + private void FindActorsWithMatchingMissingScript(List missingScripts) + { + foreach (Actor actor in Level.GetActors(typeof(Actor))) + { + for (int scriptIndex = 0; scriptIndex < actor.ScriptsCount; scriptIndex++) + { + Script actorScript = actor.Scripts[scriptIndex]; + if (actorScript is not MissingScript missingActorScript) + continue; + + MissingScript currentMissing = Values[0] as MissingScript; + if (missingActorScript.MissingTypeName != currentMissing.MissingTypeName) + continue; + + missingScripts.Add(missingActorScript); + } + } + } + + private void RunReplacementMultiCast(List actions) + { + if (actions.Count == 0) + { + Editor.LogWarning("Failed to replace scripts!"); + return; + } + + var multiAction = new MultiUndoAction(actions); + multiAction.Do(); + var presenter = ParentEditor.Presenter; + if (presenter != null) + { + presenter.Undo.AddAction(multiAction); + presenter.Control.Focus(); + } + } + + private void ReplaceScript(ScriptType script, bool replaceAllInScene) + { + var actions = new List(4); + + var missingScripts = new List(); + if (!replaceAllInScene) + missingScripts.Add((MissingScript)Values[0]); + else + FindActorsWithMatchingMissingScript(missingScripts); + + foreach (var missingScript in missingScripts) + actions.Add(AddRemoveScript.Add(missingScript.Actor, script)); + RunReplacementMultiCast(actions); + + for (int actionIdx = 0; actionIdx < actions.Count; actionIdx++) + { + AddRemoveScript addRemoveScriptAction = (AddRemoveScript)actions[actionIdx]; + int orderInParent = addRemoveScriptAction.GetOrderInParent(); + + Script newScript = missingScripts[actionIdx].Actor.Scripts[orderInParent]; + missingScripts[actionIdx].ReferenceScript = newScript; + } + actions.Clear(); + + foreach (var missingScript in missingScripts) + actions.Add(AddRemoveScript.Remove(missingScript)); + RunReplacementMultiCast(actions); + } + + private void OnReplaceScriptButtonClicked() + { + var scripts = Editor.Instance.CodeEditing.Scripts.Get(); + if (scripts.Count == 0) + { + // No scripts + var cm1 = new ContextMenu(); + cm1.AddButton("No scripts in project"); + cm1.Show(_dropPanel, _replaceScriptButton.BottomLeft); + return; + } + + // Show context menu with list of scripts to add + var cm = new ItemsListContextMenu(180); + for (int i = 0; i < scripts.Count; i++) + cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i])); + cm.ItemClicked += item => ReplaceScript((ScriptType)item.Tag, _shouldReplaceAllCheckbox.Checked); + cm.SortItems(); + cm.Show(_dropPanel, _replaceScriptButton.BottomLeft - new Float2((cm.Width - _replaceScriptButton.Width) / 2, 0)); + } } diff --git a/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs b/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs index e46040a42..07af5e991 100644 --- a/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs +++ b/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs @@ -22,7 +22,7 @@ namespace FlaxEditor.CustomEditors.Elements /// /// [Deprecated on 26.05.2022, expires on 26.05.2024] /// - [System.Obsolete("Deprecated in 1.4")] + [System.Obsolete("Deprecated in 1.4, use ValueBox instead")] public DoubleValueBox DoubleValue => ValueBox; /// diff --git a/Source/Editor/CustomEditors/Elements/FloatValueElement.cs b/Source/Editor/CustomEditors/Elements/FloatValueElement.cs index 552e9d125..789d8966e 100644 --- a/Source/Editor/CustomEditors/Elements/FloatValueElement.cs +++ b/Source/Editor/CustomEditors/Elements/FloatValueElement.cs @@ -22,7 +22,7 @@ namespace FlaxEditor.CustomEditors.Elements /// /// [Deprecated on 26.05.2022, expires on 26.05.2024] /// - [System.Obsolete("Deprecated in 1.4, ValueBox instead")] + [System.Obsolete("Deprecated in 1.4, use ValueBox instead")] public FloatValueBox FloatValue => ValueBox; /// diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 1b46222ea..b384b6515 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -364,7 +364,7 @@ namespace FlaxEditor { foreach (var preview in activePreviews) { - if (preview == loadingPreview || + if (preview == loadingPreview || (preview.Instance != null && (preview.Instance == control || preview.Instance.HasActorInHierarchy(control)))) { // Link it to the prefab preview to see it in the editor diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs index 3e5d22eb0..8d6b0f9e2 100644 --- a/Source/Editor/GUI/AssetPicker.cs +++ b/Source/Editor/GUI/AssetPicker.cs @@ -482,8 +482,8 @@ namespace FlaxEditor.GUI Focus(); }); if (_selected != null) - { - var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path); + { + var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path); popup.ScrollToAndHighlightItemByName(selectedAssetName); } } diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs index 7af36fae0..49a60a04e 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs @@ -72,7 +72,7 @@ namespace FlaxEditor.GUI.ContextMenu // Hide parent CM popups and set itself as child parentContextMenu.ShowChild(ContextMenu, PointToParent(ParentContextMenu, new Float2(Width, 0))); } - + /// public override bool OnMouseUp(Float2 location, MouseButton button) { diff --git a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs index 161b3f4ae..27878a763 100644 --- a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs +++ b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs @@ -319,7 +319,9 @@ namespace FlaxEditor.GUI.Dialogs protected override void OnShow() { // Auto cancel on lost focus +#if !PLATFORM_LINUX ((WindowRootControl)Root).Window.LostFocus += OnCancel; +#endif base.OnShow(); } diff --git a/Source/Editor/GUI/Dialogs/Dialog.cs b/Source/Editor/GUI/Dialogs/Dialog.cs index 07fc3ff0d..5054aee98 100644 --- a/Source/Editor/GUI/Dialogs/Dialog.cs +++ b/Source/Editor/GUI/Dialogs/Dialog.cs @@ -293,7 +293,7 @@ namespace FlaxEditor.GUI.Dialogs if (Root != null) { bool shiftDown = Root.GetKey(KeyboardKeys.Shift); - Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next); + Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next); } return true; } diff --git a/Source/Editor/GUI/Input/SearchBox.cs b/Source/Editor/GUI/Input/SearchBox.cs index 994ef14b1..73cfe55bf 100644 --- a/Source/Editor/GUI/Input/SearchBox.cs +++ b/Source/Editor/GUI/Input/SearchBox.cs @@ -20,7 +20,7 @@ namespace FlaxEditor.GUI.Input : this(false, 0, 0) { } - + /// /// Init search box /// @@ -28,7 +28,7 @@ namespace FlaxEditor.GUI.Input : base(isMultiline, x, y, width) { WatermarkText = "Search..."; - + ClearSearchButton = new Button { Parent = this, diff --git a/Source/Editor/GUI/Input/ValueBox.cs b/Source/Editor/GUI/Input/ValueBox.cs index 492887611..321782a8e 100644 --- a/Source/Editor/GUI/Input/ValueBox.cs +++ b/Source/Editor/GUI/Input/ValueBox.cs @@ -182,6 +182,7 @@ namespace FlaxEditor.GUI.Input } SlidingEnd?.Invoke(); Defocus(); + Parent?.Focus(); } /// diff --git a/Source/Editor/GUI/Row.cs b/Source/Editor/GUI/Row.cs index b07d693e5..cb9cb09b2 100644 --- a/Source/Editor/GUI/Row.cs +++ b/Source/Editor/GUI/Row.cs @@ -241,7 +241,7 @@ namespace FlaxEditor.GUI { DoubleClick?.Invoke(); RowDoubleClick?.Invoke(this); - + return base.OnMouseDoubleClick(location, button); } diff --git a/Source/Editor/GUI/Tabs/Tabs.cs b/Source/Editor/GUI/Tabs/Tabs.cs index f9bfd3f6c..d830b9e0f 100644 --- a/Source/Editor/GUI/Tabs/Tabs.cs +++ b/Source/Editor/GUI/Tabs/Tabs.cs @@ -191,6 +191,8 @@ namespace FlaxEditor.GUI.Tabs get => _autoTabsSizeAuto; set { + if (_autoTabsSizeAuto == value) + return; _autoTabsSizeAuto = value; PerformLayout(); } @@ -204,11 +206,11 @@ namespace FlaxEditor.GUI.Tabs get => _orientation; set { + if (_orientation == value) + return; _orientation = value; - if (UseScroll) TabsPanel.ScrollBars = _orientation == Orientation.Horizontal ? ScrollBars.Horizontal : ScrollBars.Vertical; - PerformLayout(); } } @@ -402,6 +404,14 @@ namespace FlaxEditor.GUI.Tabs tabHeader.Size = tabsSize; } } + else if (UseScroll) + { + // If scroll bar is visible it covers part of the tab header so include this in tab size to improve usability + if (_orientation == Orientation.Horizontal && TabsPanel.HScrollBar.Visible) + tabsSize.Y += TabsPanel.HScrollBar.Height; + else if (_orientation == Orientation.Vertical && TabsPanel.VScrollBar.Visible) + tabsSize.X += TabsPanel.VScrollBar.Width; + } // Fit the tabs panel TabsPanel.Size = _orientation == Orientation.Horizontal diff --git a/Source/Editor/Gizmo/IGizmoOwner.cs b/Source/Editor/Gizmo/IGizmoOwner.cs index a28810179..ff653196e 100644 --- a/Source/Editor/Gizmo/IGizmoOwner.cs +++ b/Source/Editor/Gizmo/IGizmoOwner.cs @@ -11,6 +11,11 @@ namespace FlaxEditor.Gizmo [HideInEditor] public interface IGizmoOwner { + /// + /// Gets the gizmos collection. + /// + FlaxEditor.Viewport.EditorViewport Viewport { get; } + /// /// Gets the gizmos collection. /// diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index dd5cc6c76..6123d348a 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -162,10 +162,23 @@ namespace FlaxEditor.Gizmo // Scale gizmo to fit on-screen Vector3 position = Position; - Vector3 vLength = Owner.ViewPosition - position; - float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize; - _screenScale = (float)(vLength.Length / GizmoScaleFactor * gizmoSize); - + if (Owner.Viewport.UseOrthographicProjection) + { + //[hack] this is far form ideal the View Position is in wrong location, any think using the View Position will have problem + //the camera system needs rewrite the to be a camera on springarm, similar how the ArcBallCamera is handled + //the ortho projection cannot exist with fps camera because there is no + // - focus point to calculate correct View Position with Orthographic Scale as a reference and Orthographic Scale from View Position + // with make the camera jump + // - and deaph so w and s movment in orto mode moves the cliping plane now + float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize; + _screenScale = gizmoSize * (50 * Owner.Viewport.OrthographicScale); + } + else + { + Vector3 vLength = Owner.ViewPosition - position; + float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize; + _screenScale = (float)(vLength.Length / GizmoScaleFactor * gizmoSize); + } // Setup world Quaternion orientation = GetSelectedObject(0).Orientation; _gizmoWorld = new Transform(position, orientation, new Float3(_screenScale)); diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs index eec55c788..80f419a6f 100644 --- a/Source/Editor/Modules/ContentDatabaseModule.cs +++ b/Source/Editor/Modules/ContentDatabaseModule.cs @@ -649,8 +649,6 @@ namespace FlaxEditor.Modules // Special case for folders if (item is ContentFolder folder) { - // TODO: maybe don't remove folders recursive but at once? - // Delete all children if (folder.Children.Count > 0) { @@ -664,6 +662,9 @@ namespace FlaxEditor.Modules // Remove directory if (deletedByUser && Directory.Exists(path)) { + // Flush files removal before removing folder (loaded assets remove file during object destruction in Asset::OnDeleteObject) + FlaxEngine.Scripting.FlushRemovedObjects(); + try { Directory.Delete(path, true); @@ -810,10 +811,9 @@ namespace FlaxEditor.Modules { if (node == null) return; - - // Temporary data var folder = node.Folder; var path = folder.Path; + var canHaveAssets = node.CanHaveAssets; if (_isDuringFastSetup) { @@ -832,20 +832,38 @@ namespace FlaxEditor.Modules var child = folder.Children[i]; if (!child.Exists) { - // Send info + // Item doesn't exist anymore Editor.Log(string.Format($"Content item \'{child.Path}\' has been removed")); - - // Destroy it Delete(child, false); - i--; } + else if (canHaveAssets && child is AssetItem childAsset) + { + // Check if asset type doesn't match the item proxy (eg. item reimported as Material Instance instead of Material) + if (FlaxEngine.Content.GetAssetInfo(child.Path, out var assetInfo)) + { + bool changed = assetInfo.ID != childAsset.ID; + if (!changed && assetInfo.TypeName != childAsset.TypeName) + { + // Use proxy check (eg. scene asset might accept different typename than AssetInfo reports) + var proxy = GetAssetProxy(childAsset.TypeName, child.Path); + if (proxy == null) + proxy = GetAssetProxy(assetInfo.TypeName, child.Path); + changed = !proxy.AcceptsAsset(assetInfo.TypeName, child.Path); + } + if (changed) + { + OnAssetTypeInfoChanged(childAsset, ref assetInfo); + i--; + } + } + } } } // Find files var files = Directory.GetFiles(path, "*.*", SearchOption.TopDirectoryOnly); - if (node.CanHaveAssets) + if (canHaveAssets) { LoadAssets(node, files); } @@ -1134,17 +1152,19 @@ namespace FlaxEditor.Modules RebuildInternal(); - Editor.ContentImporting.ImportFileEnd += ContentImporting_ImportFileDone; + Editor.ContentImporting.ImportFileEnd += (obj, failed) => + { + var path = obj.ResultUrl; + if (!failed) + FlaxEngine.Scripting.InvokeOnUpdate(() => OnImportFileDone(path)); + }; _enableEvents = true; } - private void ContentImporting_ImportFileDone(IFileEntryAction obj, bool failed) + private void OnImportFileDone(string path) { - if (failed) - return; - // Check if already has that element - var item = Find(obj.ResultUrl); + var item = Find(path); if (item is BinaryAssetItem binaryAssetItem) { // Get asset info from the registry (content layer will update cache it just after import) @@ -1154,19 +1174,8 @@ namespace FlaxEditor.Modules // For eg. change texture to sprite atlas on reimport if (binaryAssetItem.TypeName != assetInfo.TypeName) { - // Asset type has been changed! - Editor.LogWarning(string.Format("Asset \'{0}\' changed type from {1} to {2}", item.Path, binaryAssetItem.TypeName, assetInfo.TypeName)); - Editor.Windows.CloseAllEditors(item); - - // Remove this item from the database and some related data var toRefresh = binaryAssetItem.ParentFolder; - binaryAssetItem.Dispose(); - toRefresh.Children.Remove(binaryAssetItem); - if (!binaryAssetItem.HasDefaultThumbnail) - { - // Delete old thumbnail and remove it from the cache - Editor.Instance.Thumbnails.DeletePreview(binaryAssetItem); - } + OnAssetTypeInfoChanged(binaryAssetItem, ref assetInfo); // Refresh the parent folder to find the new asset (it should have different type or some other format) RefreshFolder(toRefresh, false); @@ -1183,6 +1192,23 @@ namespace FlaxEditor.Modules } } + private void OnAssetTypeInfoChanged(AssetItem assetItem, ref AssetInfo assetInfo) + { + // Asset type has been changed! + Editor.LogWarning(string.Format("Asset \'{0}\' changed type from {1} to {2}", assetItem.Path, assetItem.TypeName, assetInfo.TypeName)); + Editor.Windows.CloseAllEditors(assetItem); + + // Remove this item from the database and some related data + assetItem.Dispose(); + assetItem.ParentFolder.Children.Remove(assetItem); + + // Delete old thumbnail and remove it from the cache + if (!assetItem.HasDefaultThumbnail) + { + Editor.Instance.Thumbnails.DeletePreview(assetItem); + } + } + internal void OnDirectoryEvent(MainContentTreeNode node, FileSystemEventArgs e) { // Ensure to be ready for external events diff --git a/Source/Editor/Modules/ContentEditingModule.cs b/Source/Editor/Modules/ContentEditingModule.cs index 1a8920916..f262864dd 100644 --- a/Source/Editor/Modules/ContentEditingModule.cs +++ b/Source/Editor/Modules/ContentEditingModule.cs @@ -104,7 +104,7 @@ namespace FlaxEditor.Modules hint = "Too long name."; return false; } - + if (item.IsFolder && shortName.EndsWith(".")) { hint = "Name cannot end with '.'"; diff --git a/Source/Editor/Modules/ContentImportingModule.cs b/Source/Editor/Modules/ContentImportingModule.cs index c94ba650e..85dd50d35 100644 --- a/Source/Editor/Modules/ContentImportingModule.cs +++ b/Source/Editor/Modules/ContentImportingModule.cs @@ -55,7 +55,7 @@ namespace FlaxEditor.Modules public event Action ImportingQueueBegin; /// - /// Occurs when file is being imported. + /// Occurs when file is being imported. Can be called on non-main thread. /// public event Action ImportFileBegin; @@ -67,12 +67,12 @@ namespace FlaxEditor.Modules public delegate void ImportFileEndDelegate(IFileEntryAction entry, bool failed); /// - /// Occurs when file importing end. + /// Occurs when file importing end. Can be called on non-main thread. /// public event ImportFileEndDelegate ImportFileEnd; /// - /// Occurs when assets importing ends. + /// Occurs when assets importing ends. Can be called on non-main thread. /// public event Action ImportingQueueEnd; diff --git a/Source/Editor/Modules/PrefabsModule.cs b/Source/Editor/Modules/PrefabsModule.cs index bb76e125b..adb5f7685 100644 --- a/Source/Editor/Modules/PrefabsModule.cs +++ b/Source/Editor/Modules/PrefabsModule.cs @@ -133,7 +133,7 @@ namespace FlaxEditor.Modules return; var actorsList = new List(); Utilities.Utils.GetActorsTree(actorsList, actor); - + var actions = new IUndoAction[actorsList.Count]; for (int i = 0; i < actorsList.Count; i++) actions[i] = BreakPrefabLinkAction.Linked(actorsList[i]); diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 2b8bf718e..970ca8e99 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -453,7 +453,7 @@ namespace FlaxEditor.Modules { Editor.Windows.SceneWin.Focus(); } - + // fix scene window layout Editor.Windows.SceneWin.PerformLayout(); Editor.Windows.SceneWin.PerformLayout(); @@ -520,7 +520,7 @@ namespace FlaxEditor.Modules Undo.AddAction(new MultiUndoAction(pasteAction, selectAction)); OnSelectionChanged(); } - + // Scroll to new selected node while pasting Editor.Windows.SceneWin.ScrollToSelectedNode(); } @@ -620,7 +620,7 @@ namespace FlaxEditor.Modules Undo.AddAction(new MultiUndoAction(undoActions)); OnSelectionChanged(); } - + // Scroll to new selected node while duplicating Editor.Windows.SceneWin.ScrollToSelectedNode(); } diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs index 257262585..7789eb7c4 100644 --- a/Source/Editor/Modules/SceneModule.cs +++ b/Source/Editor/Modules/SceneModule.cs @@ -332,7 +332,7 @@ namespace FlaxEditor.Modules continue; scenes.Add(s); } - + // In play-mode Editor mocks the level streaming script if (Editor.IsPlayMode) { diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs index 4089f6d79..96cf44e09 100644 --- a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs +++ b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs @@ -29,10 +29,10 @@ namespace FlaxEditor.Modules.SourceCodeEditing private static bool CheckFunc(ScriptType scriptType) { - if (scriptType.IsStatic || - scriptType.IsGenericType || - !scriptType.IsPublic || - scriptType.HasAttribute(typeof(HideInEditorAttribute), true) || + if (scriptType.IsStatic || + scriptType.IsGenericType || + !scriptType.IsPublic || + scriptType.HasAttribute(typeof(HideInEditorAttribute), true) || scriptType.HasAttribute(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false)) return false; var managedType = TypeUtils.GetType(scriptType); @@ -410,9 +410,11 @@ namespace FlaxEditor.Modules.SourceCodeEditing base.OnUpdate(); // Automatic project files generation after workspace modifications - if (_autoGenerateScriptsProjectFiles && ScriptsBuilder.IsSourceWorkspaceDirty) + if (_autoGenerateScriptsProjectFiles && ScriptsBuilder.IsSourceWorkspaceDirty && !ScriptsBuilder.IsCompiling) { - Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync(); + // Try to delay generation when a lot of files are added at once + if (ScriptsBuilder.IsSourceDirtyFor(TimeSpan.FromMilliseconds(150))) + Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync(); } } diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index c882efb87..1369ecd1e 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -2,7 +2,6 @@ using System; using System.IO; -using System.Linq; using System.Collections.Generic; using FlaxEditor.Gizmo; using FlaxEditor.GUI; @@ -11,7 +10,6 @@ using FlaxEditor.GUI.Dialogs; using FlaxEditor.GUI.Input; using FlaxEditor.Progress.Handlers; using FlaxEditor.SceneGraph; -using FlaxEditor.SceneGraph.Actors; using FlaxEditor.Utilities; using FlaxEditor.Viewport.Cameras; using FlaxEditor.Windows; @@ -208,6 +206,7 @@ namespace FlaxEditor.Modules _toolStripScale.Checked = gizmoMode == TransformGizmoBase.Mode.Scale; // _toolStripBuildScenes.Enabled = (canEditScene && !isPlayMode) || Editor.StateMachine.BuildingScenesState.IsActive; + _toolStripBuildScenes.Visible = Editor.Options.Options.General.BuildActions?.Length != 0; _toolStripCook.Enabled = Editor.Windows.GameCookerWin.CanBuild(Platform.PlatformType) && !GameCooker.IsRunning; // var play = _toolStripPlay; @@ -299,7 +298,7 @@ namespace FlaxEditor.Modules else text = "Ready"; - if(ProgressVisible) + if (ProgressVisible) { color = Style.Current.Statusbar.Loading; } @@ -402,7 +401,7 @@ namespace FlaxEditor.Modules { UpdateStatusBar(); } - else if(ProgressVisible) + else if (ProgressVisible) { UpdateStatusBar(); } @@ -557,7 +556,7 @@ namespace FlaxEditor.Modules cm.AddButton("Game Settings", () => { var item = Editor.ContentDatabase.Find(GameSettings.GameSettingsAssetPath); - if(item != null) + if (item != null) Editor.ContentEditing.Open(item); }); @@ -653,7 +652,7 @@ namespace FlaxEditor.Modules cm.AddButton("Information about Flax", () => new AboutDialog().Show()); } - private void OnOptionsChanged(FlaxEditor.Options.EditorOptions options) + private void OnOptionsChanged(EditorOptions options) { var inputOptions = options.Input; @@ -688,6 +687,8 @@ namespace FlaxEditor.Modules _menuToolsTakeScreenshot.ShortKeys = inputOptions.TakeScreenshot.ToString(); MainMenuShortcutKeysUpdated?.Invoke(); + + UpdateToolstrip(); } private void InitToolstrip(RootControl mainWindow) @@ -709,11 +710,11 @@ namespace FlaxEditor.Modules _toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip($"Change Gizmo tool mode to Scale ({inputOptions.ScaleMode})"); ToolStrip.AddSeparator(); - // Cook scenes + // Build scenes _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip($"Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options ({inputOptions.BuildScenesData})"); // Cook and run - _toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip($"Cook & Run - build game for the current platform and run it locally ({inputOptions.Play})"); + _toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip($"Cook & Run - build game for the current platform and run it locally ({inputOptions.CookAndRun})"); _toolStripCook.ContextMenu = new ContextMenu(); _toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked); _toolStripCook.ContextMenu.AddSeparator(); diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index d0c8b9747..1a4f5ce9e 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -276,9 +276,6 @@ namespace FlaxEditor.Modules // Get metadata int version = int.Parse(root.Attributes["Version"].Value, CultureInfo.InvariantCulture); - var virtualDesktopBounds = Platform.VirtualDesktopBounds; - var virtualDesktopSafeLeftCorner = virtualDesktopBounds.Location; - var virtualDesktopSafeRightCorner = virtualDesktopBounds.BottomRight; switch (version) { @@ -288,31 +285,9 @@ namespace FlaxEditor.Modules if (MainWindow) { var mainWindowNode = root["MainWindow"]; - Rectangle bounds = LoadBounds(mainWindowNode["Bounds"]); - bool isMaximized = bool.Parse(mainWindowNode.GetAttribute("IsMaximized")); - - // Clamp position to match current desktop dimensions (if window was on desktop that is now inactive) - if (bounds.X < virtualDesktopSafeLeftCorner.X || bounds.Y < virtualDesktopSafeLeftCorner.Y || bounds.X > virtualDesktopSafeRightCorner.X || bounds.Y > virtualDesktopSafeRightCorner.Y) - bounds.Location = virtualDesktopSafeLeftCorner; - - if (isMaximized) - { - if (MainWindow.IsMaximized) - MainWindow.Restore(); - MainWindow.ClientPosition = bounds.Location; - MainWindow.Maximize(); - } - else - { - if (Mathf.Min(bounds.Size.X, bounds.Size.Y) >= 1) - { - MainWindow.ClientBounds = bounds; - } - else - { - MainWindow.ClientPosition = bounds.Location; - } - } + bool isMaximized = true, isMinimized = false; + Rectangle bounds = LoadBounds(mainWindowNode, ref isMaximized, ref isMinimized); + LoadWindow(MainWindow, ref bounds, isMaximized, false); } // Load master panel structure @@ -332,11 +307,13 @@ namespace FlaxEditor.Modules continue; // Get window properties - Rectangle bounds = LoadBounds(child["Bounds"]); + bool isMaximized = false, isMinimized = false; + Rectangle bounds = LoadBounds(child, ref isMaximized, ref isMinimized); // Create window and floating dock panel var window = FloatWindowDockPanel.CreateFloatWindow(MainWindow.GUI, bounds.Location, bounds.Size, WindowStartPosition.Manual, string.Empty); var panel = new FloatWindowDockPanel(masterPanel, window.GUI); + LoadWindow(panel.Window.Window, ref bounds, isMaximized, isMinimized); // Load structure LoadPanel(child, panel); @@ -493,23 +470,67 @@ namespace FlaxEditor.Modules private static void SaveBounds(XmlWriter writer, Window win) { - var bounds = win.ClientBounds; - - writer.WriteAttributeString("X", bounds.X.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Y", bounds.Y.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Width", bounds.Width.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Height", bounds.Height.ToString(CultureInfo.InvariantCulture)); + writer.WriteStartElement("Bounds"); + { + var bounds = win.ClientBounds; + writer.WriteAttributeString("X", bounds.X.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Y", bounds.Y.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Width", bounds.Width.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Height", bounds.Height.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("IsMaximized", win.IsMaximized.ToString()); + writer.WriteAttributeString("IsMinimized", win.IsMinimized.ToString()); + } + writer.WriteEndElement(); } - private static Rectangle LoadBounds(XmlElement node) + private static Rectangle LoadBounds(XmlElement node, ref bool isMaximized, ref bool isMinimized) { - float x = float.Parse(node.GetAttribute("X"), CultureInfo.InvariantCulture); - float y = float.Parse(node.GetAttribute("Y"), CultureInfo.InvariantCulture); - float width = float.Parse(node.GetAttribute("Width"), CultureInfo.InvariantCulture); - float height = float.Parse(node.GetAttribute("Height"), CultureInfo.InvariantCulture); + var bounds = node["Bounds"]; + var isMaximizedText = bounds.GetAttribute("IsMaximized"); + if (!string.IsNullOrEmpty(isMaximizedText)) + isMaximized = bool.Parse(isMaximizedText); + var isMinimizedText = bounds.GetAttribute("IsMinimized"); + if (!string.IsNullOrEmpty(isMinimizedText)) + isMinimized = bool.Parse(isMinimizedText); + float x = float.Parse(bounds.GetAttribute("X"), CultureInfo.InvariantCulture); + float y = float.Parse(bounds.GetAttribute("Y"), CultureInfo.InvariantCulture); + float width = float.Parse(bounds.GetAttribute("Width"), CultureInfo.InvariantCulture); + float height = float.Parse(bounds.GetAttribute("Height"), CultureInfo.InvariantCulture); return new Rectangle(x, y, width, height); } + private static void LoadWindow(Window win, ref Rectangle bounds, bool isMaximized, bool isMinimized) + { + var virtualDesktopBounds = Platform.VirtualDesktopBounds; + var virtualDesktopSafeLeftCorner = virtualDesktopBounds.Location; + var virtualDesktopSafeRightCorner = virtualDesktopBounds.BottomRight; + + // Clamp position to match current desktop dimensions (if window was on desktop that is now inactive) + if (bounds.X < virtualDesktopSafeLeftCorner.X || bounds.Y < virtualDesktopSafeLeftCorner.Y || bounds.X > virtualDesktopSafeRightCorner.X || bounds.Y > virtualDesktopSafeRightCorner.Y) + bounds.Location = virtualDesktopSafeLeftCorner; + + if (isMaximized) + { + if (win.IsMaximized) + win.Restore(); + win.ClientPosition = bounds.Location; + win.Maximize(); + } + else + { + if (Mathf.Min(bounds.Size.X, bounds.Size.Y) >= 1) + { + win.ClientBounds = bounds; + } + else + { + win.ClientPosition = bounds.Location; + } + if (isMinimized) + win.Minimize(); + } + } + private class LayoutNameDialog : Dialog { private TextBox _textbox; @@ -609,13 +630,8 @@ namespace FlaxEditor.Modules if (MainWindow) { writer.WriteStartElement("MainWindow"); - writer.WriteAttributeString("IsMaximized", MainWindow.IsMaximized.ToString()); - - writer.WriteStartElement("Bounds"); SaveBounds(writer, MainWindow); writer.WriteEndElement(); - - writer.WriteEndElement(); } // Master panel structure @@ -628,22 +644,13 @@ namespace FlaxEditor.Modules { var panel = masterPanel.FloatingPanels[i]; var window = panel.Window; - if (window == null) - { - Editor.LogWarning("Floating panel has missing window"); continue; - } writer.WriteStartElement("Float"); - SavePanel(writer, panel); - - writer.WriteStartElement("Bounds"); SaveBounds(writer, window.Window); writer.WriteEndElement(); - - writer.WriteEndElement(); } writer.WriteEndElement(); diff --git a/Source/Editor/Options/GeneralOptions.cs b/Source/Editor/Options/GeneralOptions.cs index 33124bab0..9f29f4bdb 100644 --- a/Source/Editor/Options/GeneralOptions.cs +++ b/Source/Editor/Options/GeneralOptions.cs @@ -117,7 +117,7 @@ namespace FlaxEditor.Options /// /// Gets or sets the sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh). /// - [EditorDisplay("General"), EditorOrder(200), Tooltip("The sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh).")] + [EditorDisplay("General"), EditorOrder(200), ExpandGroups, Tooltip("The sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh).")] public BuildAction[] BuildActions { get; set; } = { BuildAction.CSG, diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index c2d744239..07e899c5e 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -244,11 +244,11 @@ namespace FlaxEditor.Options CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC), ProgressNormal = Color.FromBgra(0xFF0ad328), - Statusbar = new Style.StatusbarStyle() + Statusbar = new Style.StatusbarStyle { PlayMode = Color.FromBgra(0xFF2F9135), Failed = Color.FromBgra(0xFF9C2424), - Loading = Color.FromBgra(0xFF2D2D30) + Loading = Color.FromBgra(0xFF2D2D30), }, // Fonts @@ -271,7 +271,7 @@ namespace FlaxEditor.Options Scale = Editor.Icons.Scale32, Scalar = Editor.Icons.Scalar32, - SharedTooltip = new Tooltip() + SharedTooltip = new Tooltip(), }; style.DragWindow = style.BackgroundSelected * 0.7f; diff --git a/Source/Editor/Progress/Handlers/ImportAssetsProgress.cs b/Source/Editor/Progress/Handlers/ImportAssetsProgress.cs index 7bbefbc2d..fa1e943f0 100644 --- a/Source/Editor/Progress/Handlers/ImportAssetsProgress.cs +++ b/Source/Editor/Progress/Handlers/ImportAssetsProgress.cs @@ -19,25 +19,25 @@ namespace FlaxEditor.Progress.Handlers public ImportAssetsProgress() { var importing = Editor.Instance.ContentImporting; - importing.ImportingQueueBegin += OnStart; - importing.ImportingQueueEnd += OnEnd; + importing.ImportingQueueBegin += () => FlaxEngine.Scripting.InvokeOnUpdate(OnStart); + importing.ImportingQueueEnd += () => FlaxEngine.Scripting.InvokeOnUpdate(OnEnd); importing.ImportFileBegin += OnImportFileBegin; } private void OnImportFileBegin(IFileEntryAction importFileEntry) { + string info; if (importFileEntry is ImportFileEntry) - _currentInfo = string.Format("Importing \'{0}\'", System.IO.Path.GetFileName(importFileEntry.SourceUrl)); + info = string.Format("Importing \'{0}\'", System.IO.Path.GetFileName(importFileEntry.SourceUrl)); else - _currentInfo = string.Format("Creating \'{0}\'", importFileEntry.SourceUrl); - UpdateProgress(); - } - - private void UpdateProgress() - { - var importing = Editor.Instance.ContentImporting; - var info = string.Format("{0} ({1}/{2})...", _currentInfo, importing.ImportBatchDone, importing.ImportBatchSize); - OnUpdate(importing.ImportingProgress, info); + info = string.Format("Creating \'{0}\'", importFileEntry.SourceUrl); + FlaxEngine.Scripting.InvokeOnUpdate(() => + { + _currentInfo = info; + var importing = Editor.Instance.ContentImporting; + var text = string.Format("{0} ({1}/{2})...", _currentInfo, importing.ImportBatchDone, importing.ImportBatchSize); + OnUpdate(importing.ImportingProgress, text); + }); } } } diff --git a/Source/Editor/Progress/ProgressHandler.cs b/Source/Editor/Progress/ProgressHandler.cs index 2738b7c57..9581a0de6 100644 --- a/Source/Editor/Progress/ProgressHandler.cs +++ b/Source/Editor/Progress/ProgressHandler.cs @@ -16,7 +16,7 @@ namespace FlaxEditor.Progress /// /// The calling handler. public delegate void ProgressDelegate(ProgressHandler handler); - + /// /// Progress failed handler event delegate /// @@ -127,7 +127,7 @@ namespace FlaxEditor.Progress { if (!_isActive) throw new InvalidOperationException("Already ended."); - + _isActive = false; _progress = 0; _infoText = string.Empty; diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp index 2357fd241..a65b3ec9e 100644 --- a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp +++ b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp @@ -286,81 +286,17 @@ namespace VisualStudio return "Visual Studio open timout"; } - static ComPtr FindItem(const ComPtr& projectItems, BSTR filePath) - { - long count; - projectItems->get_Count(&count); - if (count == 0) - return nullptr; - - for (long i = 1; i <= count; i++) // They are counting from [1..Count] - { - ComPtr projectItem; - projectItems->Item(_variant_t(i), &projectItem); - if (!projectItem) - continue; - - short fileCount = 0; - projectItem->get_FileCount(&fileCount); - for (short fileIndex = 1; fileIndex <= fileCount; fileIndex++) - { - _bstr_t filename; - projectItem->get_FileNames(fileIndex, filename.GetAddress()); - - if (filename.GetBSTR() != nullptr && AreFilePathsEqual(filePath, filename)) - { - return projectItem; - } - } - - ComPtr childProjectItems; - projectItem->get_ProjectItems(&childProjectItems); - if (childProjectItems) - { - ComPtr result = FindItem(childProjectItems, filePath); - if (result) - return result; - } - } - - return nullptr; - } - - static ComPtr FindItem(const ComPtr& solution, BSTR filePath) - { + static ComPtr FindItem(const ComPtr& solution, BSTR filePath) + { HRESULT result; - ComPtr projects; - result = solution->get_Projects(&projects); + ComPtr projectItem; + result = solution->FindProjectItem(filePath, &projectItem); if (FAILED(result)) return nullptr; - long projectsCount = 0; - result = projects->get_Count(&projectsCount); - if (FAILED(result)) - return nullptr; - - for (long projectIndex = 1; projectIndex <= projectsCount; projectIndex++) // They are counting from [1..Count] - { - ComPtr project; - result = projects->Item(_variant_t(projectIndex), &project); - if (FAILED(result) || !project) - continue; - - ComPtr projectItems; - result = project->get_ProjectItems(&projectItems); - if (FAILED(result) || !projectItems) - continue; - - auto projectItem = FindItem(projectItems, filePath); - if (projectItem) - { - return projectItem; - } - } - - return nullptr; - } + return projectItem; + } // Opens a file on a specific line in a running Visual Studio instance. // diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp index 8807379cf..0b3cf6452 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.cpp +++ b/Source/Editor/Scripting/ScriptsBuilder.cpp @@ -170,7 +170,7 @@ bool ScriptsBuilder::IsSourceWorkspaceDirty() bool ScriptsBuilder::IsSourceDirtyFor(const TimeSpan& timeout) { ScopeLock scopeLock(_locker); - return _lastSourceCodeEdited > (_lastCompileAction + timeout); + return _lastSourceCodeEdited > _lastCompileAction && DateTime::Now() > _lastSourceCodeEdited + timeout; } bool ScriptsBuilder::IsCompiling() @@ -266,7 +266,7 @@ bool ScriptsBuilder::RunBuildTool(const StringView& args, const StringView& work bool ScriptsBuilder::GenerateProject(const StringView& customArgs) { - String args(TEXT("-log -genproject ")); + String args(TEXT("-log -mutex -genproject ")); args += customArgs; _wasProjectStructureChanged = false; return RunBuildTool(args); @@ -669,7 +669,7 @@ void ScriptsBuilderService::Update() } // Check if compile code (if has been edited) - const TimeSpan timeToCallCompileIfDirty = TimeSpan::FromMilliseconds(50); + const TimeSpan timeToCallCompileIfDirty = TimeSpan::FromMilliseconds(150); auto mainWindow = Engine::MainWindow; if (ScriptsBuilder::IsSourceDirtyFor(timeToCallCompileIfDirty) && mainWindow && mainWindow->IsFocused()) { diff --git a/Source/Editor/Scripting/ScriptsBuilder.h b/Source/Editor/Scripting/ScriptsBuilder.h index f954b0fd0..0a11bd9b7 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.h +++ b/Source/Editor/Scripting/ScriptsBuilder.h @@ -68,7 +68,7 @@ public: /// /// Time to use for checking. /// True if source code is dirty, otherwise false. - static bool IsSourceDirtyFor(const TimeSpan& timeout); + API_FUNCTION() static bool IsSourceDirtyFor(const TimeSpan& timeout); /// /// Returns true if scripts are being now compiled/reloaded. diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs index 16b7993de..40a3d2a63 100644 --- a/Source/Editor/Surface/Archetypes/Animation.cs +++ b/Source/Editor/Surface/Archetypes/Animation.cs @@ -34,6 +34,9 @@ namespace FlaxEditor.Surface.Archetypes /// public class Sample : SurfaceNode { + private AssetSelect _assetSelect; + private Box _assetBox; + /// public Sample(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) : base(id, context, nodeArch, groupArch) @@ -54,16 +57,42 @@ namespace FlaxEditor.Surface.Archetypes base.OnSurfaceLoaded(action); if (Surface != null) + { + _assetSelect = GetChild(); + if (TryGetBox(8, out var box)) + { + _assetBox = box; + _assetSelect.Visible = !_assetBox.HasAnyConnection; + } UpdateTitle(); + } } private void UpdateTitle() { var asset = Editor.Instance.ContentDatabase.Find((Guid)Values[0]); - Title = asset?.ShortName ?? "Animation"; + if (_assetBox != null) + Title = _assetBox.HasAnyConnection || asset == null ? "Animation" : asset.ShortName; + else + Title = asset?.ShortName ?? "Animation"; + var style = Style.Current; Resize(Mathf.Max(230, style.FontLarge.MeasureText(Title).X + 30), 160); } + + /// + public override void ConnectionTick(Box box) + { + base.ConnectionTick(box); + + if (_assetBox == null) + return; + if (box.ID != _assetBox.ID) + return; + + _assetSelect.Visible = !box.HasAnyConnection; + UpdateTitle(); + } } /// @@ -305,7 +334,8 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(0, "Speed", true, typeof(float), 5, 1), NodeElementArchetype.Factory.Input(1, "Loop", true, typeof(bool), 6, 2), NodeElementArchetype.Factory.Input(2, "Start Position", true, typeof(float), 7, 3), - NodeElementArchetype.Factory.Asset(0, Surface.Constants.LayoutOffsetY * 3, 0, typeof(FlaxEngine.Animation)), + NodeElementArchetype.Factory.Input(3, "Animation Asset", true, typeof(FlaxEngine.Animation), 8), + NodeElementArchetype.Factory.Asset(0, Surface.Constants.LayoutOffsetY * 4, 0, typeof(FlaxEngine.Animation)), } }, new NodeArchetype diff --git a/Source/Editor/Surface/Archetypes/Layers.cs b/Source/Editor/Surface/Archetypes/Layers.cs index 4a46276c3..0308cab15 100644 --- a/Source/Editor/Surface/Archetypes/Layers.cs +++ b/Source/Editor/Surface/Archetypes/Layers.cs @@ -176,7 +176,7 @@ namespace FlaxEditor.Surface.Archetypes Size = new Float2(200, 100), DefaultValues = new object[] { - 0.0f, + 0.5f, }, Elements = new[] { diff --git a/Source/Editor/Surface/Archetypes/Parameters.cs b/Source/Editor/Surface/Archetypes/Parameters.cs index 3fe727840..6ed5a61e6 100644 --- a/Source/Editor/Surface/Archetypes/Parameters.cs +++ b/Source/Editor/Surface/Archetypes/Parameters.cs @@ -510,7 +510,7 @@ namespace FlaxEditor.Surface.Archetypes for (var i = 0; i < elements.Length; i++) { - if(elements[i].Type != NodeElementType.Output) + if (elements[i].Type != NodeElementType.Output) continue; if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, inputType, hint)) @@ -533,7 +533,7 @@ namespace FlaxEditor.Surface.Archetypes for (var i = 0; i < elements.Length; i++) { - if(elements[i].Type != NodeElementType.Input) + if (elements[i].Type != NodeElementType.Input) continue; if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, outputType, hint)) return true; @@ -725,7 +725,7 @@ namespace FlaxEditor.Surface.Archetypes /// protected override bool UseNormalMaps => false; - + internal new static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context) { if (inputType == ScriptType.Object) @@ -743,7 +743,7 @@ namespace FlaxEditor.Surface.Archetypes for (var i = 0; i < elements.Length; i++) { - if(elements[i].Type != NodeElementType.Output) + if (elements[i].Type != NodeElementType.Output) continue; if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, inputType, hint)) return true; @@ -765,7 +765,7 @@ namespace FlaxEditor.Surface.Archetypes for (var i = 0; i < elements.Length; i++) { - if(elements[i].Type != NodeElementType.Input) + if (elements[i].Type != NodeElementType.Input) continue; if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, outputType, hint)) return true; @@ -789,7 +789,7 @@ namespace FlaxEditor.Surface.Archetypes /// protected override bool UseNormalMaps => false; - + internal new static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context) { if (inputType == ScriptType.Object) @@ -987,7 +987,7 @@ namespace FlaxEditor.Surface.Archetypes _combobox.Width = Width - 50; } } - + internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context) { return inputType == ScriptType.Void; @@ -997,7 +997,7 @@ namespace FlaxEditor.Surface.Archetypes { if (outputType == ScriptType.Void) return true; - + SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]); ScriptType type = parameter?.Type ?? ScriptType.Null; diff --git a/Source/Editor/Surface/VisjectSurface.Draw.cs b/Source/Editor/Surface/VisjectSurface.Draw.cs index f60f6ff3a..e7b76a538 100644 --- a/Source/Editor/Surface/VisjectSurface.Draw.cs +++ b/Source/Editor/Surface/VisjectSurface.Draw.cs @@ -141,7 +141,7 @@ namespace FlaxEditor.Surface if (_connectionInstigator is Archetypes.Tools.RerouteNode) { - if (endPos.X < startPos.X && _lastInstigatorUnderMouse is null or Box { IsOutput: true}) + if (endPos.X < startPos.X && _lastInstigatorUnderMouse is null or Box { IsOutput: true }) { actualStartPos = endPos; actualEndPos = startPos; diff --git a/Source/Editor/Tools/Terrain/PaintTerrainGizmo.cs b/Source/Editor/Tools/Terrain/PaintTerrainGizmo.cs index c16bdd7e6..c9a521734 100644 --- a/Source/Editor/Tools/Terrain/PaintTerrainGizmo.cs +++ b/Source/Editor/Tools/Terrain/PaintTerrainGizmo.cs @@ -150,6 +150,12 @@ namespace FlaxEditor.Tools.Terrain return; } + // Increase or decrease brush size with scroll + if (Input.GetKey(KeyboardKeys.Shift)) + { + Mode.CurrentBrush.Size += dt * Mode.CurrentBrush.Size * Input.Mouse.ScrollDelta * 5f; + } + // Check if no terrain is selected var terrain = SelectedTerrain; if (!terrain) diff --git a/Source/Editor/Tools/Terrain/SculptTerrainGizmo.cs b/Source/Editor/Tools/Terrain/SculptTerrainGizmo.cs index 96270a740..fef8bbf09 100644 --- a/Source/Editor/Tools/Terrain/SculptTerrainGizmo.cs +++ b/Source/Editor/Tools/Terrain/SculptTerrainGizmo.cs @@ -158,6 +158,12 @@ namespace FlaxEditor.Tools.Terrain return; } + // Increase or decrease brush size with scroll + if (Input.GetKey(KeyboardKeys.Shift)) + { + Mode.CurrentBrush.Size += dt * Mode.CurrentBrush.Size * Input.Mouse.ScrollDelta * 5f; + } + // Check if selected terrain was changed during painting if (terrain != _paintTerrain && IsPainting) { diff --git a/Source/Editor/Undo/Actions/AddRemoveScriptAction.cs b/Source/Editor/Undo/Actions/AddRemoveScriptAction.cs index 69a790b39..dac2900f4 100644 --- a/Source/Editor/Undo/Actions/AddRemoveScriptAction.cs +++ b/Source/Editor/Undo/Actions/AddRemoveScriptAction.cs @@ -49,7 +49,16 @@ namespace FlaxEditor.Actions _scriptTypeName = script.TypeName; _prefabId = script.PrefabID; _prefabObjectId = script.PrefabObjectID; - _scriptData = FlaxEngine.Json.JsonSerializer.Serialize(script); + try + { + _scriptData = FlaxEngine.Json.JsonSerializer.Serialize(script); + } + catch (Exception ex) + { + _scriptData = null; + Debug.LogError("Failed to serialize script data for Undo due to exception"); + Debug.LogException(ex); + } _parentId = script.Actor.ID; _orderInParent = script.OrderInParent; _enabled = script.Enabled; @@ -66,6 +75,11 @@ namespace FlaxEditor.Actions _enabled = true; } + public int GetOrderInParent() + { + return _orderInParent; + } + /// /// Creates a new added script undo action. /// @@ -175,6 +189,7 @@ namespace FlaxEditor.Actions script.Parent = parentActor; if (_orderInParent != -1) script.OrderInParent = _orderInParent; + _orderInParent = script.OrderInParent; // Ensure order is correct for script that want to use it later if (_prefabObjectId != Guid.Empty) SceneObject.Internal_LinkPrefab(Object.GetUnmanagedPtr(script), ref _prefabId, ref _prefabObjectId); Editor.Instance.Scene.MarkSceneEdited(parentActor.Scene); diff --git a/Source/Editor/Utilities/EditorUtilities.cpp b/Source/Editor/Utilities/EditorUtilities.cpp index 8e3fc0e4a..a2418ceb1 100644 --- a/Source/Editor/Utilities/EditorUtilities.cpp +++ b/Source/Editor/Utilities/EditorUtilities.cpp @@ -394,8 +394,12 @@ bool EditorUtilities::UpdateExeIcon(const String& path, const TextureData& icon) // - icon/cursor/etc data std::fstream stream; +#if PLATFORM_WINDOWS + stream.open(path.Get(), std::ios::in | std::ios::out | std::ios::binary); +#else StringAsANSI<> pathAnsi(path.Get()); stream.open(pathAnsi.Get(), std::ios::in | std::ios::out | std::ios::binary); +#endif if (!stream.is_open()) { LOG(Warning, "Cannot open file"); diff --git a/Source/Editor/Utilities/ScreenUtilities.cpp b/Source/Editor/Utilities/ScreenUtilities.cpp index 44f52350e..730a69aa3 100644 --- a/Source/Editor/Utilities/ScreenUtilities.cpp +++ b/Source/Editor/Utilities/ScreenUtilities.cpp @@ -73,6 +73,7 @@ Color32 ScreenUtilities::GetColorAt(const Float2& pos) outputColor.R = color.red / 256; outputColor.G = color.green / 256; outputColor.B = color.blue / 256; + outputColor.A = 255; return outputColor; } diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs index 9a4fd34a2..a24dd2f38 100644 --- a/Source/Editor/Viewport/EditorGizmoViewport.cs +++ b/Source/Editor/Viewport/EditorGizmoViewport.cs @@ -41,6 +41,8 @@ namespace FlaxEditor.Viewport Gizmos[i].Update(deltaTime); } } + /// + public EditorViewport Viewport => this; /// public GizmosCollection Gizmos { get; } diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 13c06157a..9a444f2da 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -593,12 +593,14 @@ namespace FlaxEditor.Viewport { ref var vv = ref v.Options[j]; var button = childMenu.AddButton(vv.Name); + button.CloseMenuOnClick = false; button.Tag = vv.Mode; } } else { var button = debugView.AddButton(v.Name); + button.CloseMenuOnClick = false; button.Tag = v.Mode; } } @@ -1119,7 +1121,12 @@ namespace FlaxEditor.Viewport var win = (WindowRootControl)Root; // Get current mouse position in the view - _viewMousePos = PointFromWindow(win.MousePosition); + { + // When the window is not focused, the position in window does not return sane values + Float2 pos = PointFromWindow(win.MousePosition); + if (!float.IsInfinity(pos.LengthSquared)) + _viewMousePos = pos; + } // Update input var window = win.Window; @@ -1582,7 +1589,14 @@ namespace FlaxEditor.Viewport private void WidgetViewModeShowHideClicked(ContextMenuButton button) { if (button.Tag is ViewMode v) + { Task.ViewMode = v; + var cm = button.ParentContextMenu; + WidgetViewModeShowHide(cm); + var mainCM = ViewWidgetButtonMenu.GetChildMenu("Debug View").ContextMenu; + if (mainCM != null && cm != mainCM) + WidgetViewModeShowHide(mainCM); + } } private void WidgetViewModeShowHide(Control cm) @@ -1594,7 +1608,7 @@ namespace FlaxEditor.Viewport foreach (var e in ccm.Items) { if (e is ContextMenuButton b && b.Tag is ViewMode v) - b.Icon = Task.View.Mode == v ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; + b.Icon = Task.ViewMode == v ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; } } diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index 1dc459135..eb462deb1 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -306,6 +306,8 @@ namespace FlaxEditor.Viewport var orient = ViewOrientation; ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orient); } + /// + public EditorViewport Viewport => this; /// public GizmosCollection Gizmos { get; } diff --git a/Source/Editor/Viewport/Previews/AudioClipPreview.cs b/Source/Editor/Viewport/Previews/AudioClipPreview.cs index 446cef657..3235cd194 100644 --- a/Source/Editor/Viewport/Previews/AudioClipPreview.cs +++ b/Source/Editor/Viewport/Previews/AudioClipPreview.cs @@ -171,7 +171,7 @@ namespace FlaxEditor.Viewport.Previews case DrawModes.Fill: clipsInView = 1.0f; clipWidth = width; - samplesPerIndex = (uint)(samplesPerChannel / width); + samplesPerIndex = (uint)(samplesPerChannel / width) * info.NumChannels; break; case DrawModes.Single: clipsInView = Mathf.Min(clipsInView, 1.0f); diff --git a/Source/Editor/ViewportDebugDrawData.cs b/Source/Editor/ViewportDebugDrawData.cs index 48b551675..7fccd697f 100644 --- a/Source/Editor/ViewportDebugDrawData.cs +++ b/Source/Editor/ViewportDebugDrawData.cs @@ -97,7 +97,7 @@ namespace FlaxEditor if (_highlightMaterial == null || (_highlights.Count == 0 && _highlightTriangles.Count == 0) || renderContext.View.Pass == DrawPass.Depth - ) + ) return; Profiler.BeginEvent("ViewportDebugDrawData.OnDraw"); diff --git a/Source/Editor/Windows/Assets/AssetEditorWindow.cs b/Source/Editor/Windows/Assets/AssetEditorWindow.cs index 0d244479c..2e048b924 100644 --- a/Source/Editor/Windows/Assets/AssetEditorWindow.cs +++ b/Source/Editor/Windows/Assets/AssetEditorWindow.cs @@ -388,14 +388,16 @@ namespace FlaxEditor.Windows.Assets protected override void OnShow() { // Check if has no asset (but has item linked) - if (_asset == null && _item != null) + var item = _item; + if (_asset == null && item != null) { // Load asset _asset = LoadAsset(); if (_asset == null) { - Editor.LogError(string.Format("Cannot load asset \'{0}\' ({1})", _item.Path, typeof(T))); + Editor.LogError(string.Format("Cannot load asset \'{0}\' ({1})", item.Path, typeof(T))); Close(); + Editor.ContentDatabase.RefreshFolder(item, false); return; } diff --git a/Source/Editor/Windows/Assets/AudioClipWindow.cs b/Source/Editor/Windows/Assets/AudioClipWindow.cs index 8cf31f416..b1d9796dc 100644 --- a/Source/Editor/Windows/Assets/AudioClipWindow.cs +++ b/Source/Editor/Windows/Assets/AudioClipWindow.cs @@ -335,6 +335,22 @@ namespace FlaxEditor.Windows.Assets } } + /// + public override bool OnKeyDown(KeyboardKeys key) + { + if (base.OnKeyDown(key)) + return true; + + if (key == KeyboardKeys.Spacebar) + { + if (_previewSource?.State == AudioSource.States.Playing) + OnPause(); + else + OnPlay(); + } + return false; + } + /// public override bool UseLayoutData => true; diff --git a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs index 5f1273999..3e0525fb7 100644 --- a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs @@ -521,8 +521,11 @@ namespace FlaxEditor.Windows.Assets /// protected override void OnClose() { - // Discard unsaved changes - _properties.DiscardChanges(); + if (Asset) + { + // Discard unsaved changes + _properties.DiscardChanges(); + } // Cleanup _undo.Clear(); diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs index 6de553a30..05b59b800 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs @@ -153,7 +153,7 @@ namespace FlaxEditor.Windows.Assets { OnPasteAction(pasteAction); } - + // Scroll to new selected node ScrollToSelectedNode(); } @@ -183,7 +183,7 @@ namespace FlaxEditor.Windows.Assets { OnPasteAction(pasteAction); } - + // Scroll to new selected node ScrollToSelectedNode(); } @@ -334,7 +334,7 @@ namespace FlaxEditor.Windows.Assets }, action2.ActionString); action.Do(); Undo.AddAction(action); - + _treePanel.PerformLayout(); _treePanel.PerformLayout(); } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 4deaf5d7d..4face0dc0 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -207,7 +207,7 @@ namespace FlaxEditor.Windows.Assets InputActions.Add(options => options.Rename, Rename); InputActions.Add(options => options.FocusSelection, _viewport.FocusSelection); } - + /// /// Enables or disables vertical and horizontal scrolling on the tree panel. /// @@ -257,7 +257,7 @@ namespace FlaxEditor.Windows.Assets { if (base.OnMouseUp(location, button)) return true; - + if (button == MouseButton.Right && _treePanel.ContainsPoint(ref location)) { _tree.Deselect(); diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index 03873df57..56136cfbf 100644 --- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs +++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs @@ -2,6 +2,7 @@ using System; using System.IO; +using System.Linq; using FlaxEditor.Content; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.Scripting; @@ -249,6 +250,10 @@ namespace FlaxEditor.Windows }); } + // Remove any leftover separator + if (cm.ItemsContainer.Children.LastOrDefault() is ContextMenuSeparator) + cm.ItemsContainer.Children.Last().Dispose(); + // Show it cm.Show(this, location); } diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index a06ec839d..d43174cf3 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -629,8 +629,9 @@ namespace FlaxEditor.Windows if (items.Count == 0) return; - // TODO: remove items that depend on different items in the list: use wants to remove `folderA` and `folderA/asset.x`, we should just remove `folderA` + // Sort items to remove files first, then folders var toDelete = new List(items); + toDelete.Sort((a, b) => a.IsFolder ? 1 : b.IsFolder ? -1 : a.Compare(b)); string msg = toDelete.Count == 1 ? string.Format("Are you sure to delete \'{0}\'?\nThis action cannot be undone. Files will be deleted permanently.", items[0].Path) diff --git a/Source/Editor/Windows/Profiler/Network.cs b/Source/Editor/Windows/Profiler/Network.cs index dbee0e8e7..d29000d27 100644 --- a/Source/Editor/Windows/Profiler/Network.cs +++ b/Source/Editor/Windows/Profiler/Network.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using FlaxEditor.GUI; using FlaxEngine; using FlaxEngine.GUI; +using FlaxEngine.Networking; namespace FlaxEngine { @@ -37,11 +38,14 @@ namespace FlaxEditor.Windows.Profiler { private readonly SingleChart _dataSentChart; private readonly SingleChart _dataReceivedChart; + private readonly SingleChart _dataSentRateChart; + private readonly SingleChart _dataReceivedRateChart; private readonly Table _tableRpc; private readonly Table _tableRep; - private SamplesBuffer _events; private List _tableRowsCache; - private FlaxEngine.Networking.NetworkDriverStats _prevStats; + private SamplesBuffer _events; + private NetworkDriverStats _prevStats; + private List _stats; public Network() : base("Network") @@ -76,6 +80,20 @@ namespace FlaxEditor.Windows.Profiler Parent = layout, }; _dataReceivedChart.SelectedSampleChanged += OnSelectedSampleChanged; + _dataSentRateChart = new SingleChart + { + Title = "Data Sent Rate", + FormatSample = FormatSampleBytesRate, + Parent = layout, + }; + _dataSentRateChart.SelectedSampleChanged += OnSelectedSampleChanged; + _dataReceivedRateChart = new SingleChart + { + Title = "Data Received Rate", + FormatSample = FormatSampleBytesRate, + Parent = layout, + }; + _dataReceivedRateChart.SelectedSampleChanged += OnSelectedSampleChanged; // Tables _tableRpc = InitTable(layout, "RPC Name"); @@ -87,24 +105,52 @@ namespace FlaxEditor.Windows.Profiler { _dataSentChart.Clear(); _dataReceivedChart.Clear(); + _dataSentRateChart.Clear(); + _dataReceivedRateChart.Clear(); _events?.Clear(); + _stats?.Clear(); + _prevStats = new NetworkDriverStats(); } /// public override void Update(ref SharedUpdateData sharedData) { // Gather peer stats - var peers = FlaxEngine.Networking.NetworkPeer.Peers; - var stats = new FlaxEngine.Networking.NetworkDriverStats(); + var peers = NetworkPeer.Peers; + var thisStats = new NetworkDriverStats(); + thisStats.RTT = Time.UnscaledGameTime; // Store sample time in RTT foreach (var peer in peers) { var peerStats = peer.NetworkDriver.GetStats(); - stats.TotalDataSent += peerStats.TotalDataSent; - stats.TotalDataReceived += peerStats.TotalDataReceived; + thisStats.TotalDataSent += peerStats.TotalDataSent; + thisStats.TotalDataReceived += peerStats.TotalDataReceived; } - _dataSentChart.AddSample(Mathf.Max((long)stats.TotalDataSent - (long)_prevStats.TotalDataSent, 0)); - _dataReceivedChart.AddSample(Mathf.Max((long)stats.TotalDataReceived - (long)_prevStats.TotalDataReceived, 0)); - _prevStats = stats; + var stats = thisStats; + stats.TotalDataSent = (uint)Mathf.Max((long)thisStats.TotalDataSent - (long)_prevStats.TotalDataSent, 0); + stats.TotalDataReceived = (uint)Mathf.Max((long)thisStats.TotalDataReceived - (long)_prevStats.TotalDataReceived, 0); + _dataSentChart.AddSample(stats.TotalDataSent); + _dataReceivedChart.AddSample(stats.TotalDataReceived); + _prevStats = thisStats; + if (_stats == null) + _stats = new List(); + _stats.Add(stats); + + // Remove all stats older than 1 second + while (_stats.Count > 0 && thisStats.RTT - _stats[0].RTT >= 1.0f) + _stats.RemoveAt(0); + + // Calculate average data rates (from last second) + var avgStats = new NetworkDriverStats(); + foreach (var e in _stats) + { + avgStats.TotalDataSent += e.TotalDataSent; + avgStats.TotalDataReceived += e.TotalDataReceived; + } + avgStats.TotalDataSent /= (uint)_stats.Count; + avgStats.TotalDataReceived /= (uint)_stats.Count; + _dataSentRateChart.AddSample(avgStats.TotalDataSent); + _dataReceivedRateChart.AddSample(avgStats.TotalDataReceived); + // Gather network events var events = ProfilingTools.EventsNetwork; @@ -118,6 +164,8 @@ namespace FlaxEditor.Windows.Profiler { _dataSentChart.SelectedSampleIndex = selectedFrame; _dataReceivedChart.SelectedSampleIndex = selectedFrame; + _dataSentRateChart.SelectedSampleIndex = selectedFrame; + _dataReceivedRateChart.SelectedSampleIndex = selectedFrame; // Update events tables if (_events != null) @@ -128,7 +176,7 @@ namespace FlaxEditor.Windows.Profiler _tableRep.IsLayoutLocked = true; RecycleTableRows(_tableRpc, _tableRowsCache); RecycleTableRows(_tableRep, _tableRowsCache); - + var events = _events.Get(selectedFrame); var rowCount = Int2.Zero; if (events != null && events.Length != 0) @@ -186,7 +234,7 @@ namespace FlaxEditor.Windows.Profiler _tableRep.Visible = rowCount.Y != 0; _tableRpc.Children.Sort(SortRows); _tableRep.Children.Sort(SortRows); - + _tableRpc.UnlockChildrenRecursive(); _tableRpc.PerformLayout(); _tableRep.UnlockChildrenRecursive(); @@ -257,6 +305,11 @@ namespace FlaxEditor.Windows.Profiler return Utilities.Utils.FormatBytesCount((ulong)v); } + private static string FormatSampleBytesRate(float v) + { + return Utilities.Utils.FormatBytesCount((ulong)v) + "/s"; + } + private static string FormatCellBytes(object x) { return Utilities.Utils.FormatBytesCount((int)x); diff --git a/Source/Editor/Windows/Profiler/SamplesBuffer.cs b/Source/Editor/Windows/Profiler/SamplesBuffer.cs index 999156dca..2e0169d47 100644 --- a/Source/Editor/Windows/Profiler/SamplesBuffer.cs +++ b/Source/Editor/Windows/Profiler/SamplesBuffer.cs @@ -49,7 +49,7 @@ namespace FlaxEditor.Windows.Profiler /// The sample value public T Get(int index) { - if (index >= _data.Length || _data.Length == 0) + if (_count == 0 || index >= _data.Length || _data.Length == 0) return default; return index == -1 ? _data[_count - 1] : _data[index]; } diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 73ddbc41c..0518fe248 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "AnimGraph.h" +#include "Engine/Core/Types/VariantValueCast.h" #include "Engine/Content/Assets/Animation.h" #include "Engine/Content/Assets/SkeletonMask.h" #include "Engine/Content/Assets/AnimationGraphFunction.h" @@ -512,18 +513,6 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const transitionIndex++; continue; } - const bool useDefaultRule = EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule); - if (transition.RuleGraph && !useDefaultRule) - { - // Execute transition rule - auto rootNode = transition.RuleGraph->GetRootNode(); - ASSERT(rootNode); - if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0])) - { - transitionIndex++; - continue; - } - } // Evaluate source state transition data (position, length, etc.) const Value sourceStatePtr = SampleState(stateMachineBucket.CurrentState); @@ -543,6 +532,19 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const transitionData.Length = ZeroTolerance; } + const bool useDefaultRule = EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule); + if (transition.RuleGraph && !useDefaultRule) + { + // Execute transition rule + auto rootNode = transition.RuleGraph->GetRootNode(); + ASSERT(rootNode); + if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0])) + { + transitionIndex++; + continue; + } + } + // Check if can trigger the transition bool canEnter = false; if (useDefaultRule) @@ -749,9 +751,16 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Animation case 2: { - const auto anim = node->Assets[0].As(); + auto anim = node->Assets[0].As(); auto& bucket = context.Data->State[node->BucketIndex].Animation; + // Override animation when animation reference box is connected + auto animationAssetBox = node->TryGetBox(8); + if (animationAssetBox && animationAssetBox->HasConnection()) + { + anim = TVariantValueCast::Cast(tryGetValue(animationAssetBox, Value::Null)); + } + switch (box->ID) { // Animation diff --git a/Source/Engine/Audio/AudioSource.cpp b/Source/Engine/Audio/AudioSource.cpp index afe5c4b49..cb32e0967 100644 --- a/Source/Engine/Audio/AudioSource.cpp +++ b/Source/Engine/Audio/AudioSource.cpp @@ -187,7 +187,6 @@ float AudioSource::GetTime() const return 0.0f; float time = AudioBackend::Source::GetCurrentBufferTime(this); - ASSERT(time >= 0.0f && time <= Clip->GetLength()); if (UseStreaming()) { diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp index 2f65a4b6c..ace8b6591 100644 --- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp +++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp @@ -33,7 +33,8 @@ int alError = alGetError(); \ if (alError != 0) \ { \ - LOG(Error, "OpenAL method {0} failed with error 0x{1:X} (at line {2})", TEXT(#method), alError, __LINE__ - 1); \ + const Char* errorStr = GetOpenALErrorString(alError); \ + LOG(Error, "OpenAL method {0} failed with error 0x{1:X}:{2} (at line {3})", TEXT(#method), alError, errorStr, __LINE__ - 1); \ } \ } #endif @@ -290,6 +291,28 @@ ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth) return 0; } +const Char* GetOpenALErrorString(int error) +{ + switch (error) + { + case AL_NO_ERROR: + return TEXT("AL_NO_ERROR"); + case AL_INVALID_NAME: + return TEXT("AL_INVALID_NAME"); + case AL_INVALID_ENUM: + return TEXT("AL_INVALID_ENUM"); + case AL_INVALID_VALUE: + return TEXT("AL_INVALID_VALUE"); + case AL_INVALID_OPERATION: + return TEXT("AL_INVALID_OPERATION"); + case AL_OUT_OF_MEMORY: + return TEXT("AL_OUT_OF_MEMORY"); + default: + break; + } + return TEXT("???"); +} + void AudioBackendOAL::Listener_OnAdd(AudioListener* listener) { #if ALC_MULTIPLE_LISTENERS @@ -838,7 +861,11 @@ bool AudioBackendOAL::Base_Init() // Init Base_SetDopplerFactor(AudioSettings::Get()->DopplerFactor); alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // Default attenuation model - ALC::RebuildContexts(true); + int32 clampedIndex = Math::Clamp(activeDeviceIndex, -1, Audio::Devices.Count() - 1); + if (clampedIndex == Audio::GetActiveDeviceIndex()) + { + ALC::RebuildContexts(true); + } Audio::SetActiveDeviceIndex(activeDeviceIndex); #ifdef AL_SOFT_source_spatialize if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize")) diff --git a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp index f90c58875..3092a2229 100644 --- a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp +++ b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp @@ -27,7 +27,15 @@ #define MAX_INPUT_CHANNELS 2 #define MAX_OUTPUT_CHANNELS 8 #define MAX_CHANNELS_MATRIX_SIZE (MAX_INPUT_CHANNELS*MAX_OUTPUT_CHANNELS) - +#if ENABLE_ASSERTION +#define XAUDIO2_CHECK_ERROR(method) \ + if (hr != 0) \ + { \ + LOG(Error, "XAudio2 method {0} failed with error 0x{1:X} (at line {2})", TEXT(#method), (uint32)hr, __LINE__ - 1); \ + } +#else +#define XAUDIO2_CHECK_ERROR(method) +#endif #define FLAX_COORD_SCALE 0.01f // units are meters #define FLAX_DST_TO_XAUDIO(x) x * FLAX_COORD_SCALE #define FLAX_POS_TO_XAUDIO(vec) X3DAUDIO_VECTOR(vec.X * FLAX_COORD_SCALE, vec.Y * FLAX_COORD_SCALE, vec.Z * FLAX_COORD_SCALE) @@ -104,7 +112,9 @@ namespace XAudio2 COM_DECLSPEC_NOTHROW void STDMETHODCALLTYPE OnVoiceError(THIS_ void* pBufferContext, HRESULT Error) override { +#if ENABLE_ASSERTION LOG(Warning, "IXAudio2VoiceCallback::OnVoiceError! Error: 0x{0:x}", Error); +#endif } public: @@ -121,7 +131,8 @@ namespace XAudio2 XAUDIO2_SEND_DESCRIPTOR Destination; float Pitch; float Pan; - float StartTime; + float StartTimeForQueueBuffer; + float LastBufferStartTime; float DopplerFactor; uint64 LastBufferStartSamplesPlayed; int32 BuffersProcessed; @@ -145,7 +156,8 @@ namespace XAudio2 Destination.pOutputVoice = nullptr; Pitch = 1.0f; Pan = 0.0f; - StartTime = 0.0f; + StartTimeForQueueBuffer = 0.0f; + LastBufferStartTime = 0.0f; IsDirty = false; Is3D = false; IsPlaying = false; @@ -255,18 +267,18 @@ namespace XAudio2 buffer.pAudioData = aBuffer->Data.Get(); buffer.AudioBytes = aBuffer->Data.Count(); - if (aSource->StartTime > ZeroTolerance) + if (aSource->StartTimeForQueueBuffer > ZeroTolerance) { - buffer.PlayBegin = (UINT32)(aSource->StartTime * (aBuffer->Info.SampleRate * aBuffer->Info.NumChannels)); - buffer.PlayLength = aBuffer->Info.NumSamples / aBuffer->Info.NumChannels - buffer.PlayBegin; - aSource->StartTime = 0; + // Offset start position when playing buffer with a custom time offset + const uint32 bytesPerSample = aBuffer->Info.BitDepth / 8 * aBuffer->Info.NumChannels; + buffer.PlayBegin = (UINT32)(aSource->StartTimeForQueueBuffer * aBuffer->Info.SampleRate); + buffer.PlayLength = (buffer.AudioBytes / bytesPerSample) - buffer.PlayBegin; + aSource->LastBufferStartTime = aSource->StartTimeForQueueBuffer; + aSource->StartTimeForQueueBuffer = 0; } const HRESULT hr = aSource->Voice->SubmitSourceBuffer(&buffer); - if (FAILED(hr)) - { - LOG(Warning, "XAudio2: Failed to submit source buffer (error: 0x{0:x})", hr); - } + XAUDIO2_CHECK_ERROR(SubmitSourceBuffer); } void VoiceCallback::OnBufferEnd(void* pBufferContext) @@ -375,7 +387,7 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source) const auto& header = clip->AudioHeader; auto& format = aSource->Format; format.wFormatTag = WAVE_FORMAT_PCM; - format.nChannels = source->Is3D() ? 1 : header.Info.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write) + format.nChannels = clip->Is3D() ? 1 : header.Info.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write) format.nSamplesPerSec = header.Info.SampleRate; format.wBitsPerSample = header.Info.BitDepth; format.nBlockAlign = (WORD)(format.nChannels * (format.wBitsPerSample / 8)); @@ -391,12 +403,10 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source) 1, &aSource->Destination }; - const HRESULT hr = XAudio2::Instance->CreateSourceVoice(&aSource->Voice, &aSource->Format, 0, 2.0f, &aSource->Callback, &sendList); + HRESULT hr = XAudio2::Instance->CreateSourceVoice(&aSource->Voice, &aSource->Format, 0, 2.0f, &aSource->Callback, &sendList); + XAUDIO2_CHECK_ERROR(CreateSourceVoice); if (FAILED(hr)) - { - LOG(Error, "Failed to create XAudio2 voice. Error: 0x{0:x}", hr); return; - } // Prepare source state aSource->Callback.Source = source; @@ -410,7 +420,8 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source) aSource->DopplerFactor = source->GetDopplerFactor(); aSource->UpdateTransform(source); aSource->UpdateVelocity(source); - aSource->Voice->SetVolume(source->GetVolume()); + hr = aSource->Voice->SetVolume(source->GetVolume()); + XAUDIO2_CHECK_ERROR(SetVolume); // 0 is invalid ID so shift them sourceID++; @@ -451,7 +462,8 @@ void AudioBackendXAudio2::Source_VolumeChanged(AudioSource* source) auto aSource = XAudio2::GetSource(source); if (aSource && aSource->Voice) { - aSource->Voice->SetVolume(source->GetVolume()); + const HRESULT hr = aSource->Voice->SetVolume(source->GetVolume()); + XAUDIO2_CHECK_ERROR(SetVolume); } } @@ -494,12 +506,18 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source) XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1]; XAudio2::Locker.Unlock(); + HRESULT hr; const bool isPlaying = source->IsActuallyPlayingSth(); if (isPlaying) - aSource->Voice->Stop(); + { + hr = aSource->Voice->Stop(); + XAUDIO2_CHECK_ERROR(Stop); + } - aSource->Voice->FlushSourceBuffers(); + hr = aSource->Voice->FlushSourceBuffers(); + XAUDIO2_CHECK_ERROR(FlushSourceBuffers); aSource->LastBufferStartSamplesPlayed = 0; + aSource->LastBufferStartTime = 0; aSource->BuffersProcessed = 0; XAUDIO2_BUFFER buffer = { 0 }; @@ -512,12 +530,15 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source) const UINT32 totalSamples = aBuffer->Info.NumSamples / aBuffer->Info.NumChannels; buffer.PlayBegin = state.SamplesPlayed % totalSamples; buffer.PlayLength = totalSamples - buffer.PlayBegin; - aSource->StartTime = 0; + aSource->StartTimeForQueueBuffer = 0; XAudio2::QueueBuffer(aSource, source, bufferId, buffer); if (isPlaying) - aSource->Voice->Start(); + { + hr = aSource->Voice->Start(); + XAUDIO2_CHECK_ERROR(Start); + } } void AudioBackendXAudio2::Source_SpatialSetupChanged(AudioSource* source) @@ -572,7 +593,8 @@ void AudioBackendXAudio2::Source_Play(AudioSource* source) if (aSource && aSource->Voice && !aSource->IsPlaying) { // Play - aSource->Voice->Start(); + const HRESULT hr = aSource->Voice->Start(); + XAUDIO2_CHECK_ERROR(Start); aSource->IsPlaying = true; } } @@ -583,7 +605,8 @@ void AudioBackendXAudio2::Source_Pause(AudioSource* source) if (aSource && aSource->Voice && aSource->IsPlaying) { // Pause - aSource->Voice->Stop(); + const HRESULT hr = aSource->Voice->Stop(); + XAUDIO2_CHECK_ERROR(Stop); aSource->IsPlaying = false; } } @@ -593,14 +616,18 @@ void AudioBackendXAudio2::Source_Stop(AudioSource* source) auto aSource = XAudio2::GetSource(source); if (aSource && aSource->Voice) { - aSource->StartTime = 0.0f; + aSource->StartTimeForQueueBuffer = 0.0f; + aSource->LastBufferStartTime = 0.0f; // Pause - aSource->Voice->Stop(); + HRESULT hr = aSource->Voice->Stop(); + XAUDIO2_CHECK_ERROR(Stop); aSource->IsPlaying = false; // Unset streaming buffers to rewind - aSource->Voice->FlushSourceBuffers(); + hr = aSource->Voice->FlushSourceBuffers(); + XAUDIO2_CHECK_ERROR(FlushSourceBuffers); + Platform::Sleep(10); // TODO: find a better way to handle case when VoiceCallback::OnBufferEnd is called after source was stopped thus BuffersProcessed != 0, probably via buffers contexts ptrs aSource->BuffersProcessed = 0; aSource->Callback.PeekSamples(); } @@ -612,7 +639,7 @@ void AudioBackendXAudio2::Source_SetCurrentBufferTime(AudioSource* source, float if (aSource) { // Store start time so next buffer submitted will start from here (assumes audio is stopped) - aSource->StartTime = value; + aSource->StartTimeForQueueBuffer = value; } } @@ -628,8 +655,9 @@ float AudioBackendXAudio2::Source_GetCurrentBufferTime(const AudioSource* source aSource->Voice->GetState(&state); const uint32 numChannels = clipInfo.NumChannels; const uint32 totalSamples = clipInfo.NumSamples / numChannels; + const uint32 sampleRate = clipInfo.SampleRate;// / clipInfo.NumChannels; state.SamplesPlayed -= aSource->LastBufferStartSamplesPlayed % totalSamples; // Offset by the last buffer start to get time relative to its begin - time = aSource->StartTime + (state.SamplesPlayed % totalSamples) / static_cast(Math::Max(1U, clipInfo.SampleRate)); + time = aSource->LastBufferStartTime + (state.SamplesPlayed % totalSamples) / static_cast(Math::Max(1U, sampleRate)); } return time; } @@ -697,10 +725,7 @@ void AudioBackendXAudio2::Source_DequeueProcessedBuffers(AudioSource* source) if (aSource && aSource->Voice) { const HRESULT hr = aSource->Voice->FlushSourceBuffers(); - if (FAILED(hr)) - { - LOG(Warning, "XAudio2: FlushSourceBuffers failed. Error: 0x{0:x}", hr); - } + XAUDIO2_CHECK_ERROR(FlushSourceBuffers); aSource->BuffersProcessed = 0; } } @@ -749,8 +774,7 @@ void AudioBackendXAudio2::Buffer_Write(uint32 bufferId, byte* samples, const Aud XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1]; XAudio2::Locker.Unlock(); - const uint32 bytesPerSample = info.BitDepth / 8; - const int32 samplesLength = info.NumSamples * bytesPerSample; + const uint32 samplesLength = info.NumSamples * info.BitDepth / 8; aBuffer->Info = info; aBuffer->Data.Set(samples, samplesLength); @@ -779,7 +803,8 @@ void AudioBackendXAudio2::Base_SetVolume(float value) { if (XAudio2::MasteringVoice) { - XAudio2::MasteringVoice->SetVolume(value); + const HRESULT hr = XAudio2::MasteringVoice->SetVolume(value); + XAUDIO2_CHECK_ERROR(SetVolume); } } diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index 309a82e04..613c7c2c2 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -538,11 +538,7 @@ ContentLoadTask* Asset::createLoadingTask() void Asset::startLoading() { - // Check if is already loaded - if (IsLoaded()) - return; - - // Start loading (using async tasks) + ASSERT(!IsLoaded()); ASSERT(_loadingTask == nullptr); _loadingTask = createLoadingTask(); ASSERT(_loadingTask != nullptr); diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index 76ed34b80..276b6b1f7 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -20,6 +20,7 @@ #include "Engine/ShadersCompilation/Config.h" #if BUILD_DEBUG #include "Engine/Engine/Globals.h" +#include "Engine/Scripting/BinaryModule.h" #endif #endif @@ -256,7 +257,9 @@ Asset::LoadResult Material::load() #if BUILD_DEBUG && USE_EDITOR // Dump generated material source to the temporary file + BinaryModule::Locker.Lock(); source.SaveToFile(Globals::ProjectCacheFolder / TEXT("material.txt")); + BinaryModule::Locker.Unlock(); #endif // Encrypt source code diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index ea627f2c7..bd27abc5a 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -154,7 +154,7 @@ void ContentService::LateUpdate() // Unload marked assets for (int32 i = 0; i < ToUnload.Count(); i++) { - Asset* asset = ToUnload[i]; + Asset* asset = ToUnload[i]; // Check if has no references if (asset->GetReferencesCount() <= 0) @@ -521,37 +521,33 @@ Asset* Content::GetAsset(const Guid& id) void Content::DeleteAsset(Asset* asset) { - ScopeLock locker(AssetsLocker); - - // Validate if (asset == nullptr || asset->_deleteFileOnUnload) - { - // Back return; - } LOG(Info, "Deleting asset {0}...", asset->ToString()); + // Ensure that asset is loaded (easier than cancel in-flight loading) + asset->WaitForLoaded(); + // Mark asset for delete queue (delete it after auto unload) asset->_deleteFileOnUnload = true; // Unload - UnloadAsset(asset); + asset->DeleteObject(); } void Content::DeleteAsset(const StringView& path) { - ScopeLock locker(AssetsLocker); - - // Check if is loaded + // Try to delete already loaded asset Asset* asset = GetAsset(path); if (asset != nullptr) { - // Delete asset DeleteAsset(asset); return; } + ScopeLock locker(AssetsLocker); + // Remove from registry AssetInfo info; if (Cache.DeleteAsset(path, &info)) @@ -573,7 +569,6 @@ void Content::deleteFileSafety(const StringView& path, const Guid& id) // Check if given id is invalid if (!id.IsValid()) { - // Cancel operation LOG(Warning, "Cannot remove file \'{0}\'. Given ID is invalid.", path); return; } @@ -585,7 +580,6 @@ void Content::deleteFileSafety(const StringView& path, const Guid& id) storage->CloseFileHandles(); // Close file handle to allow removing it if (!storage->HasAsset(id)) { - // Skip removing LOG(Warning, "Cannot remove file \'{0}\'. It doesn\'t contain asset {1}.", path, id); return; } @@ -703,13 +697,11 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat LOG(Warning, "Cannot copy file to destination."); return true; } - if (JsonStorageProxy::ChangeId(dstPath, dstId)) { LOG(Warning, "Cannot change asset ID."); return true; } - return false; } @@ -774,12 +766,9 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat FileSystem::DeleteFile(tmpPath); // Reload storage + if (auto storage = ContentStorageManager::GetStorage(dstPath)) { - auto storage = ContentStorageManager::GetStorage(dstPath); - if (storage) - { - storage->Reload(); - } + storage->Reload(); } } @@ -790,10 +779,8 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat void Content::UnloadAsset(Asset* asset) { - // Check input if (asset == nullptr) return; - asset->DeleteObject(); } @@ -919,12 +906,8 @@ bool Content::IsAssetTypeIdInvalid(const ScriptingTypeHandle& type, const Script Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type) { - // Early out if (!id.IsValid()) - { - // Back return nullptr; - } // Check if asset has been already loaded Asset* result = GetAsset(id); @@ -936,7 +919,6 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type) LOG(Warning, "Different loaded asset type! Asset: \'{0}\'. Expected type: {1}", result->ToString(), type.ToString()); return nullptr; } - return result; } @@ -954,12 +936,8 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type) LoadCallAssetsLocker.Lock(); const bool contains = LoadCallAssets.Contains(id); LoadCallAssetsLocker.Unlock(); - if (!contains) - { return GetAsset(id); - } - Platform::Sleep(1); } } @@ -967,7 +945,6 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type) { // Mark asset as loading LoadCallAssets.Add(id); - LoadCallAssetsLocker.Unlock(); } @@ -988,7 +965,7 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& // Get cached asset info (from registry) if (!GetAssetInfo(id, assetInfo)) { - LOG(Warning, "Invalid or missing asset ({0}, {1}).", id.ToString(Guid::FormatType::N), type.ToString()); + LOG(Warning, "Invalid or missing asset ({0}, {1}).", id, type.ToString()); return nullptr; } @@ -1032,11 +1009,13 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& ASSERT(!Assets.ContainsKey(id)); #endif Assets.Add(id, result); - AssetsLocker.Unlock(); // Start asset loading + // TODO: refactor this to create asset loading task-chain before AssetsLocker.Lock() to allow better parallelization result->startLoading(); + AssetsLocker.Unlock(); + return result; } diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h index 5ee384769..19d6fdd31 100644 --- a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h +++ b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h @@ -31,10 +31,13 @@ public: if (Asset) { Asset->Locker.Lock(); - Asset->_loadFailed = true; - Asset->_isLoaded = false; - LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed)); - Asset->_loadingTask = nullptr; + if (Asset->_loadingTask == this) + { + Asset->_loadFailed = true; + Asset->_isLoaded = false; + LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed)); + Asset->_loadingTask = nullptr; + } Asset->Locker.Unlock(); } } @@ -73,7 +76,10 @@ protected: { if (Asset) { - Asset->_loadingTask = nullptr; + Asset->Locker.Lock(); + if (Asset->_loadingTask == this) + Asset->_loadingTask = nullptr; + Asset->Locker.Unlock(); Asset = nullptr; } @@ -84,7 +90,10 @@ protected: { if (Asset) { - Asset->_loadingTask = nullptr; + Asset->Locker.Lock(); + if (Asset->_loadingTask == this) + Asset->_loadingTask = nullptr; + Asset->Locker.Unlock(); Asset = nullptr; } diff --git a/Source/Engine/ContentImporters/ImportModelFile.cpp b/Source/Engine/ContentImporters/ImportModelFile.cpp index b9a4f5675..b34bbfaaf 100644 --- a/Source/Engine/ContentImporters/ImportModelFile.cpp +++ b/Source/Engine/ContentImporters/ImportModelFile.cpp @@ -124,7 +124,8 @@ CreateAssetResult ImportModelFile::Import(CreateAssetContext& context) // Import model file ModelData modelData; String errorMsg; - String autoImportOutput = String(StringUtils::GetDirectoryName(context.TargetAssetPath)) / StringUtils::GetFileNameWithoutExtension(context.InputPath); + String autoImportOutput(StringUtils::GetDirectoryName(context.TargetAssetPath)); + autoImportOutput /= options.SubAssetFolder.HasChars() ? options.SubAssetFolder.TrimTrailing() : String(StringUtils::GetFileNameWithoutExtension(context.InputPath)); if (ModelTool::ImportModel(context.InputPath, modelData, options, errorMsg, autoImportOutput)) { LOG(Error, "Cannot import model file. {0}", errorMsg); diff --git a/Source/Engine/Core/Collections/ChunkedArray.h b/Source/Engine/Core/Collections/ChunkedArray.h index 38bf92fb8..b3765a9fe 100644 --- a/Source/Engine/Core/Collections/ChunkedArray.h +++ b/Source/Engine/Core/Collections/ChunkedArray.h @@ -100,7 +100,7 @@ public: int32 _chunkIndex; int32 _index; - Iterator(ChunkedArray const* collection, const int32 index) + Iterator(const ChunkedArray* collection, const int32 index) : _collection(const_cast(collection)) , _chunkIndex(index / ChunkSize) , _index(index % ChunkSize) @@ -122,29 +122,29 @@ public: { } - public: - FORCE_INLINE ChunkedArray* GetChunkedArray() const + Iterator(Iterator&& i) + : _collection(i._collection) + , _chunkIndex(i._chunkIndex) + , _index(i._index) { - return _collection; } + public: FORCE_INLINE int32 Index() const { return _chunkIndex * ChunkSize + _index; } - public: - bool IsEnd() const + FORCE_INLINE bool IsEnd() const { - return Index() == _collection->Count(); + return (_chunkIndex * ChunkSize + _index) == _collection->_count; } - bool IsNotEnd() const + FORCE_INLINE bool IsNotEnd() const { - return Index() != _collection->Count(); + return (_chunkIndex * ChunkSize + _index) != _collection->_count; } - public: FORCE_INLINE T& operator*() const { return _collection->_chunks[_chunkIndex]->At(_index); @@ -155,7 +155,6 @@ public: return &_collection->_chunks[_chunkIndex]->At(_index); } - public: FORCE_INLINE bool operator==(const Iterator& v) const { return _collection == v._collection && _chunkIndex == v._chunkIndex && _index == v._index; @@ -166,17 +165,22 @@ public: return _collection != v._collection || _chunkIndex != v._chunkIndex || _index != v._index; } - public: + Iterator& operator=(const Iterator& v) + { + _collection = v._collection; + _chunkIndex = v._chunkIndex; + _index = v._index; + return *this; + } + Iterator& operator++() { // Check if it is not at end - const int32 end = _collection->Count(); - if (Index() != end) + if ((_chunkIndex * ChunkSize + _index) != _collection->_count) { // Move forward within chunk _index++; - // Check if need to change chunk if (_index == ChunkSize && _chunkIndex < _collection->_chunks.Count() - 1) { // Move to next chunk @@ -189,9 +193,9 @@ public: Iterator operator++(int) { - Iterator temp = *this; - ++temp; - return temp; + Iterator i = *this; + ++i; + return i; } Iterator& operator--() @@ -199,7 +203,6 @@ public: // Check if it's not at beginning if (_index != 0 || _chunkIndex != 0) { - // Check if need to change chunk if (_index == 0) { // Move to previous chunk @@ -217,9 +220,9 @@ public: Iterator operator--(int) { - Iterator temp = *this; - --temp; - return temp; + Iterator i = *this; + --i; + return i; } }; @@ -294,7 +297,7 @@ public: { if (IsEmpty()) return; - ASSERT(i.GetChunkedArray() == this); + ASSERT(i._collection == this); ASSERT(i._chunkIndex < _chunks.Count() && i._index < ChunkSize); ASSERT(i.Index() < Count()); @@ -432,11 +435,31 @@ public: Iterator End() const { - return Iterator(this, Count()); + return Iterator(this, _count); } Iterator IteratorAt(int32 index) const { return Iterator(this, index); } + + FORCE_INLINE Iterator begin() + { + return Iterator(this, 0); + } + + FORCE_INLINE Iterator end() + { + return Iterator(this, _count); + } + + FORCE_INLINE const Iterator begin() const + { + return Iterator(this, 0); + } + + FORCE_INLINE const Iterator end() const + { + return Iterator(this, _count); + } }; diff --git a/Source/Engine/Core/Config/BuildSettings.h b/Source/Engine/Core/Config/BuildSettings.h index 318c304e0..3232c6b02 100644 --- a/Source/Engine/Core/Config/BuildSettings.h +++ b/Source/Engine/Core/Config/BuildSettings.h @@ -4,6 +4,7 @@ #include "Engine/Core/Config/Settings.h" #include "Engine/Serialization/Serialization.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Content/Asset.h" #include "Engine/Content/AssetReference.h" #include "Engine/Content/SceneReference.h" @@ -76,6 +77,12 @@ public: API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Content\")") bool ShadersGenerateDebugData = false; + /// + /// If checked, skips bundling default engine fonts for UI. Use if to reduce build size if you don't use default engine fonts but custom ones only. + /// + API_FIELD(Attributes="EditorOrder(2100), EditorDisplay(\"Content\")") + bool SkipDefaultFonts = false; + /// /// If checked, .NET Runtime won't be packaged with a game and will be required by user to be installed on system upon running game build. Available only on supported platforms such as Windows, Linux and macOS. /// @@ -106,6 +113,7 @@ public: DESERIALIZE(AdditionalAssetFolders); DESERIALIZE(ShadersNoOptimize); DESERIALIZE(ShadersGenerateDebugData); + DESERIALIZE(SkipDefaultFonts); DESERIALIZE(SkipDotnetPackaging); DESERIALIZE(SkipUnusedDotnetLibsPackaging); } diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp index e3d97e149..bb8d42aba 100644 --- a/Source/Engine/Core/Log.cpp +++ b/Source/Engine/Core/Log.cpp @@ -3,16 +3,15 @@ #include "Log.h" #include "Engine/Engine/CommandLine.h" #include "Engine/Core/Types/DateTime.h" -#include "Engine/Core/Collections/Sorting.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Engine/Time.h" #include "Engine/Engine/Globals.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Platform/CriticalSection.h" #include "Engine/Serialization/FileWriteStream.h" -#include "Engine/Serialization/MemoryWriteStream.h" #include "Engine/Debug/Exceptions/Exceptions.h" #if USE_EDITOR -#include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Collections/Sorting.h" #endif #include @@ -199,35 +198,36 @@ void Log::Logger::WriteFloor() void Log::Logger::ProcessLogMessage(LogType type, const StringView& msg, fmt_flax::memory_buffer& w) { const TimeSpan time = DateTime::Now() - LogStartTime; + const int32 msgLength = msg.Length(); fmt_flax::format(w, TEXT("[ {0} ]: [{1}] "), *time.ToString('a'), ToString(type)); // On Windows convert all '\n' into '\r\n' #if PLATFORM_WINDOWS - const int32 msgLength = msg.Length(); - if (msgLength > 1) + bool hasWindowsNewLine = false; + for (int32 i = 1; i < msgLength && !hasWindowsNewLine; i++) + hasWindowsNewLine |= msg.Get()[i - 1] != '\r' && msg.Get()[i] == '\n'; + if (hasWindowsNewLine) { - MemoryWriteStream msgStream(msgLength * sizeof(Char)); - msgStream.WriteChar(msg[0]); + Array msgStream; + msgStream.EnsureCapacity(msgLength); + msgStream.Add(msg.Get()[0]); for (int32 i = 1; i < msgLength; i++) { - if (msg[i - 1] != '\r' && msg[i] == '\n') - msgStream.WriteChar(TEXT('\r')); - msgStream.WriteChar(msg[i]); + if (msg.Get()[i - 1] != '\r' && msg.Get()[i] == '\n') + msgStream.Add(TEXT('\r')); + msgStream.Add(msg.Get()[i]); } - msgStream.WriteChar(msg[msgLength]); - msgStream.WriteChar(TEXT('\0')); - fmt_flax::format(w, TEXT("{}"), (const Char*)msgStream.GetHandle()); - //w.append(msgStream.GetHandle(), msgStream.GetHandle() + msgStream.GetPosition()); + msgStream.Add(TEXT('\0')); + w.append(msgStream.Get(), (const Char*)(msgStream.Get() + msgStream.Count())); + //fmt_flax::format(w, TEXT("{}"), (const Char*)msgStream.Get()); + return; } - else - { - //w.append(msg.Get(), msg.Get() + msg.Length()); - fmt_flax::format(w, TEXT("{}"), msg); - } -#else - fmt_flax::format(w, TEXT("{}"), msg); #endif + + // Output raw message to the log + w.append(msg.Get(), msg.Get() + msg.Length()); + //fmt_flax::format(w, TEXT("{}"), msg); } void Log::Logger::Write(LogType type, const StringView& msg) diff --git a/Source/Engine/Core/Math/Color.h b/Source/Engine/Core/Math/Color.h index af93fc0f2..836ead090 100644 --- a/Source/Engine/Core/Math/Color.h +++ b/Source/Engine/Core/Math/Color.h @@ -71,7 +71,7 @@ public: /// The green channel value. /// The blue channel value. /// The alpha channel value. - Color(float r, float g, float b, float a = 1) + FORCE_INLINE Color(float r, float g, float b, float a = 1) : R(r) , G(g) , B(b) @@ -203,7 +203,7 @@ public: return Color(R - b.R, G - b.G, B - b.B, A - b.A); } - Color operator*(const Color& b) const + FORCE_INLINE Color operator*(const Color& b) const { return Color(R * b.R, G * b.G, B * b.B, A * b.A); } diff --git a/Source/Engine/Core/Math/Half.cpp b/Source/Engine/Core/Math/Half.cpp index 4e4b6eb50..b3c1696a5 100644 --- a/Source/Engine/Core/Math/Half.cpp +++ b/Source/Engine/Core/Math/Half.cpp @@ -1,10 +1,8 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "Half.h" -#include "Rectangle.h" -#include "Vector2.h" -#include "Vector3.h" #include "Vector4.h" +#include "Rectangle.h" #include "Color.h" static_assert(sizeof(Half) == 2, "Invalid Half type size."); @@ -16,12 +14,47 @@ Half2 Half2::Zero(0.0f, 0.0f); Half3 Half3::Zero(0.0f, 0.0f, 0.0f); Half4 Half4::Zero(0.0f, 0.0f, 0.0f, 0.0f); -Half2::Half2(const Float2& v) +#if !USE_SSE_HALF_CONVERSION + +Half Float16Compressor::Compress(float value) { - X = Float16Compressor::Compress(v.X); - Y = Float16Compressor::Compress(v.Y); + Bits v, s; + v.f = value; + uint32 sign = v.si & signN; + v.si ^= sign; + sign >>= shiftSign; // logical shift + s.si = mulN; + s.si = static_cast(s.f * v.f); // correct subnormals + v.si ^= (s.si ^ v.si) & -(minN > v.si); + v.si ^= (infN ^ v.si) & -((infN > v.si) & (v.si > maxN)); + v.si ^= (nanN ^ v.si) & -((nanN > v.si) & (v.si > infN)); + v.ui >>= shift; // logical shift + v.si ^= ((v.si - maxD) ^ v.si) & -(v.si > maxC); + v.si ^= ((v.si - minD) ^ v.si) & -(v.si > subC); + return v.ui | sign; } +float Float16Compressor::Decompress(Half value) +{ + Bits v; + v.ui = value; + int32 sign = v.si & signC; + v.si ^= sign; + sign <<= shiftSign; + v.si ^= ((v.si + minD) ^ v.si) & -(v.si > subC); + v.si ^= ((v.si + maxD) ^ v.si) & -(v.si > maxC); + Bits s; + s.si = mulC; + s.f *= v.si; + const int32 mask = -(norC > v.si); + v.si <<= shift; + v.si ^= (s.si ^ v.si) & mask; + v.si |= sign; + return v.f; +} + +#endif + Float2 Half2::ToFloat2() const { return Float2( @@ -30,13 +63,6 @@ Float2 Half2::ToFloat2() const ); } -Half3::Half3(const Float3& v) -{ - X = Float16Compressor::Compress(v.X); - Y = Float16Compressor::Compress(v.Y); - Z = Float16Compressor::Compress(v.Z); -} - Float3 Half3::ToFloat3() const { return Float3( diff --git a/Source/Engine/Core/Math/Half.h b/Source/Engine/Core/Math/Half.h index 346b5d6b3..44b90df66 100644 --- a/Source/Engine/Core/Math/Half.h +++ b/Source/Engine/Core/Math/Half.h @@ -3,6 +3,8 @@ #pragma once #include "Math.h" +#include "Vector2.h" +#include "Vector3.h" /// /// Half-precision 16 bit floating point number consisting of a sign bit, a 5 bit biased exponent, and a 10 bit mantissa @@ -45,54 +47,23 @@ class FLAXENGINE_API Float16Compressor static const int32 minD = minC - subC - 1; public: - static Half Compress(const float value) - { #if USE_SSE_HALF_CONVERSION + FORCE_INLINE static Half Compress(float value) + { __m128 value1 = _mm_set_ss(value); __m128i value2 = _mm_cvtps_ph(value1, 0); return static_cast(_mm_cvtsi128_si32(value2)); -#else - Bits v, s; - v.f = value; - uint32 sign = v.si & signN; - v.si ^= sign; - sign >>= shiftSign; // logical shift - s.si = mulN; - s.si = static_cast(s.f * v.f); // correct subnormals - v.si ^= (s.si ^ v.si) & -(minN > v.si); - v.si ^= (infN ^ v.si) & -((infN > v.si) & (v.si > maxN)); - v.si ^= (nanN ^ v.si) & -((nanN > v.si) & (v.si > infN)); - v.ui >>= shift; // logical shift - v.si ^= ((v.si - maxD) ^ v.si) & -(v.si > maxC); - v.si ^= ((v.si - minD) ^ v.si) & -(v.si > subC); - return v.ui | sign; -#endif } - - static float Decompress(const Half value) + FORCE_INLINE static float Decompress(Half value) { -#if USE_SSE_HALF_CONVERSION __m128i value1 = _mm_cvtsi32_si128(static_cast(value)); __m128 value2 = _mm_cvtph_ps(value1); return _mm_cvtss_f32(value2); -#else - Bits v; - v.ui = value; - int32 sign = v.si & signC; - v.si ^= sign; - sign <<= shiftSign; - v.si ^= ((v.si + minD) ^ v.si) & -(v.si > subC); - v.si ^= ((v.si + maxD) ^ v.si) & -(v.si > maxC); - Bits s; - s.si = mulC; - s.f *= v.si; - const int32 mask = -(norC > v.si); - v.si <<= shift; - v.si ^= (s.si ^ v.si) & mask; - v.si |= sign; - return v.f; -#endif } +#else + static Half Compress(float value); + static float Decompress(Half value); +#endif }; /// @@ -128,7 +99,7 @@ public: /// /// X component /// Y component - Half2(Half x, Half y) + FORCE_INLINE Half2(Half x, Half y) : X(x) , Y(y) { @@ -139,7 +110,7 @@ public: /// /// X component /// Y component - Half2(float x, float y) + FORCE_INLINE Half2(float x, float y) { X = Float16Compressor::Compress(x); Y = Float16Compressor::Compress(y); @@ -149,7 +120,11 @@ public: /// Init /// /// X and Y components - Half2(const Float2& v); + FORCE_INLINE Half2(const Float2& v) + { + X = Float16Compressor::Compress(v.X); + Y = Float16Compressor::Compress(v.Y); + } public: Float2 ToFloat2() const; @@ -185,21 +160,26 @@ public: public: Half3() = default; - Half3(Half x, Half y, Half z) + FORCE_INLINE Half3(Half x, Half y, Half z) : X(x) , Y(y) , Z(z) { } - Half3(const float x, const float y, const float z) + FORCE_INLINE Half3(float x, float y, float z) { X = Float16Compressor::Compress(x); Y = Float16Compressor::Compress(y); Z = Float16Compressor::Compress(z); } - Half3(const Float3& v); + FORCE_INLINE Half3(const Float3& v) + { + X = Float16Compressor::Compress(v.X); + Y = Float16Compressor::Compress(v.Y); + Z = Float16Compressor::Compress(v.Z); + } public: Float3 ToFloat3() const; diff --git a/Source/Engine/Core/ObjectsRemovalService.cpp b/Source/Engine/Core/ObjectsRemovalService.cpp index bb3fd8bf1..a020f7844 100644 --- a/Source/Engine/Core/ObjectsRemovalService.cpp +++ b/Source/Engine/Core/ObjectsRemovalService.cpp @@ -5,7 +5,6 @@ #include "Collections/Dictionary.h" #include "Engine/Engine/Time.h" #include "Engine/Engine/EngineService.h" -#include "Engine/Threading/Threading.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/ScriptingObject.h" @@ -14,16 +13,15 @@ const Char* HertzSizesData[] = { TEXT("Hz"), TEXT("KHz"), TEXT("MHz"), TEXT("GHz Span Utilities::Private::BytesSizes(BytesSizesData, ARRAY_COUNT(BytesSizesData)); Span Utilities::Private::HertzSizes(HertzSizesData, ARRAY_COUNT(HertzSizesData)); -namespace ObjectsRemovalServiceImpl +namespace { CriticalSection PoolLocker; DateTime LastUpdate; float LastUpdateGameTime; Dictionary Pool(8192); + uint64 PoolCounter = 0; } -using namespace ObjectsRemovalServiceImpl; - class ObjectsRemoval : public EngineService { public: @@ -64,6 +62,7 @@ void ObjectsRemovalService::Add(Object* obj, float timeToLive, bool useGameTime) PoolLocker.Lock(); Pool[obj] = timeToLive; + PoolCounter++; PoolLocker.Unlock(); } @@ -72,6 +71,7 @@ void ObjectsRemovalService::Flush(float dt, float gameDelta) PROFILE_CPU(); PoolLocker.Lock(); + PoolCounter = 0; // Update timeouts and delete objects that timed out for (auto i = Pool.Begin(); i.IsNotEnd(); ++i) @@ -90,6 +90,24 @@ void ObjectsRemovalService::Flush(float dt, float gameDelta) } } + // If any object was added to the pool while removing objects (by this thread) then retry removing any nested objects (but without delta time) + if (PoolCounter != 0) + { + RETRY: + PoolCounter = 0; + for (auto i = Pool.Begin(); i.IsNotEnd(); ++i) + { + if (i->Value <= 0.0f) + { + Object* obj = i->Key; + Pool.Remove(i); + obj->OnDeleteObject(); + } + } + if (PoolCounter != 0) + goto RETRY; + } + PoolLocker.Unlock(); } @@ -121,7 +139,7 @@ void ObjectsRemoval::Dispose() // Delete all remaining objects { - ScopeLock lock(PoolLocker); + PoolLocker.Lock(); for (auto i = Pool.Begin(); i.IsNotEnd(); ++i) { Object* obj = i->Key; @@ -129,6 +147,7 @@ void ObjectsRemoval::Dispose() obj->OnDeleteObject(); } Pool.Clear(); + PoolLocker.Unlock(); } } diff --git a/Source/Engine/Core/Types/Span.h b/Source/Engine/Core/Types/Span.h index e0518ec77..b51c858ec 100644 --- a/Source/Engine/Core/Types/Span.h +++ b/Source/Engine/Core/Types/Span.h @@ -107,6 +107,26 @@ public: ASSERT(index >= 0 && index < _length); return _data[index]; } + + FORCE_INLINE T* begin() + { + return _data; + } + + FORCE_INLINE T* end() + { + return _data + _length; + } + + FORCE_INLINE const T* begin() const + { + return _data; + } + + FORCE_INLINE const T* end() const + { + return _data + _length; + } }; template diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 5b93ab588..1a57c4f10 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -596,11 +596,13 @@ void EngineImpl::InitPaths() Globals::ProjectCacheFolder = Globals::ProjectFolder / TEXT("Cache"); #endif +#if USE_MONO // We must ensure that engine is located in folder which path contains only ANSI characters // Why? Mono lib must have etc and lib folders at ANSI path // But project can be located on Unicode path if (!Globals::StartupFolder.IsANSI()) Platform::Fatal(TEXT("Cannot start application in directory which name contains non-ANSI characters.")); +#endif #if !PLATFORM_SWITCH && !FLAX_TESTS // Setup directories diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index b40fa740a..0391974b6 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -188,7 +188,7 @@ namespace FlaxEngine.Interop internal static void RegisterNativeLibrary(IntPtr moduleNamePtr, IntPtr modulePathPtr) { string moduleName = Marshal.PtrToStringAnsi(moduleNamePtr); - string modulePath = Marshal.PtrToStringAnsi(modulePathPtr); + string modulePath = Marshal.PtrToStringUni(modulePathPtr); libraryPaths[moduleName] = modulePath; } @@ -857,36 +857,25 @@ namespace FlaxEngine.Interop } [UnmanagedCallersOnly] - internal static void FieldGetValueReference(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle, IntPtr valuePtr) + internal static void FieldGetValueReference(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle, int fieldOffset, IntPtr valuePtr) { object fieldOwner = fieldOwnerHandle.Target; + IntPtr fieldRef; +#if USE_AOT FieldHolder field = Unsafe.As(fieldHandle.Target); + fieldRef = IntPtr.Zero; + Debug.LogError("Not supported FieldGetValueReference"); +#else if (fieldOwner.GetType().IsValueType) { - ref IntPtr fieldRef = ref FieldHelper.GetValueTypeFieldReference(field.fieldOffset, ref fieldOwner); - Unsafe.Write(valuePtr.ToPointer(), fieldRef); + fieldRef = FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); } else { - ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(field.fieldOffset, ref fieldOwner); - Unsafe.Write(valuePtr.ToPointer(), fieldRef); - } - } - - [UnmanagedCallersOnly] - internal static void FieldGetValueReferenceWithOffset(ManagedHandle fieldOwnerHandle, int fieldOffset, IntPtr valuePtr) - { - object fieldOwner = fieldOwnerHandle.Target; - if (fieldOwner.GetType().IsValueType) - { - ref IntPtr fieldRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); - Unsafe.Write(valuePtr.ToPointer(), fieldRef); - } - else - { - ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); - Unsafe.Write(valuePtr.ToPointer(), fieldRef); + fieldRef = FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); } +#endif + Unsafe.Write(valuePtr.ToPointer(), fieldRef); } [UnmanagedCallersOnly] diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index f3bb57665..2f74e421c 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -119,6 +119,7 @@ namespace FlaxEngine.Interop { } +#if !USE_AOT // Cache offsets to frequently accessed fields of FlaxEngine.Object private static int unmanagedPtrFieldOffset = IntPtr.Size + (Unsafe.Read((typeof(FlaxEngine.Object).GetField("__unmanagedPtr", BindingFlags.Instance | BindingFlags.NonPublic).FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF); private static int internalIdFieldOffset = IntPtr.Size + (Unsafe.Read((typeof(FlaxEngine.Object).GetField("__internalId", BindingFlags.Instance | BindingFlags.NonPublic).FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF); @@ -150,6 +151,7 @@ namespace FlaxEngine.Interop object obj = typeHolder.CreateScriptingObject(unmanagedPtr, idPtr); return ManagedHandle.Alloc(obj); } +#endif internal static void* NativeAlloc(int byteCount) { @@ -429,6 +431,9 @@ namespace FlaxEngine.Interop /// internal static int GetFieldOffset(FieldInfo field, Type type) { + if (field.IsLiteral) + return 0; + // Get the address of the field, source: https://stackoverflow.com/a/56512720 int fieldOffset = Unsafe.Read((field.FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF; if (!type.IsValueType) @@ -436,6 +441,23 @@ namespace FlaxEngine.Interop return fieldOffset; } +#if USE_AOT + /// + /// Helper utility to set field of the referenced value via reflection. + /// + internal static void SetReferenceTypeField(FieldInfo field, ref T fieldOwner, object fieldValue) + { + if (typeof(T).IsValueType) + { + // Value types need setting via boxed object to properly propagate value + object fieldOwnerBoxed = fieldOwner; + field.SetValue(fieldOwnerBoxed, fieldValue); + fieldOwner = (T)fieldOwnerBoxed; + } + else + field.SetValue(fieldOwner, fieldValue); + } +#else /// /// Returns a reference to the value of the field. /// @@ -462,6 +484,7 @@ namespace FlaxEngine.Interop byte* fieldPtr = (byte*)Unsafe.As(ref fieldOwner) + fieldOffset; return ref Unsafe.AsRef(fieldPtr); } +#endif } /// @@ -735,29 +758,49 @@ namespace FlaxEngine.Interop private static void ToManagedFieldPointerValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { +#if USE_AOT + IntPtr fieldValue = Unsafe.Read(nativeFieldPtr.ToPointer()); + FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); +#else ref IntPtr fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); fieldValueRef = Unsafe.Read(nativeFieldPtr.ToPointer()); +#endif fieldSize = IntPtr.Size; } private static void ToManagedFieldPointerReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { +#if USE_AOT + IntPtr fieldValue = Unsafe.Read(nativeFieldPtr.ToPointer()); + FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); +#else ref IntPtr fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); fieldValueRef = Unsafe.Read(nativeFieldPtr.ToPointer()); +#endif fieldSize = IntPtr.Size; } private static void ToNativeFieldPointerValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { - ref IntPtr fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); - Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValueRef); +#if USE_AOT + object boxed = field.GetValue(fieldOwner); + IntPtr fieldValue = new IntPtr(Pointer.Unbox(boxed)); +#else + IntPtr fieldValue = FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); +#endif + Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValue); fieldSize = IntPtr.Size; } private static void ToNativeFieldPointerReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { - ref IntPtr fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); - Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValueRef); +#if USE_AOT + object boxed = field.GetValue(fieldOwner); + IntPtr fieldValue = new IntPtr(Pointer.Unbox(boxed)); +#else + IntPtr fieldValue = FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); +#endif + Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValue); fieldSize = IntPtr.Size; } @@ -799,8 +842,15 @@ namespace FlaxEngine.Interop fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); } - ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); - MarshalHelper.ToManaged(ref fieldValueRef, nativeFieldPtr, false); +#if USE_AOT + TField fieldValue = default; +#else + ref TField fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); +#endif + MarshalHelper.ToManaged(ref fieldValue, nativeFieldPtr, false); +#if USE_AOT + FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); +#endif } internal static void ToManagedFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class @@ -813,8 +863,15 @@ namespace FlaxEngine.Interop fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); } - ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); - MarshalHelper.ToManaged(ref fieldValueRef, nativeFieldPtr, false); +#if USE_AOT + TField fieldValue = default; +#else + ref TField fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); +#endif + MarshalHelper.ToManaged(ref fieldValue, nativeFieldPtr, false); +#if USE_AOT + FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); +#endif } internal static void ToManagedFieldArrayValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct @@ -825,8 +882,15 @@ namespace FlaxEngine.Interop nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); - ref TField[] fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); - MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); +#if USE_AOT + TField[] fieldValue = (TField[])field.GetValue(fieldOwner); +#else + ref TField[] fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); +#endif + MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false); +#if USE_AOT + FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); +#endif } internal static void ToManagedFieldArrayReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class @@ -837,8 +901,15 @@ namespace FlaxEngine.Interop nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); - ref TField[] fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); - MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); +#if USE_AOT + TField[] fieldValue = null; +#else + ref TField[] fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); +#endif + MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false); +#if USE_AOT + FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); +#endif } internal static void ToNativeFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct @@ -852,11 +923,11 @@ namespace FlaxEngine.Interop } #if USE_AOT - TField fieldValueRef = (TField)field.GetValue(fieldOwner); + TField fieldValue = (TField)field.GetValue(fieldOwner); #else - ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + ref TField fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); #endif - MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); + MarshalHelper.ToNative(ref fieldValue, nativeFieldPtr); } internal static void ToNativeFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class @@ -870,11 +941,11 @@ namespace FlaxEngine.Interop } #if USE_AOT - TField fieldValueRef = (TField)field.GetValue(fieldOwner); + TField fieldValue = (TField)field.GetValue(fieldOwner); #else - ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + ref TField fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); #endif - MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); + MarshalHelper.ToNative(ref fieldValue, nativeFieldPtr); } } @@ -904,8 +975,15 @@ namespace FlaxEngine.Interop nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); - ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); - MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); +#if USE_AOT + TField fieldValue = null; +#else + ref TField fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); +#endif + MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false); +#if USE_AOT + FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); +#endif } internal static void ToManagedFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class @@ -915,8 +993,15 @@ namespace FlaxEngine.Interop nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); - ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); - MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); +#if USE_AOT + TField fieldValue = default; +#else + ref TField fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); +#endif + MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false); +#if USE_AOT + FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); +#endif } internal static void ToManagedFieldArrayValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct @@ -926,8 +1011,15 @@ namespace FlaxEngine.Interop nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); - ref TField[] fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); - MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); +#if USE_AOT + TField[] fieldValue = null; +#else + ref TField[] fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); +#endif + MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false); +#if USE_AOT + FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); +#endif } internal static void ToManagedFieldArrayReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class @@ -937,8 +1029,15 @@ namespace FlaxEngine.Interop nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); - ref TField[] fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); - MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); +#if USE_AOT + TField[] fieldValue = null; +#else + ref TField[] fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); +#endif + MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false); +#if USE_AOT + FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); +#endif } internal static void ToNativeFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct @@ -948,8 +1047,12 @@ namespace FlaxEngine.Interop nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); - ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); - MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); +#if USE_AOT + TField fieldValue = (TField)field.GetValue(fieldOwner); +#else + ref TField fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); +#endif + MarshalHelper.ToNative(ref fieldValue, nativeFieldPtr); } internal static void ToNativeFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class @@ -959,8 +1062,12 @@ namespace FlaxEngine.Interop nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); - ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); - MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); +#if USE_AOT + TField fieldValue = (TField)field.GetValue(fieldOwner); +#else + ref TField fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); +#endif + MarshalHelper.ToNative(ref fieldValue, nativeFieldPtr); } } } @@ -1044,7 +1151,7 @@ namespace FlaxEngine.Interop var fields = MarshalHelper.marshallableFields; var offsets = MarshalHelper.marshallableFieldOffsets; var marshallers = MarshalHelper.toNativeFieldMarshallers; - for (int i = 0; i < MarshalHelper.marshallableFields.Length; i++) + for (int i = 0; i < fields.Length; i++) { marshallers[i](fields[i], offsets[i], ref managedValue, nativePtr, out int fieldSize); nativePtr += fieldSize; @@ -1306,11 +1413,13 @@ namespace FlaxEngine.Interop return RuntimeHelpers.GetUninitializedObject(wrappedType); } +#if !USE_AOT internal object CreateScriptingObject(IntPtr unmanagedPtr, IntPtr idPtr) { - object obj = CreateObject(); + object obj = RuntimeHelpers.GetUninitializedObject(wrappedType); if (obj is Object) { + // TODO: use UnsafeAccessorAttribute on .NET 8 and use this path on all platforms (including non-Desktop, see MCore::ScriptingObject::CreateScriptingObject) { ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(unmanagedPtrFieldOffset, ref obj); fieldRef = unmanagedPtr; @@ -1331,8 +1440,9 @@ namespace FlaxEngine.Interop return obj; } +#endif - public static implicit operator Type(TypeHolder holder) => holder?.type ?? null; + public static implicit operator Type(TypeHolder holder) => holder?.type; public bool Equals(TypeHolder other) => type == other.type; public bool Equals(Type other) => type == other; public override int GetHashCode() => type.GetHashCode(); diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp index fc7e8a022..ede49cf92 100644 --- a/Source/Engine/Engine/Screen.cpp +++ b/Source/Engine/Engine/Screen.cpp @@ -22,7 +22,7 @@ class ScreenService : public EngineService { public: ScreenService() - : EngineService(TEXT("Screen"), 120) + : EngineService(TEXT("Screen"), 500) { } diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index 82765f151..898139ce7 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -634,15 +634,12 @@ void Foliage::RemoveFoliageType(int32 index) int32 Foliage::GetFoliageTypeInstancesCount(int32 index) const { PROFILE_CPU(); - int32 result = 0; - - for (auto i = Instances.Begin(); i.IsNotEnd(); i++) + for (auto i = Instances.Begin(); i.IsNotEnd(); ++i) { if (i->Type == index) result++; } - return result; } diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index 019cfad5a..34d8e7661 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -503,6 +503,9 @@ void GPUDevice::DrawEnd() // Call present on all used tasks int32 presentCount = 0; bool anyVSync = false; +#if COMPILE_WITH_PROFILER + const double presentStart = Platform::GetTimeSeconds(); +#endif for (int32 i = 0; i < RenderTask::Tasks.Count(); i++) { const auto task = RenderTask::Tasks[i]; @@ -537,6 +540,10 @@ void GPUDevice::DrawEnd() #endif GetMainContext()->Flush(); } +#if COMPILE_WITH_PROFILER + const double presentEnd = Platform::GetTimeSeconds(); + ProfilerGPU::OnPresentTime((float)((presentEnd - presentStart) * 1000.0)); +#endif _wasVSyncUsed = anyVSync; _isRendering = false; diff --git a/Source/Engine/Graphics/GPULimits.h b/Source/Engine/Graphics/GPULimits.h index 11da5aca3..0cae4fdfd 100644 --- a/Source/Engine/Graphics/GPULimits.h +++ b/Source/Engine/Graphics/GPULimits.h @@ -259,6 +259,11 @@ API_STRUCT() struct GPULimits /// API_FIELD() bool HasDepthAsSRV; + /// + /// True if device supports depth buffer clipping (see GPUPipelineState::Description::DepthClipEnable). + /// + API_FIELD() bool HasDepthClip; + /// /// True if device supports depth buffer texture as a readonly depth buffer (can be sampled in the shader while performing depth-test). /// diff --git a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp index 9ba2975b2..36dfc4615 100644 --- a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp @@ -92,6 +92,8 @@ bool DecalMaterialShader::Load() { GPUPipelineState::Description psDesc0 = GPUPipelineState::Description::DefaultNoDepth; psDesc0.VS = _shader->GetVS("VS_Decal"); + if (psDesc0.VS == nullptr) + return true; psDesc0.PS = _shader->GetPS("PS_Decal"); psDesc0.CullMode = CullMode::Normal; diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp index 52a0bd551..a899dbc12 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp @@ -136,6 +136,7 @@ void DeferredMaterialShader::Unload() bool DeferredMaterialShader::Load() { + bool failed = false; auto psDesc = GPUPipelineState::Description::Default; psDesc.DepthWriteEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthWrite) == MaterialFeaturesFlags::None; if (EnumHasAnyFlags(_info.FeaturesFlags, MaterialFeaturesFlags::DisableDepthTest)) @@ -155,16 +156,20 @@ bool DeferredMaterialShader::Load() // GBuffer Pass psDesc.VS = _shader->GetVS("VS"); + failed |= psDesc.VS == nullptr; psDesc.PS = _shader->GetPS("PS_GBuffer"); _cache.Default.Init(psDesc); psDesc.VS = _shader->GetVS("VS", 1); + failed |= psDesc.VS == nullptr; _cacheInstanced.Default.Init(psDesc); // GBuffer Pass with lightmap (pixel shader permutation for USE_LIGHTMAP=1) psDesc.VS = _shader->GetVS("VS"); + failed |= psDesc.VS == nullptr; psDesc.PS = _shader->GetPS("PS_GBuffer", 1); _cache.DefaultLightmap.Init(psDesc); psDesc.VS = _shader->GetVS("VS", 1); + failed |= psDesc.VS == nullptr; _cacheInstanced.DefaultLightmap.Init(psDesc); // GBuffer Pass with skinning @@ -233,5 +238,5 @@ bool DeferredMaterialShader::Load() psDesc.VS = _shader->GetVS("VS_Skinned"); _cache.DepthSkinned.Init(psDesc); - return false; + return failed; } diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp index d391f3295..9fb532b4e 100644 --- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp @@ -174,6 +174,8 @@ bool ForwardMaterialShader::Load() // Forward Pass psDesc.VS = _shader->GetVS("VS"); + if (psDesc.VS == nullptr) + return true; psDesc.PS = _shader->GetPS("PS_Forward"); psDesc.DepthWriteEnable = false; psDesc.BlendMode = BlendingMode::AlphaBlend; diff --git a/Source/Engine/Graphics/Mesh.cs b/Source/Engine/Graphics/Mesh.cs index 0a986d44d..68ff7e07f 100644 --- a/Source/Engine/Graphics/Mesh.cs +++ b/Source/Engine/Graphics/Mesh.cs @@ -339,7 +339,7 @@ namespace FlaxEngine /// The normal vectors (per vertex). Use null to compute them from normal vectors. /// The texture coordinates (per vertex). /// The vertex colors (per vertex). - [Obsolete("Deprecated in 1.4")] + [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")] public void UpdateMesh(Vector3[] vertices, int[] triangles, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null, Color32[] colors = null) { UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors); @@ -357,7 +357,7 @@ namespace FlaxEngine /// The normal vectors (per vertex). Use null to compute them from normal vectors. /// The texture coordinates (per vertex). /// The vertex colors (per vertex). - [Obsolete("Deprecated in 1.4")] + [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")] public void UpdateMesh(List vertices, List triangles, List normals = null, List tangents = null, List uv = null, List colors = null) { UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors); @@ -375,7 +375,7 @@ namespace FlaxEngine /// The normal vectors (per vertex). Use null to compute them from normal vectors. /// The texture coordinates (per vertex). /// The vertex colors (per vertex). - [Obsolete("Deprecated in 1.4")] + [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")] public void UpdateMesh(Vector3[] vertices, uint[] triangles, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null, Color32[] colors = null) { UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors); @@ -393,7 +393,7 @@ namespace FlaxEngine /// The normal vectors (per vertex). Use null to compute them from normal vectors. /// The texture coordinates (per vertex). /// The vertex colors (per vertex). - [Obsolete("Deprecated in 1.4")] + [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")] public void UpdateMesh(List vertices, List triangles, List normals = null, List tangents = null, List uv = null, List colors = null) { UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors); @@ -411,7 +411,7 @@ namespace FlaxEngine /// The tangent vectors (per vertex). Use null to compute them from normal vectors. /// The texture coordinates (per vertex). /// The vertex colors (per vertex). - [Obsolete("Deprecated in 1.4")] + [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")] public void UpdateMesh(Vector3[] vertices, ushort[] triangles, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null, Color32[] colors = null) { UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors); @@ -429,7 +429,7 @@ namespace FlaxEngine /// The tangent vectors (per vertex). Use null to compute them from normal vectors. /// The texture coordinates (per vertex). /// The vertex colors (per vertex). - [Obsolete("Deprecated in 1.4")] + [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")] public void UpdateMesh(List vertices, List triangles, List normals = null, List tangents = null, List uv = null, List colors = null) { UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors); diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index f03c013c6..1e531d0e0 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -609,7 +609,7 @@ bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& c ScopeLock lock(model->Locker); if (model->IsVirtual()) { - LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download"); + LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download."); return true; } diff --git a/Source/Engine/Graphics/Models/ModelData.Tool.cpp b/Source/Engine/Graphics/Models/ModelData.Tool.cpp index d061d0745..727fe7754 100644 --- a/Source/Engine/Graphics/Models/ModelData.Tool.cpp +++ b/Source/Engine/Graphics/Models/ModelData.Tool.cpp @@ -318,7 +318,9 @@ void MeshData::BuildIndexBuffer() } const auto endTime = Platform::GetTimeSeconds(); - LOG(Info, "Generated index buffer for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), Positions.Count(), Indices.Count()); + const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime); + if (time > 0.5f) // Don't log if generation was fast enough + LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, Indices.Count(), TEXT("indices")); } void MeshData::FindPositions(const Float3& position, float epsilon, Array& result) @@ -449,7 +451,9 @@ bool MeshData::GenerateNormals(float smoothingAngle) } const auto endTime = Platform::GetTimeSeconds(); - LOG(Info, "Generated tangents for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), vertexCount, indexCount); + const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime); + if (time > 0.5f) // Don't log if generation was fast enough + LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, indexCount, TEXT("normals")); return false; } @@ -685,7 +689,10 @@ bool MeshData::GenerateTangents(float smoothingAngle) #endif const auto endTime = Platform::GetTimeSeconds(); - LOG(Info, "Generated tangents for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), vertexCount, indexCount); + const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime); + if (time > 0.5f) // Don't log if generation was fast enough + LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, indexCount, TEXT("tangents")); + return false; } @@ -872,7 +879,9 @@ void MeshData::ImproveCacheLocality() Allocator::Free(piCandidates); const auto endTime = Platform::GetTimeSeconds(); - LOG(Info, "Cache relevant optimize for {0} vertices and {1} indices. Average output ACMR is {2}. Time: {3}s", vertexCount, indexCount, (float)iCacheMisses / indexCount / 3, Utilities::RoundTo2DecimalPlaces(endTime - startTime)); + const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime); + if (time > 0.5f) // Don't log if generation was fast enough + LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, indexCount, TEXT("optimized indices")); } float MeshData::CalculateTrianglesArea() const diff --git a/Source/Engine/Graphics/SkinnedMesh.cs b/Source/Engine/Graphics/SkinnedMesh.cs index 8fb7a83dc..a7b7594bb 100644 --- a/Source/Engine/Graphics/SkinnedMesh.cs +++ b/Source/Engine/Graphics/SkinnedMesh.cs @@ -216,7 +216,7 @@ namespace FlaxEngine /// The normal vectors (per vertex). /// The normal vectors (per vertex). Use null to compute them from normal vectors. /// The texture coordinates (per vertex). - [Obsolete("Deprecated in 1.4")] + [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")] public void UpdateMesh(Vector3[] vertices, int[] triangles, Int4[] blendIndices, Vector4[] blendWeights, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null) { UpdateMesh(Utils.ConvertCollection(vertices), triangles, blendIndices, Utils.ConvertCollection(blendWeights), Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv)); @@ -235,7 +235,7 @@ namespace FlaxEngine /// The normal vectors (per vertex). /// The normal vectors (per vertex). Use null to compute them from normal vectors. /// The texture coordinates (per vertex). - [Obsolete("Deprecated in 1.4")] + [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")] public void UpdateMesh(Vector3[] vertices, uint[] triangles, Int4[] blendIndices, Vector4[] blendWeights, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null) { UpdateMesh(Utils.ConvertCollection(vertices), triangles, blendIndices, Utils.ConvertCollection(blendWeights), Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv)); @@ -254,7 +254,7 @@ namespace FlaxEngine /// The normal vectors (per vertex). /// The tangent vectors (per vertex). Use null to compute them from normal vectors. /// The texture coordinates (per vertex). - [Obsolete("Deprecated in 1.4")] + [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")] public void UpdateMesh(Vector3[] vertices, ushort[] triangles, Int4[] blendIndices, Vector4[] blendWeights, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null) { UpdateMesh(Utils.ConvertCollection(vertices), triangles, blendIndices, Utils.ConvertCollection(blendWeights), Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv)); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index c29c6254d..026422799 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -359,6 +359,7 @@ bool GPUDeviceDX11::Init() limits.HasAppendConsumeBuffers = true; limits.HasSeparateRenderTargetBlendState = true; limits.HasDepthAsSRV = true; + limits.HasDepthClip = true; limits.HasReadOnlyDepth = true; limits.HasMultisampleDepthAsSRV = true; limits.HasTypedUAVLoad = featureDataD3D11Options2.TypedUAVLoadAdditionalFormats != 0; @@ -382,6 +383,7 @@ bool GPUDeviceDX11::Init() limits.HasAppendConsumeBuffers = false; limits.HasSeparateRenderTargetBlendState = false; limits.HasDepthAsSRV = false; + limits.HasDepthClip = true; limits.HasReadOnlyDepth = createdFeatureLevel == D3D_FEATURE_LEVEL_10_1; limits.HasMultisampleDepthAsSRV = false; limits.HasTypedUAVLoad = false; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp index e2266f551..d9dc54f97 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp @@ -381,6 +381,7 @@ bool GPUDeviceDX12::Init() limits.HasAppendConsumeBuffers = true; limits.HasSeparateRenderTargetBlendState = true; limits.HasDepthAsSRV = true; + limits.HasDepthClip = true; limits.HasReadOnlyDepth = true; limits.HasMultisampleDepthAsSRV = true; limits.HasTypedUAVLoad = options.TypedUAVLoadAdditionalFormats != 0; diff --git a/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp b/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp index 7869ef312..fa390ccab 100644 --- a/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp +++ b/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp @@ -50,18 +50,7 @@ bool GPUDeviceNull::Init() // Init device limits { auto& limits = Limits; - limits.HasCompute = false; - limits.HasTessellation = false; - limits.HasGeometryShaders = false; - limits.HasInstancing = false; - limits.HasVolumeTextureRendering = false; - limits.HasDrawIndirect = false; - limits.HasAppendConsumeBuffers = false; - limits.HasSeparateRenderTargetBlendState = false; - limits.HasDepthAsSRV = false; - limits.HasReadOnlyDepth = false; - limits.HasMultisampleDepthAsSRV = false; - limits.HasTypedUAVLoad = false; + Platform::MemoryClear(&limits, sizeof(limits)); limits.MaximumMipLevelsCount = 14; limits.MaximumTexture1DSize = 8192; limits.MaximumTexture1DArraySize = 512; @@ -70,11 +59,8 @@ bool GPUDeviceNull::Init() limits.MaximumTexture3DSize = 2048; limits.MaximumTextureCubeSize = 16384; limits.MaximumSamplerAnisotropy = 1; - for (int32 i = 0; i < static_cast(PixelFormat::MAX); i++) - { FeaturesPerFormat[i] = FormatFeatures(static_cast(i), MSAALevel::None, FormatSupport::None); - } } // Create main context diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index d758ec999..79e882f6d 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -1210,16 +1210,16 @@ void GPUContextVulkan::ResolveMultisample(GPUTexture* sourceMultisampleTexture, void GPUContextVulkan::DrawInstanced(uint32 verticesCount, uint32 instanceCount, int32 startInstance, int32 startVertex) { - const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); OnDrawCall(); + const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); vkCmdDraw(cmdBuffer->GetHandle(), verticesCount, instanceCount, startVertex, startInstance); RENDER_STAT_DRAW_CALL(verticesCount * instanceCount, verticesCount * instanceCount / 3); } void GPUContextVulkan::DrawIndexedInstanced(uint32 indicesCount, uint32 instanceCount, int32 startInstance, int32 startVertex, int32 startIndex) { - const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); OnDrawCall(); + const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); vkCmdDrawIndexed(cmdBuffer->GetHandle(), indicesCount, instanceCount, startIndex, startVertex, startInstance); RENDER_STAT_DRAW_CALL(0, indicesCount / 3 * instanceCount); } @@ -1227,10 +1227,9 @@ void GPUContextVulkan::DrawIndexedInstanced(uint32 indicesCount, uint32 instance void GPUContextVulkan::DrawInstancedIndirect(GPUBuffer* bufferForArgs, uint32 offsetForArgs) { ASSERT(bufferForArgs && EnumHasAnyFlags(bufferForArgs->GetFlags(), GPUBufferFlags::Argument)); - + OnDrawCall(); auto bufferForArgsVK = (GPUBufferVulkan*)bufferForArgs; const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); - OnDrawCall(); vkCmdDrawIndirect(cmdBuffer->GetHandle(), bufferForArgsVK->GetHandle(), (VkDeviceSize)offsetForArgs, 1, sizeof(VkDrawIndirectCommand)); RENDER_STAT_DRAW_CALL(0, 0); } @@ -1238,10 +1237,9 @@ void GPUContextVulkan::DrawInstancedIndirect(GPUBuffer* bufferForArgs, uint32 of void GPUContextVulkan::DrawIndexedInstancedIndirect(GPUBuffer* bufferForArgs, uint32 offsetForArgs) { ASSERT(bufferForArgs && EnumHasAnyFlags(bufferForArgs->GetFlags(), GPUBufferFlags::Argument)); - + OnDrawCall(); auto bufferForArgsVK = (GPUBufferVulkan*)bufferForArgs; const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); - OnDrawCall(); vkCmdDrawIndexedIndirect(cmdBuffer->GetHandle(), bufferForArgsVK->GetHandle(), (VkDeviceSize)offsetForArgs, 1, sizeof(VkDrawIndexedIndirectCommand)); RENDER_STAT_DRAW_CALL(0, 0); } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index b4a6112a9..9c553060f 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -1704,6 +1704,7 @@ bool GPUDeviceVulkan::Init() limits.HasDrawIndirect = PhysicalDeviceLimits.maxDrawIndirectCount >= 1; limits.HasAppendConsumeBuffers = false; // TODO: add Append Consume buffers support for Vulkan limits.HasSeparateRenderTargetBlendState = true; + limits.HasDepthClip = PhysicalDeviceFeatures.depthClamp; limits.HasDepthAsSRV = true; limits.HasReadOnlyDepth = true; limits.HasMultisampleDepthAsSRV = !!PhysicalDeviceFeatures.sampleRateShading; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp index 136a7c6e5..c5c1970e0 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp @@ -340,7 +340,7 @@ bool GPUPipelineStateVulkan::Init(const Description& desc) break; } _descRasterization.frontFace = VK_FRONT_FACE_CLOCKWISE; - _descRasterization.depthClampEnable = !desc.DepthClipEnable; + _descRasterization.depthClampEnable = !desc.DepthClipEnable && _device->Limits.HasDepthClip; _descRasterization.lineWidth = 1.0f; _desc.pRasterizationState = &_descRasterization; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.cpp index 9c58b77b0..cce86799a 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.cpp @@ -16,7 +16,7 @@ void GPUTimerQueryVulkan::Interrupt(CmdBufferVulkan* cmdBuffer) if (!_interrupted) { _interrupted = true; - WriteTimestamp(cmdBuffer, _queries[_queryIndex].End); + WriteTimestamp(cmdBuffer, _queries[_queryIndex].End, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); } } @@ -28,7 +28,7 @@ void GPUTimerQueryVulkan::Resume(CmdBufferVulkan* cmdBuffer) e.End.Pool = nullptr; _interrupted = false; - WriteTimestamp(cmdBuffer, e.Begin); + WriteTimestamp(cmdBuffer, e.Begin, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); _queries.Add(e); _queryIndex++; @@ -56,13 +56,13 @@ bool GPUTimerQueryVulkan::GetResult(Query& query) return false; } -void GPUTimerQueryVulkan::WriteTimestamp(CmdBufferVulkan* cmdBuffer, Query& query) const +void GPUTimerQueryVulkan::WriteTimestamp(CmdBufferVulkan* cmdBuffer, Query& query, VkPipelineStageFlagBits stage) const { auto pool = _device->FindAvailableTimestampQueryPool(); uint32 index; pool->AcquireQuery(index); - vkCmdWriteTimestamp(cmdBuffer->GetHandle(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, pool->GetHandle(), index); + vkCmdWriteTimestamp(cmdBuffer->GetHandle(), stage, pool->GetHandle(), index); pool->MarkQueryAsStarted(index); query.Pool = pool; @@ -168,7 +168,7 @@ void GPUTimerQueryVulkan::Begin() _queryIndex = 0; _interrupted = false; - WriteTimestamp(cmdBuffer, e.Begin); + WriteTimestamp(cmdBuffer, e.Begin, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); context->GetCmdBufferManager()->OnQueryBegin(this); ASSERT(_queries.IsEmpty()); @@ -193,7 +193,7 @@ void GPUTimerQueryVulkan::End() if (!_interrupted) { - WriteTimestamp(cmdBuffer, _queries[_queryIndex].End); + WriteTimestamp(cmdBuffer, _queries[_queryIndex].End, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); } context->GetCmdBufferManager()->OnQueryEnd(this); } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.h index b081b946d..ddf461b13 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.h @@ -59,7 +59,7 @@ public: private: bool GetResult(Query& query); - void WriteTimestamp(CmdBufferVulkan* cmdBuffer, Query& query) const; + void WriteTimestamp(CmdBufferVulkan* cmdBuffer, Query& query, VkPipelineStageFlagBits stage) const; bool TryGetResult(); bool UseQueries(); diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 459e1fb6b..88f7e6876 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -706,13 +706,12 @@ void AnimatedModel::UpdateBounds() const int32 bonesCount = skeleton.Bones.Count(); for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) box.Merge(_transform.LocalToWorld(GraphInstance.NodesPose[skeleton.Bones.Get()[boneIndex].NodeIndex].GetTranslation())); - _box = box; } // Apply margin based on model dimensions const Vector3 modelBoxSize = modelBox.GetSize(); - const Vector3 center = _box.GetCenter(); - const Vector3 sizeHalf = Vector3::Max(_box.GetSize() + modelBoxSize * 0.2f, modelBoxSize) * 0.5f; + const Vector3 center = box.GetCenter(); + const Vector3 sizeHalf = Vector3::Max(box.GetSize() + modelBoxSize * 0.2f, modelBoxSize) * 0.5f; _box = BoundingBox(center - sizeHalf, center + sizeHalf); } else diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index d070d99a2..0c5c4d73b 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -12,7 +12,7 @@ /// /// Performs an animation and renders a skinned model. /// -API_CLASS(Attributes="ActorContextMenu(\"New/Other/Animated Model\"), ActorToolbox(\"Other\")") +API_CLASS(Attributes="ActorContextMenu(\"New/Other/Animated Model\"), ActorToolbox(\"Visuals\")") class FLAXENGINE_API AnimatedModel : public ModelInstanceActor { DECLARE_SCENE_OBJECT(AnimatedModel); diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h index 2e932096e..2f064eb8a 100644 --- a/Source/Engine/Level/Actors/StaticModel.h +++ b/Source/Engine/Level/Actors/StaticModel.h @@ -10,7 +10,8 @@ /// /// Renders model on the screen. /// -API_CLASS(Attributes="ActorContextMenu(\"New/Model\")") class FLAXENGINE_API StaticModel : public ModelInstanceActor +API_CLASS(Attributes="ActorContextMenu(\"New/Model\"), ActorToolbox(\"Visuals\")") +class FLAXENGINE_API StaticModel : public ModelInstanceActor { DECLARE_SCENE_OBJECT(StaticModel); private: diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 83b24c841..dfd5786c6 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -132,7 +132,7 @@ class LevelService : public EngineService { public: LevelService() - : EngineService(TEXT("Scene Manager"), 30) + : EngineService(TEXT("Scene Manager"), 200) { } @@ -1218,10 +1218,9 @@ bool LevelImpl::saveScene(Scene* scene, rapidjson_flax::StringBuffer& outBuffer, // Json resource data writer.JKEY("Data"); writer.StartArray(); + SceneObject** objects = allObjects.Get(); for (int32 i = 0; i < allObjects.Count(); i++) - { - writer.SceneObject(allObjects[i]); - } + writer.SceneObject(objects[i]); writer.EndArray(); } writer.EndObject(); diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp index 123013064..cd9d893ec 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.cpp +++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp @@ -29,7 +29,7 @@ class PrefabManagerService : public EngineService { public: PrefabManagerService() - : EngineService(TEXT("Prefab Manager"), 110) + : EngineService(TEXT("Prefab Manager")) { } }; diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 7f62d943b..5632d8bc7 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -14,6 +14,9 @@ #include "Engine/Serialization/JsonWriters.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Threading/ThreadLocal.h" +#if !BUILD_RELEASE || USE_EDITOR +#include "Engine/Level/Level.h" +#endif SceneObjectsFactory::Context::Context(ISerializeModifier* modifier) : Modifier(modifier) @@ -254,6 +257,10 @@ void SceneObjectsFactory::Deserialize(Context& context, SceneObject* obj, ISeria void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::DeserializeStream& value) { +#if !BUILD_RELEASE || USE_EDITOR + // Prevent race-conditions when logging missing objects (especially when adding dummy MissingScript) + ScopeLock lock(Level::ScenesLock); + // Print invalid object data contents rapidjson_flax::StringBuffer buffer; PrettyJsonWriter writer(buffer); @@ -280,6 +287,7 @@ void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable:: LOG(Warning, "Parent actor of the missing object: {0}", parent->GetName()); } } +#endif } Actor* SceneObjectsFactory::CreateActor(int32 typeId, const Guid& id) @@ -361,14 +369,14 @@ SceneObjectsFactory::PrefabSyncData::PrefabSyncData(Array& sceneOb { } -void SceneObjectsFactory::SetupPrefabInstances(Context& context, PrefabSyncData& data) +void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyncData& data) { PROFILE_CPU_NAMED("SetupPrefabInstances"); const int32 count = data.Data.Size(); ASSERT(count <= data.SceneObjects.Count()); for (int32 i = 0; i < count; i++) { - SceneObject* obj = data.SceneObjects[i]; + const SceneObject* obj = data.SceneObjects[i]; if (!obj) continue; const auto& stream = data.Data[i]; @@ -409,6 +417,21 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, PrefabSyncData& // Add to the prefab instance IDs mapping auto& prefabInstance = context.Instances[index]; prefabInstance.IdsMapping[prefabObjectId] = id; + + // Walk over nested prefabs to link any subobjects into this object (eg. if nested prefab uses cross-object references to link them correctly) + NESTED_PREFAB_WALK: + const ISerializable::DeserializeStream* prefabData; + if (prefab->ObjectsDataCache.TryGet(prefabObjectId, prefabData) && JsonTools::GetGuidIfValid(prefabObjectId, *prefabData, "PrefabObjectID")) + { + prefabId = JsonTools::GetGuid(stream, "PrefabID"); + prefab = Content::LoadAsync(prefabId); + if (prefab && !prefab->WaitForLoaded()) + { + // Map prefab object ID to the deserialized instance ID + prefabInstance.IdsMapping[prefabObjectId] = id; + goto NESTED_PREFAB_WALK; + } + } } } diff --git a/Source/Engine/Level/SceneObjectsFactory.h b/Source/Engine/Level/SceneObjectsFactory.h index f7f495449..97cf6b9ce 100644 --- a/Source/Engine/Level/SceneObjectsFactory.h +++ b/Source/Engine/Level/SceneObjectsFactory.h @@ -100,7 +100,7 @@ public: /// /// The serialization context. /// The sync data. - static void SetupPrefabInstances(Context& context, PrefabSyncData& data); + static void SetupPrefabInstances(Context& context, const PrefabSyncData& data); /// /// Synchronizes the new prefab instances by spawning missing objects that were added to prefab but were not saved with scene objects collection. diff --git a/Source/Engine/Navigation/NavCrowd.cpp b/Source/Engine/Navigation/NavCrowd.cpp index 80055c3a4..b05f0f7d3 100644 --- a/Source/Engine/Navigation/NavCrowd.cpp +++ b/Source/Engine/Navigation/NavCrowd.cpp @@ -3,6 +3,9 @@ #include "NavCrowd.h" #include "NavMesh.h" #include "NavMeshRuntime.h" +#include "Engine/Core/Log.h" +#include "Engine/Level/Level.h" +#include "Engine/Level/Scene/Scene.h" #include "Engine/Profiler/ProfilerCPU.h" #include @@ -26,6 +29,15 @@ bool NavCrowd::Init(float maxAgentRadius, int32 maxAgents, NavMesh* navMesh) bool NavCrowd::Init(const NavAgentProperties& agentProperties, int32 maxAgents) { NavMeshRuntime* navMeshRuntime = NavMeshRuntime::Get(agentProperties); +#if !BUILD_RELEASE + if (!navMeshRuntime) + { + if (NavMeshRuntime::Get()) + LOG(Error, "Cannot create crowd. Failed to find a navmesh that matches a given agent properties."); + else + LOG(Error, "Cannot create crowd. No navmesh is loaded."); + } +#endif return Init(agentProperties.Radius * 3.0f, maxAgents, navMeshRuntime); } @@ -33,6 +45,41 @@ bool NavCrowd::Init(float maxAgentRadius, int32 maxAgents, NavMeshRuntime* navMe { if (!_crowd || !navMesh) return true; + + // This can happen on game start when no navmesh is loaded yet (eg. navmesh tiles data is during streaming) so wait for navmesh + if (navMesh->GetNavMesh() == nullptr) + { + PROFILE_CPU_NAMED("WaitForNavMesh"); + if (IsInMainThread()) + { + // Wait for any navmesh data load + for (const Scene* scene : Level::Scenes) + { + for (const NavMesh* actor : scene->Navigation.Meshes) + { + if (actor->DataAsset) + { + actor->DataAsset->WaitForLoaded(); + if (navMesh->GetNavMesh()) + break; + } + } + if (navMesh->GetNavMesh()) + break; + } + } + else + { + while (navMesh->GetNavMesh() == nullptr) + Platform::Sleep(1); + } + if (navMesh->GetNavMesh() == nullptr) + { + LOG(Error, "Cannot create crowd. Navmesh is not yet laoded."); + return true; + } + } + return !_crowd->init(maxAgents, maxAgentRadius, navMesh->GetNavMesh()); } @@ -48,7 +95,7 @@ Vector3 NavCrowd::GetAgentPosition(int32 id) const { Vector3 result = Vector3::Zero; const dtCrowdAgent* agent = _crowd->getAgent(id); - if (agent && agent->state != DT_CROWDAGENT_STATE_INVALID) + if (agent) { result = Float3(agent->npos); } @@ -59,7 +106,7 @@ Vector3 NavCrowd::GetAgentVelocity(int32 id) const { Vector3 result = Vector3::Zero; const dtCrowdAgent* agent = _crowd->getAgent(id); - if (agent && agent->state != DT_CROWDAGENT_STATE_INVALID) + if (agent) { result = Float3(agent->vel); } diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 4d85238e3..1e9ea5b79 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -258,18 +258,32 @@ void NetworkReplicationService::Dispose() NetworkReplicationService NetworkReplicationServiceInstance; -void INetworkSerializable_Serialize(void* instance, NetworkStream* stream, void* tag) +void INetworkSerializable_Native_Serialize(void* instance, NetworkStream* stream, void* tag) { const int16 vtableOffset = (int16)(intptr)tag; ((INetworkSerializable*)((byte*)instance + vtableOffset))->Serialize(stream); } -void INetworkSerializable_Deserialize(void* instance, NetworkStream* stream, void* tag) +void INetworkSerializable_Native_Deserialize(void* instance, NetworkStream* stream, void* tag) { const int16 vtableOffset = (int16)(intptr)tag; ((INetworkSerializable*)((byte*)instance + vtableOffset))->Deserialize(stream); } +void INetworkSerializable_Script_Serialize(void* instance, NetworkStream* stream, void* tag) +{ + auto obj = (ScriptingObject*)instance; + auto interface = ScriptingObject::ToInterface(obj); + interface->Serialize(stream); +} + +void INetworkSerializable_Script_Deserialize(void* instance, NetworkStream* stream, void* tag) +{ + auto obj = (ScriptingObject*)instance; + auto interface = ScriptingObject::ToInterface(obj); + interface->Deserialize(stream); +} + NetworkReplicatedObject* ResolveObject(Guid objectId) { auto it = Objects.Find(objectId); @@ -1064,9 +1078,21 @@ bool NetworkReplicator::InvokeSerializer(const ScriptingTypeHandle& typeHandle, const ScriptingType::InterfaceImplementation* interface = type.GetInterface(INetworkSerializable::TypeInitializer); if (interface) { - serializer.Methods[0] = INetworkSerializable_Serialize; - serializer.Methods[1] = INetworkSerializable_Deserialize; - serializer.Tags[0] = serializer.Tags[1] = (void*)(intptr)interface->VTableOffset; // Pass VTableOffset to the callback + if (interface->IsNative) + { + // Native interface (implemented in C++) + serializer.Methods[0] = INetworkSerializable_Native_Serialize; + serializer.Methods[1] = INetworkSerializable_Native_Deserialize; + serializer.Tags[0] = serializer.Tags[1] = (void*)(intptr)interface->VTableOffset; // Pass VTableOffset to the callback + } + else + { + // Generic interface (implemented in C# or elsewhere) + ASSERT(type.Type == ScriptingTypes::Script); + serializer.Methods[0] = INetworkSerializable_Script_Serialize; + serializer.Methods[1] = INetworkSerializable_Script_Deserialize; + serializer.Tags[0] = serializer.Tags[1] = nullptr; + } SerializersTable.Add(typeHandle, serializer); } else if (const ScriptingTypeHandle baseTypeHandle = typeHandle.GetType().GetBaseType()) diff --git a/Source/Engine/Online/Online.cpp b/Source/Engine/Online/Online.cpp index 313497f98..bb79c6d60 100644 --- a/Source/Engine/Online/Online.cpp +++ b/Source/Engine/Online/Online.cpp @@ -10,7 +10,7 @@ class OnlineService : public EngineService { public: OnlineService() - : EngineService(TEXT("Online"), 100) + : EngineService(TEXT("Online"), 500) { } diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp index 9d91d0013..cb2d7004e 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp @@ -7,7 +7,7 @@ #include "Engine/Graphics/RenderTask.h" #define GET_VIEW() auto mainViewTask = MainRenderTask::Instance && MainRenderTask::Instance->LastUsedFrame != 0 ? MainRenderTask::Instance : nullptr -#define ACCESS_PARTICLE_ATTRIBUTE(index) (context.Data->Buffer->GetParticleCPU(context.ParticleIndex) + context.Data->Buffer->Layout->Attributes[node->Attributes[index]].Offset) +#define ACCESS_PARTICLE_ATTRIBUTE(index) (context.Data->Buffer->GetParticleCPU(context.ParticleIndex) + context.Data->Buffer->Layout->Attributes[context.AttributesRemappingTable[node->Attributes[index]]].Offset) #define GET_PARTICLE_ATTRIBUTE(index, type) *(type*)ACCESS_PARTICLE_ATTRIBUTE(index) void ParticleEmitterGraphCPUExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value) @@ -436,9 +436,19 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node auto* functionOutputNode = &graph->Nodes[function->Outputs[outputIndex]]; Box* functionOutputBox = functionOutputNode->TryGetBox(0); + // Setup particle attributes remapping (so particle data access nodes inside the function will read data at proper offset, see macro ACCESS_PARTICLE_ATTRIBUTE) + byte attributesRemappingTable[PARTICLE_ATTRIBUTES_MAX_COUNT]; + Platform::MemoryCopy(attributesRemappingTable, context.AttributesRemappingTable, sizeof(attributesRemappingTable)); + for (int32 i = 0; i < graph->Layout.Attributes.Count(); i++) + { + const ParticleAttribute& e = graph->Layout.Attributes[i]; + context.AttributesRemappingTable[i] = context.Data->Buffer->Layout->FindAttribute(e.Name, e.ValueType); + } + // Evaluate the function output context.GraphStack.Push(graph); value = functionOutputBox && functionOutputBox->HasConnection() ? eatBox(nodeBase, functionOutputBox->FirstConnection()) : Value::Zero; + Platform::MemoryCopy(context.AttributesRemappingTable, attributesRemappingTable, sizeof(attributesRemappingTable)); context.GraphStack.Pop(); break; } diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp index d86bc6d54..a8f7898e1 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp @@ -133,6 +133,8 @@ void ParticleEmitterGraphCPUExecutor::Init(ParticleEmitter* emitter, ParticleEff context.ViewTask = effect->GetRenderTask(); context.CallStackSize = 0; context.Functions.Clear(); + for (int32 i = 0; i < PARTICLE_ATTRIBUTES_MAX_COUNT; i++) + context.AttributesRemappingTable[i] = i; } bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, BoundingBox& result) diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h index 9d1d48c81..4f55da6e2 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h @@ -119,6 +119,7 @@ struct ParticleEmitterGraphCPUContext class SceneRenderTask* ViewTask; Array> GraphStack; Dictionary Functions; + byte AttributesRemappingTable[PARTICLE_ATTRIBUTES_MAX_COUNT]; // Maps node attribute indices to the current particle layout (used to support accessing particle data from function graph which has different layout). int32 CallStackSize = 0; VisjectExecutor::Node* CallStack[PARTICLE_EMITTER_MAX_CALL_STACK]; }; diff --git a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp index 32f6b1e23..57ffa251a 100644 --- a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp +++ b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp @@ -6,6 +6,7 @@ #include "Engine/Particles/ParticleEmitter.h" #include "Engine/Particles/ParticleEffect.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/GPUContext.h" @@ -168,7 +169,7 @@ void GPUParticles::Execute(GPUContext* context, ParticleEmitter* emitter, Partic if (viewTask) { bindMeta.Buffers = viewTask->Buffers; - bindMeta.CanSampleDepth = bindMeta.CanSampleGBuffer = true; + bindMeta.CanSampleDepth = bindMeta.CanSampleGBuffer = bindMeta.Buffers && bindMeta.Buffers->GetWidth() != 0; } else { @@ -179,7 +180,7 @@ void GPUParticles::Execute(GPUContext* context, ParticleEmitter* emitter, Partic for (int32 i = 0; i < data.Parameters.Count(); i++) { // Copy instance parameters values - _params[i].SetValue(data.Parameters[i]); + _params[i].SetValue(data.Parameters.Get()[i]); } MaterialParamsLink link; link.This = &_params; diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp index de142827c..22512481b 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp @@ -40,7 +40,7 @@ namespace ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAttribute(Node* caller, const StringView& name, ParticleAttribute::ValueTypes valueType, AccessMode mode) { // Find this attribute - return AccessParticleAttribute(caller, ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.FindAttribute(name, valueType), mode); + return AccessParticleAttribute(caller, GetRootGraph()->Layout.FindAttribute(name, valueType), mode); } ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAttribute(Node* caller, int32 index, AccessMode mode) @@ -55,7 +55,7 @@ ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAt if (value.Variable.Type != VariantType::Null) return value.Variable; - auto& attribute = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.Attributes[index]; + auto& attribute = GetRootGraph()->Layout.Attributes[index]; const VariantType::Types type = GetValueType(attribute.ValueType); // Generate local variable name that matches the attribute name for easier shader source debugging @@ -77,7 +77,7 @@ ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAt else if (_contextType == ParticleContextType::Initialize) { // Initialize with default value - const Value defaultValue(((ParticleEmitterGraphGPU*)_graphStack.Peek())->AttributesDefaults[index]); + const Value defaultValue(GetRootGraph()->AttributesDefaults[index]); value.Variable = writeLocal(type, defaultValue.Value, caller, localName); } else @@ -251,10 +251,10 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va { const Char* format; auto valueType = static_cast(node->Values[1].AsInt); - const int32 attributeIndex = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.FindAttribute((StringView)node->Values[0], valueType); + const int32 attributeIndex = GetRootGraph()->Layout.FindAttribute((StringView)node->Values[0], valueType); if (attributeIndex == -1) return; - auto& attribute = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.Attributes[attributeIndex]; + auto& attribute = GetRootGraph()->Layout.Attributes[attributeIndex]; const auto particleIndex = Value::Cast(tryGetValue(node->GetBox(1), Value(VariantType::Uint, TEXT("context.ParticleIndex"))), VariantType::Uint); switch (valueType) { diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp index 513252e80..024823293 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp @@ -30,10 +30,10 @@ void ParticleEmitterGraphGPU::ClearCache() { for (int32 i = 0; i < Nodes.Count(); i++) { - auto& node = Nodes[i]; + ParticleEmitterGraphGPUNode& node = Nodes.Get()[i]; for (int32 j = 0; j < node.Boxes.Count(); j++) { - node.Boxes[j].Cache.Clear(); + node.Boxes.Get()[j].Cache.Clear(); } } } @@ -86,9 +86,7 @@ bool ParticleEmitterGPUGenerator::Generate(WriteStream& source, BytesContainer& auto& layout = baseGraph->Layout; _attributeValues.Resize(layout.Attributes.Count()); for (int32 i = 0; i < _attributeValues.Count(); i++) - { _attributeValues[i] = AttributeCache(); - } _contextUsesKill = false; // Cache attributes @@ -112,7 +110,7 @@ bool ParticleEmitterGPUGenerator::Generate(WriteStream& source, BytesContainer& for (int32 i = 0; i < baseGraph->InitModules.Count(); i++) { - ProcessModule(baseGraph->InitModules[i]); + ProcessModule(baseGraph->InitModules.Get()[i]); } WriteParticleAttributesWrites(); @@ -135,7 +133,7 @@ bool ParticleEmitterGPUGenerator::Generate(WriteStream& source, BytesContainer& for (int32 i = 0; i < baseGraph->UpdateModules.Count(); i++) { - ProcessModule(baseGraph->UpdateModules[i]); + ProcessModule(baseGraph->UpdateModules.Get()[i]); } // Dead particles removal @@ -321,22 +319,22 @@ void ParticleEmitterGPUGenerator::clearCache() // Reset cached boxes values for (int32 i = 0; i < _graphs.Count(); i++) { - _graphs[i]->ClearCache(); + _graphs.Get()[i]->ClearCache(); } - for (auto& e : _functions) + for (const auto& e : _functions) { - for (auto& node : e.Value->Nodes) + auto& nodes = ((ParticleEmitterGraphGPU*)e.Value)->Nodes; + for (int32 i = 0; i < nodes.Count(); i++) { + ParticleEmitterGraphGPUNode& node = nodes.Get()[i]; for (int32 j = 0; j < node.Boxes.Count(); j++) - node.Boxes[j].Cache.Clear(); + node.Boxes.Get()[j].Cache.Clear(); } } // Reset cached attributes for (int32 i = 0; i < _attributeValues.Count(); i++) - { - _attributeValues[i] = AttributeCache(); - } + _attributeValues.Get()[i] = AttributeCache(); _contextUsesKill = false; } @@ -344,10 +342,11 @@ void ParticleEmitterGPUGenerator::clearCache() void ParticleEmitterGPUGenerator::WriteParticleAttributesWrites() { bool hadAnyWrite = false; + ParticleEmitterGraphGPU* graph = GetRootGraph(); for (int32 i = 0; i < _attributeValues.Count(); i++) { auto& value = _attributeValues[i]; - auto& attribute = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.Attributes[i]; + auto& attribute = graph->Layout.Attributes[i]; // Skip not used attributes or read-only attributes if (value.Variable.Type == VariantType::Null || ((int)value.Access & (int)AccessMode::Write) == 0) diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h index 82f9c2c93..84834a6e5 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h @@ -94,7 +94,6 @@ public: /// /// Gets the root graph. /// - /// The base graph. FORCE_INLINE ParticleEmitterGraphGPU* GetRootGraph() const { return _graphs.First(); @@ -154,12 +153,12 @@ private: bool IsLocalSimulationSpace() const { - return ((ParticleEmitterGraphGPU*)_graphStack.Peek())->SimulationSpace == ParticlesSimulationSpace::Local; + return GetRootGraph()->SimulationSpace == ParticlesSimulationSpace::Local; } bool IsWorldSimulationSpace() const { - return ((ParticleEmitterGraphGPU*)_graphStack.Peek())->SimulationSpace == ParticlesSimulationSpace::World; + return GetRootGraph()->SimulationSpace == ParticlesSimulationSpace::World; } }; diff --git a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h index f5946c226..91dae0ed2 100644 --- a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h +++ b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h @@ -37,12 +37,12 @@ public: /// /// Flag valid for used particle nodes that need per-particle data to evaluate its value (including dependant nodes linked to input boxes). Used to skip per-particle graph evaluation if graph uses the same value for all particles (eg. is not using per-particle seed or position node). /// - bool UsesParticleData; + bool UsesParticleData = false; /// /// Flag valid for used particle nodes that result in constant data (nothing random nor particle data). /// - bool IsConstant; + bool IsConstant = true; /// /// The cached particle attribute indices used by the simulation graph to access particle properties. @@ -50,6 +50,8 @@ public: int32 Attributes[PARTICLE_EMITTER_MAX_ATTRIBUTES_REFS_PER_NODE]; }; +void InitParticleEmitterFunctionCall(const Guid& assetId, AssetReference& asset, bool& usesParticleData, ParticleLayout& layout); + /// /// The Particle Emitter Graph used to simulate particles. /// @@ -157,8 +159,6 @@ public: if (node->Used) return; node->Used = true; - node->UsesParticleData = false; - node->IsConstant = true; #define USE_ATTRIBUTE(name, valueType, slot) \ { \ @@ -292,14 +292,11 @@ public: case GRAPH_NODE_MAKE_TYPE(14, 214): case GRAPH_NODE_MAKE_TYPE(14, 215): case GRAPH_NODE_MAKE_TYPE(14, 216): - { node->IsConstant = false; break; - } // Particle Emitter Function case GRAPH_NODE_MAKE_TYPE(14, 300): - node->Assets[0] = Content::LoadAsync((Guid)node->Values[0]); - node->UsesParticleData = true; // TODO: analyze emitter function graph to detect if it's actually using any particle data at all (even from inputs after inline) + InitParticleEmitterFunctionCall((Guid)node->Values[0], node->Assets[0], node->UsesParticleData, Layout); break; // Particle Index case GRAPH_NODE_MAKE_TYPE(14, 301): @@ -564,7 +561,7 @@ public: return true; // Compute particle data layout and initialize used nodes (for only used nodes, start depth searching rom the modules) - Layout.AddAttribute(TEXT("Position"), ParticleAttribute::ValueTypes::Float3); + //Layout.AddAttribute(TEXT("Position"), ParticleAttribute::ValueTypes::Float3); #define PROCESS_MODULES(modules) for (int32 i = 0; i < modules.Count(); i++) { modules[i]->Used = false; InitializeNode(modules[i]); } PROCESS_MODULES(SpawnModules); PROCESS_MODULES(InitModules); diff --git a/Source/Engine/Particles/ParticleEmitter.cpp b/Source/Engine/Particles/ParticleEmitter.cpp index 20e4c0490..ea1161c6b 100644 --- a/Source/Engine/Particles/ParticleEmitter.cpp +++ b/Source/Engine/Particles/ParticleEmitter.cpp @@ -19,6 +19,7 @@ #include "Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h" #if BUILD_DEBUG #include "Engine/Engine/Globals.h" +#include "Engine/Scripting/BinaryModule.h" #endif #endif #if COMPILE_WITH_GPU_PARTICLES @@ -185,7 +186,9 @@ Asset::LoadResult ParticleEmitter::load() #if BUILD_DEBUG && USE_EDITOR // Dump generated shader source to the temporary file + BinaryModule::Locker.Lock(); source.SaveToFile(Globals::ProjectCacheFolder / TEXT("particle_emitter.txt")); + BinaryModule::Locker.Unlock(); #endif // Encrypt source code diff --git a/Source/Engine/Particles/ParticleEmitterFunction.cpp b/Source/Engine/Particles/ParticleEmitterFunction.cpp index c91bc0d82..5abb4a434 100644 --- a/Source/Engine/Particles/ParticleEmitterFunction.cpp +++ b/Source/Engine/Particles/ParticleEmitterFunction.cpp @@ -9,6 +9,27 @@ #endif #include "Engine/Content/Factories/BinaryAssetFactory.h" +void InitParticleEmitterFunctionCall(const Guid& assetId, AssetReference& asset, bool& usesParticleData, ParticleLayout& layout) +{ + const auto function = Content::Load(assetId); + asset = function; + if (function) + { + // Insert any used particle data into the calling graph + for (const ParticleAttribute& e : function->Graph.Layout.Attributes) + { + if (layout.FindAttribute(e.Name, e.ValueType) == -1) + layout.AddAttribute(e.Name, e.ValueType); + } + + // Detect if function needs to be evaluated per-particle + for (int32 i = 0; i < function->Outputs.Count() && !usesParticleData; i++) + { + usesParticleData = function->Graph.Nodes[function->Outputs.Get()[i]].UsesParticleData; + } + } +} + REGISTER_BINARY_ASSET(ParticleEmitterFunction, "FlaxEngine.ParticleEmitterFunction", false); ParticleEmitterFunction::ParticleEmitterFunction(const SpawnParams& params, const AssetInfo* info) diff --git a/Source/Engine/Physics/CollisionCooking.cpp b/Source/Engine/Physics/CollisionCooking.cpp index dc2c42584..4c8cffdcc 100644 --- a/Source/Engine/Physics/CollisionCooking.cpp +++ b/Source/Engine/Physics/CollisionCooking.cpp @@ -127,6 +127,8 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali const auto& mesh = *meshes[i]; if ((arg.MaterialSlotsMask & (1 << mesh.GetMaterialSlotIndex())) == 0) continue; + if (mesh.GetVertexCount() == 0) + continue; int32 count; if (mesh.DownloadDataCPU(MeshBufferType::Vertex0, vertexBuffers[i], count)) @@ -159,6 +161,8 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali const auto& mesh = *meshes[i]; if ((arg.MaterialSlotsMask & (1 << mesh.GetMaterialSlotIndex())) == 0) continue; + if (mesh.GetVertexCount() == 0) + continue; auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, vertexBuffers[i]); if (task == nullptr) @@ -208,6 +212,8 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali const int32 firstVertexIndex = vertexCounter; const int32 vertexCount = vertexCounts[i]; + if (vertexCount == 0) + continue; Platform::MemoryCopy(finalVertexData.Get() + firstVertexIndex, vData.Get(), vertexCount * sizeof(Float3)); vertexCounter += vertexCount; diff --git a/Source/Engine/Physics/CollisionData.cpp b/Source/Engine/Physics/CollisionData.cpp index 36f28c111..54a49671d 100644 --- a/Source/Engine/Physics/CollisionData.cpp +++ b/Source/Engine/Physics/CollisionData.cpp @@ -23,12 +23,16 @@ CollisionData::CollisionData(const SpawnParams& params, const AssetInfo* info) bool CollisionData::CookCollision(CollisionDataType type, ModelBase* modelObj, int32 modelLodIndex, uint32 materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit) { - // Validate state if (!IsVirtual()) { LOG(Warning, "Only virtual assets can be modified at runtime."); return true; } + if (IsInMainThread() && modelObj && modelObj->IsVirtual()) + { + LOG(Error, "Cannot cook collision data for virtual models on a main thread (virtual models data is stored on GPU only). Use thread pool or async task."); + return true; + } // Prepare CollisionCooking::Argument arg; @@ -43,18 +47,12 @@ bool CollisionData::CookCollision(CollisionDataType type, ModelBase* modelObj, i SerializedOptions options; BytesContainer outputData; if (CollisionCooking::CookCollision(arg, options, outputData)) - { return true; - } - - // Clear state - unload(true); // Load data + unload(true); if (load(&options, outputData.Get(), outputData.Length()) != LoadResult::Ok) - { return true; - } // Mark as loaded (eg. Mesh Colliders using this asset will update shape for physics simulation) onLoaded(); diff --git a/Source/Engine/Physics/CollisionData.cs b/Source/Engine/Physics/CollisionData.cs index 8b54c6218..e7f76a90f 100644 --- a/Source/Engine/Physics/CollisionData.cs +++ b/Source/Engine/Physics/CollisionData.cs @@ -19,7 +19,7 @@ namespace FlaxEngine /// The convex mesh generation flags. /// The convex mesh vertex limit. Use values in range [8;255] /// True if failed, otherwise false. - [Obsolete("Deprecated in 1.4")] + [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")] public bool CookCollision(CollisionDataType type, Vector3[] vertices, uint[] triangles, ConvexMeshGenerationFlags convexFlags = ConvexMeshGenerationFlags.None, int convexVertexLimit = 255) { if (vertices == null) @@ -43,7 +43,7 @@ namespace FlaxEngine /// The convex mesh generation flags. /// The convex mesh vertex limit. Use values in range [8;255] /// True if failed, otherwise false. - [Obsolete("Deprecated in 1.4")] + [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")] public bool CookCollision(CollisionDataType type, Vector3[] vertices, int[] triangles, ConvexMeshGenerationFlags convexFlags = ConvexMeshGenerationFlags.None, int convexVertexLimit = 255) { if (vertices == null) @@ -60,7 +60,7 @@ namespace FlaxEngine /// /// The output vertex buffer. /// The output index buffer. - [Obsolete("Deprecated in 1.4")] + [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")] public void ExtractGeometry(out Vector3[] vertexBuffer, out int[] indexBuffer) { ExtractGeometry(out Float3[] tmp, out indexBuffer); diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp index 7c9355099..684aeca19 100644 --- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp +++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp @@ -132,7 +132,8 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c const PxReal* impulses = pair.contactImpulses; //const PxU32 flippedContacts = (pair.flags & PxContactPairFlag::eINTERNAL_CONTACTS_ARE_FLIPPED); - const PxU32 hasImpulses = (pair.flags & PxContactPairFlag::eINTERNAL_HAS_IMPULSES); + const bool hasImpulses = pair.flags.isSet(PxContactPairFlag::eINTERNAL_HAS_IMPULSES); + const bool hasPostVelocities = !pair.flags.isSet(PxContactPairFlag::eACTOR_PAIR_LOST_TOUCH); PxU32 nbContacts = 0; PxVec3 totalImpulse(0.0f); @@ -166,7 +167,8 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c } // Extract velocities - if (j.nextItemSet()) + c.ThisVelocity = c.OtherVelocity = Vector3::Zero; + if (hasPostVelocities && j.nextItemSet()) { ASSERT(j.contactPairIndex == pairIndex); if (j.postSolverVelocity) @@ -177,10 +179,6 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c c.ThisVelocity = P2C(linearVelocityActor0); c.OtherVelocity = P2C(linearVelocityActor1); } - else - { - c.ThisVelocity = c.OtherVelocity = Vector3::Zero; - } } c.ContactsCount = nbContacts; @@ -195,6 +193,7 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c RemovedCollisions.Add(c); } } + ASSERT(!j.nextItemSet()); } void SimulationEventCallback::onTrigger(PxTriggerPair* pairs, PxU32 count) diff --git a/Source/Engine/Platform/Android/AndroidFileSystem.cpp b/Source/Engine/Platform/Android/AndroidFileSystem.cpp index 78fa8953d..a04fc0229 100644 --- a/Source/Engine/Platform/Android/AndroidFileSystem.cpp +++ b/Source/Engine/Platform/Android/AndroidFileSystem.cpp @@ -7,6 +7,7 @@ #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/StringView.h" #include "Engine/Core/Types/TimeSpan.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Core/Math/Math.h" #include "Engine/Engine/Globals.h" #include "Engine/Utilities/StringConverter.h" diff --git a/Source/Engine/Platform/Apple/AppleFileSystem.cpp b/Source/Engine/Platform/Apple/AppleFileSystem.cpp index cc18fc3ed..9e306019a 100644 --- a/Source/Engine/Platform/Apple/AppleFileSystem.cpp +++ b/Source/Engine/Platform/Apple/AppleFileSystem.cpp @@ -8,6 +8,7 @@ #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/StringView.h" #include "Engine/Core/Types/TimeSpan.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Core/Math/Math.h" #include "Engine/Core/Log.h" #include "Engine/Utilities/StringConverter.h" diff --git a/Source/Engine/Platform/Base/FileBase.h b/Source/Engine/Platform/Base/FileBase.h index c6f76c610..5587e20a9 100644 --- a/Source/Engine/Platform/Base/FileBase.h +++ b/Source/Engine/Platform/Base/FileBase.h @@ -7,7 +7,6 @@ #include "Engine/Core/NonCopyable.h" #include "Engine/Core/Types/BaseTypes.h" #include "Engine/Core/Types/DateTime.h" -#include "Engine/Core/Collections/Array.h" class StringBuilder; template @@ -16,17 +15,51 @@ class DataContainer; /// /// Specifies how the operating system should open a file. /// -DECLARE_ENUM_FLAGS_5(FileMode, uint32, CreateAlways, 2, CreateNew, 1, OpenAlways, 4, OpenExisting, 3, TruncateExisting, 5); +enum class FileMode : uint32 +{ + // Creates a new file, only if it does not already exist. + CreateNew = 1, + // Creates a new file, always. + CreateAlways = 2, + // Opens a file, only if it exists. Fails if file doesn't exist. + OpenExisting = 3, + // Opens a file, always. + OpenAlways = 4, + // Opens a file and truncates it so that its size is zero bytes, only if it exists. Fails if file doesn't exist. + TruncateExisting = 5, +}; /// /// Defines constants for read, write, or read/write access to a file. /// -DECLARE_ENUM_FLAGS_3(FileAccess, uint32, Read, 0x80000000, Write, 0x40000000, ReadWrite, (uint32)FileAccess::Read | (uint32)FileAccess::Write); +enum class FileAccess : uint32 +{ + // Enables reading data from the file. + Read = 0x80000000, + // Enables writing data to the file. + Write = 0x40000000, + // Enables both data read and write operations on the file. + ReadWrite = Read | Write, +}; /// /// Contains constants for controlling the kind of access other objects can have to the same file. /// -DECLARE_ENUM_FLAGS_6(FileShare, uint32, Delete, 0x00000004, None, 0x00000000, Read, 0x00000001, Write, 0x00000002, ReadWrite, (uint32)FileShare::Read | (uint32)FileShare::Write, All, (uint32)FileShare::ReadWrite | (uint32)FileShare::Delete); +enum class FileShare : uint32 +{ + // Prevents any operations on the file file it's opened. + None = 0x00000000, + // Allows read operations on a file. + Read = 0x00000001, + // Allows write operations on a file. + Write = 0x00000002, + // Allows delete operations on a file. + Delete = 0x00000004, + // Allows read and write operations on a file. + ReadWrite = Read | Write, + // Allows any operations on a file. + All = ReadWrite | Delete, +}; /// /// The base class for file objects. @@ -34,7 +67,6 @@ DECLARE_ENUM_FLAGS_6(FileShare, uint32, Delete, 0x00000004, None, 0x00000000, Re class FLAXENGINE_API FileBase : public NonCopyable { public: - /// /// Finalizes an instance of the class. /// @@ -43,7 +75,6 @@ public: } public: - /// /// Reads data from a file. /// @@ -68,7 +99,6 @@ public: virtual void Close() = 0; public: - /// /// Gets size of the file (in bytes). /// @@ -100,14 +130,13 @@ public: virtual bool IsOpened() const = 0; public: - static bool ReadAllBytes(const StringView& path, byte* data, int32 length); - static bool ReadAllBytes(const StringView& path, Array& data); + static bool ReadAllBytes(const StringView& path, Array& data); static bool ReadAllBytes(const StringView& path, DataContainer& data); static bool ReadAllText(const StringView& path, String& data); static bool ReadAllText(const StringView& path, StringAnsi& data); static bool WriteAllBytes(const StringView& path, const byte* data, int32 length); - static bool WriteAllBytes(const StringView& path, const Array& data); + static bool WriteAllBytes(const StringView& path, const Array& data); static bool WriteAllText(const StringView& path, const String& data, Encoding encoding); static bool WriteAllText(const StringView& path, const StringBuilder& data, Encoding encoding); static bool WriteAllText(const StringView& path, const Char* data, int32 length, Encoding encoding); diff --git a/Source/Engine/Platform/Base/FileSystemBase.cpp b/Source/Engine/Platform/Base/FileSystemBase.cpp index f2f45a6d4..3d0c053d5 100644 --- a/Source/Engine/Platform/Base/FileSystemBase.cpp +++ b/Source/Engine/Platform/Base/FileSystemBase.cpp @@ -5,6 +5,7 @@ #include "Engine/Core/Types/Guid.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/StringView.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Core/Math/Math.h" #include "Engine/Engine/Globals.h" diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp index 279813470..8da3a378b 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp +++ b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp @@ -9,6 +9,7 @@ #include "Engine/Core/Types/StringBuilder.h" #include "Engine/Core/Types/StringView.h" #include "Engine/Core/Types/TimeSpan.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Core/Math/Math.h" #include "Engine/Core/Log.h" #include "Engine/Utilities/StringConverter.h" @@ -69,7 +70,30 @@ bool LinuxFileSystem::ShowOpenFileDialog(Window* parentWindow, const StringView& } FILE* f = popen(cmd, "r"); char buf[2048]; - fgets(buf, ARRAY_COUNT(buf), f); + char* writePointer = buf; + int remainingCapacity = ARRAY_COUNT(buf); + // make sure we read all output from kdialog + while (remainingCapacity > 0 && fgets(writePointer, remainingCapacity, f)) + { + int r = strlen(writePointer); + writePointer += r; + remainingCapacity -= r; + } + if (remainingCapacity <= 0) + { + LOG(Error, "You selected more files than an internal buffer can hold. Try selecting fewer files at a time."); + // in case of an overflow we miss the closing null byte, add it after the rightmost linefeed + while (*writePointer != '\n') + { + writePointer--; + if (writePointer == buf) + { + *buf = 0; + break; + } + } + *(++writePointer) = 0; + } int result = pclose(f); if (result != 0) { diff --git a/Source/Engine/Platform/Mac/MacFileSystem.cpp b/Source/Engine/Platform/Mac/MacFileSystem.cpp index c765feef4..e2ed24f13 100644 --- a/Source/Engine/Platform/Mac/MacFileSystem.cpp +++ b/Source/Engine/Platform/Mac/MacFileSystem.cpp @@ -9,6 +9,7 @@ #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/StringView.h" #include "Engine/Core/Types/TimeSpan.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Core/Math/Math.h" #include "Engine/Core/Log.h" #include "Engine/Utilities/StringConverter.h" diff --git a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp index 38384f05f..d07c333e0 100644 --- a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp +++ b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp @@ -8,6 +8,7 @@ #include "Engine/Platform/Windows/ComPtr.h" #include "Engine/Platform/CreateProcessSettings.h" #include "Engine/Core/Types/StringView.h" +#include "Engine/Core/Collections/Array.h" #include "../Win32/IncludeWindowsHeaders.h" // Hack this stuff (the problem is that GDI has function named Rectangle -> like one of the Flax core types) diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index 1bc50f207..ff65e9864 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -108,8 +108,7 @@ WindowsWindow::WindowsWindow(const CreateWindowSettings& settings) style |= WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_SYSMENU | WS_THICKFRAME | WS_GROUP; #elif WINDOWS_USE_NEWER_BORDER_LESS if (settings.IsRegularWindow) - style |= WS_THICKFRAME | WS_SYSMENU; - style |= WS_CAPTION; + style |= WS_THICKFRAME | WS_SYSMENU | WS_CAPTION; #endif exStyle |= WS_EX_WINDOWEDGE; } @@ -220,12 +219,6 @@ void WindowsWindow::Show() if (!_settings.HasBorder) { SetWindowPos(_handle, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER); - if (!_settings.IsRegularWindow && _settings.ShowAfterFirstPaint && _settings.StartPosition == WindowStartPosition::Manual) - { - int32 x = Math::TruncToInt(_settings.Position.X); - int32 y = Math::TruncToInt(_settings.Position.Y); - SetWindowPos(_handle, nullptr, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER); - } } #endif @@ -539,6 +532,12 @@ Float2 WindowsWindow::ClientToScreen(const Float2& clientPos) const { ASSERT(HasHWND()); + if (_minimized) + { + // Return cached position when window is not on screen + return _minimizedScreenPosition + clientPos; + } + POINT p; p.x = static_cast(clientPos.X); p.y = static_cast(clientPos.Y); @@ -1116,6 +1115,28 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam) { if (SIZE_MINIMIZED == wParam) { + // Get the minimized window position in workspace coordinates + WINDOWPLACEMENT placement; + placement.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(_handle, &placement); + + // Calculate client offsets from window borders and title bar + RECT winRect = { 0, 0, static_cast(_clientSize.X), static_cast(_clientSize.Y) }; + LONG style = GetWindowLong(_handle, GWL_STYLE); + LONG exStyle = GetWindowLong(_handle, GWL_EXSTYLE); + AdjustWindowRectEx(&winRect, style, FALSE, exStyle); + + // Calculate monitor offsets from taskbar position + const HMONITOR monitor = MonitorFromWindow(_handle, MONITOR_DEFAULTTONEAREST); + MONITORINFO monitorInfo; + monitorInfo.cbSize = sizeof(MONITORINFO); + GetMonitorInfoW(monitor, &monitorInfo); + + // Convert the workspace coordinates to screen space and store it + _minimizedScreenPosition = Float2( + static_cast(placement.rcNormalPosition.left + monitorInfo.rcWork.left - monitorInfo.rcMonitor.left - winRect.left), + static_cast(placement.rcNormalPosition.top + monitorInfo.rcWork.top - monitorInfo.rcMonitor.top - winRect.top)); + _minimized = true; _maximized = false; } diff --git a/Source/Engine/Platform/Windows/WindowsWindow.h b/Source/Engine/Platform/Windows/WindowsWindow.h index e15f86a45..186793b1e 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.h +++ b/Source/Engine/Platform/Windows/WindowsWindow.h @@ -32,6 +32,7 @@ private: Windows::HANDLE _monitor = nullptr; Windows::LONG _clipCursorRect[4]; int32 _regionWidth = 0, _regionHeight = 0; + Float2 _minimizedScreenPosition = Float2::Zero; public: diff --git a/Source/Engine/Profiler/ProfilerGPU.cpp b/Source/Engine/Profiler/ProfilerGPU.cpp index 3054abe67..6944441be 100644 --- a/Source/Engine/Profiler/ProfilerGPU.cpp +++ b/Source/Engine/Profiler/ProfilerGPU.cpp @@ -74,6 +74,7 @@ void ProfilerGPU::EventBuffer::Clear() _data.Clear(); _isResolved = false; FrameIndex = 0; + PresentTime = 0.0f; } GPUTimerQuery* ProfilerGPU::GetTimerQuery() @@ -133,7 +134,9 @@ void ProfilerGPU::BeginFrame() // Clear stats RenderStatsData::Counter = RenderStatsData(); _depth = 0; - Buffers[CurrentBuffer].FrameIndex = Engine::FrameCount; + auto& buffer = Buffers[CurrentBuffer]; + buffer.FrameIndex = Engine::FrameCount; + buffer.PresentTime = 0.0f; // Try to resolve previous frames for (int32 i = 0; i < PROFILER_GPU_EVENTS_FRAMES; i++) @@ -149,6 +152,12 @@ void ProfilerGPU::OnPresent() buffer.EndAll(); } +void ProfilerGPU::OnPresentTime(float time) +{ + auto& buffer = Buffers[CurrentBuffer]; + buffer.PresentTime += time; +} + void ProfilerGPU::EndFrame() { if (_depth) @@ -164,7 +173,7 @@ void ProfilerGPU::EndFrame() buffer.Clear(); } -bool ProfilerGPU::GetLastFrameData(float& drawTimeMs, RenderStatsData& statsData) +bool ProfilerGPU::GetLastFrameData(float& drawTimeMs, float& presentTimeMs, RenderStatsData& statsData) { uint64 maxFrame = 0; int32 maxFrameIndex = -1; @@ -177,17 +186,19 @@ bool ProfilerGPU::GetLastFrameData(float& drawTimeMs, RenderStatsData& statsData maxFrameIndex = i; } } - if (maxFrameIndex != -1) { auto& frame = frames[maxFrameIndex]; const auto root = frame.Get(0); drawTimeMs = root->Time; + presentTimeMs = frame.PresentTime; statsData = root->Stats; return true; } + // No data drawTimeMs = 0.0f; + presentTimeMs = 0.0f; Platform::MemoryClear(&statsData, sizeof(statsData)); return false; } diff --git a/Source/Engine/Profiler/ProfilerGPU.h b/Source/Engine/Profiler/ProfilerGPU.h index 29392b7a5..94ed345b9 100644 --- a/Source/Engine/Profiler/ProfilerGPU.h +++ b/Source/Engine/Profiler/ProfilerGPU.h @@ -20,6 +20,8 @@ class GPUTimerQuery; API_CLASS(Static) class FLAXENGINE_API ProfilerGPU { DECLARE_SCRIPTING_TYPE_NO_SPAWN(ProfilerGPU); + friend class Engine; + friend class GPUDevice; public: /// /// Represents single CPU profiling event data. @@ -69,6 +71,11 @@ public: /// uint64 FrameIndex; + /// + /// Sum of all present events duration on CPU during this frame (in milliseconds). + /// + float PresentTime; + /// /// Determines whether this buffer has ready data (resolved and not empty). /// @@ -125,7 +132,7 @@ public: /// /// True if GPU profiling is enabled, otherwise false to disable events collecting and GPU timer queries usage. Can be changed during rendering. /// - static bool Enabled; + API_FIELD() static bool Enabled; /// /// The current frame buffer to collect events. @@ -151,32 +158,20 @@ public: /// The event token index returned by the BeginEvent method. static void EndEvent(int32 index); - /// - /// Begins the new frame rendering. Called by the engine to sync profiling data. - /// - static void BeginFrame(); - - /// - /// Called when just before flushing current frame GPU commands (via Present or Flush). Call active timer queries should be ended now. - /// - static void OnPresent(); - - /// - /// Ends the frame rendering. Called by the engine to sync profiling data. - /// - static void EndFrame(); - /// /// Tries to get the rendering stats from the last frame drawing (that has been resolved and has valid data). /// /// The draw execution time on a GPU (in milliseconds). + /// The final frame present time on a CPU (in milliseconds). Time game waited for vsync or GPU to finish previous frame rendering. /// The rendering stats data. /// True if got the data, otherwise false. - static bool GetLastFrameData(float& drawTimeMs, RenderStatsData& statsData); + API_FUNCTION() static bool GetLastFrameData(float& drawTimeMs, float& presentTimeMs, RenderStatsData& statsData); - /// - /// Releases resources. Calls to the profiling API after Dispose are not valid - /// +private: + static void BeginFrame(); + static void OnPresent(); + static void OnPresentTime(float time); + static void EndFrame(); static void Dispose(); }; diff --git a/Source/Engine/Profiler/ProfilingTools.cpp b/Source/Engine/Profiler/ProfilingTools.cpp index 61c0e2c33..113a04ec8 100644 --- a/Source/Engine/Profiler/ProfilingTools.cpp +++ b/Source/Engine/Profiler/ProfilingTools.cpp @@ -48,7 +48,9 @@ void ProfilingToolsService::Update() stats.PhysicsTimeMs = static_cast(Time::Physics.LastLength * 1000.0); stats.DrawCPUTimeMs = static_cast(Time::Draw.LastLength * 1000.0); - ProfilerGPU::GetLastFrameData(stats.DrawGPUTimeMs, stats.DrawStats); + float presentTime; + ProfilerGPU::GetLastFrameData(stats.DrawGPUTimeMs, presentTime, stats.DrawStats); + stats.DrawCPUTimeMs = Math::Max(stats.DrawCPUTimeMs - presentTime, 0.0f); // Remove swapchain present wait time to exclude from drawing on CPU } // Extract CPU profiler events diff --git a/Source/Engine/Render2D/SpriteAtlas.cpp b/Source/Engine/Render2D/SpriteAtlas.cpp index 0e0aa071f..ac711b8b8 100644 --- a/Source/Engine/Render2D/SpriteAtlas.cpp +++ b/Source/Engine/Render2D/SpriteAtlas.cpp @@ -47,10 +47,16 @@ int32 SpriteAtlas::GetSpritesCount() const Sprite SpriteAtlas::GetSprite(int32 index) const { - CHECK_RETURN(index >= 0 && index < Sprites.Count(), Sprite()) + CHECK_RETURN(index >= 0 && index < Sprites.Count(), Sprite()); return Sprites.Get()[index]; } +void SpriteAtlas::GetSpriteArea(int32 index, Rectangle& result) const +{ + CHECK(index >= 0 && index < Sprites.Count()); + result = Sprites.Get()[index].Area; +} + void SpriteAtlas::SetSprite(int32 index, const Sprite& value) { CHECK(index >= 0 && index < Sprites.Count()); diff --git a/Source/Engine/Render2D/SpriteAtlas.cs b/Source/Engine/Render2D/SpriteAtlas.cs index b3796ffd3..93cdf68b2 100644 --- a/Source/Engine/Render2D/SpriteAtlas.cs +++ b/Source/Engine/Render2D/SpriteAtlas.cs @@ -70,7 +70,13 @@ namespace FlaxEngine [NoSerialize] public Float2 Size { - get => Area.Size * Atlas.Size; + get + { + if (Atlas == null) + throw new InvalidOperationException("Cannot use invalid sprite."); + Atlas.GetSpriteArea(Index, out var area); + return area.Size * Atlas.Size; + } set { var area = Area; @@ -89,7 +95,8 @@ namespace FlaxEngine { if (Atlas == null) throw new InvalidOperationException("Cannot use invalid sprite."); - return Atlas.GetSprite(Index).Area; + Atlas.GetSpriteArea(Index, out var area); + return area; } set { diff --git a/Source/Engine/Render2D/SpriteAtlas.h b/Source/Engine/Render2D/SpriteAtlas.h index 6fcda5162..533440c68 100644 --- a/Source/Engine/Render2D/SpriteAtlas.h +++ b/Source/Engine/Render2D/SpriteAtlas.h @@ -120,6 +120,14 @@ public: /// The sprite data. API_FUNCTION() Sprite GetSprite(int32 index) const; + /// + /// Gets the sprite area. + /// + /// The index. + /// The output sprite area. + /// The sprite data. + API_FUNCTION() void GetSpriteArea(int32 index, API_PARAM(Out) Rectangle& result) const; + /// /// Sets the sprite data. /// diff --git a/Source/Engine/Renderer/ProbesRenderer.cpp b/Source/Engine/Renderer/ProbesRenderer.cpp index e24022b07..a965dee09 100644 --- a/Source/Engine/Renderer/ProbesRenderer.cpp +++ b/Source/Engine/Renderer/ProbesRenderer.cpp @@ -105,7 +105,7 @@ class ProbesRendererService : public EngineService { public: ProbesRendererService() - : EngineService(TEXT("Probes Renderer"), 70) + : EngineService(TEXT("Probes Renderer"), 500) { } diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index b22effdc9..7e0cd1053 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -9,6 +9,7 @@ #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Content/Content.h" #include "Engine/Graphics/GPUContext.h" +#include "Engine/Scripting/Enums.h" #if USE_EDITOR #include "Engine/Renderer/Lightmaps.h" #endif @@ -86,11 +87,12 @@ bool ShadowsPass::Init() const auto formatFeaturesTexture = GPUDevice::Instance->GetFormatFeatures(formatTexture); _supportsShadows = EnumHasAllFlags(formatFeaturesDepth.Support, FormatSupport::DepthStencil | FormatSupport::Texture2D) && EnumHasAllFlags(formatFeaturesTexture.Support, FormatSupport::ShaderSample | FormatSupport::ShaderSampleComparison); + // TODO: fallback to 32-bit shadow map format if 16-bit is not supported if (!_supportsShadows) { LOG(Warning, "GPU doesn't support shadows rendering"); - LOG(Warning, "Format: {0}, features support: {1}", (int32)SHADOW_MAPS_FORMAT, (uint32)formatFeaturesDepth.Support); - LOG(Warning, "Format: {0}, features support: {1}", (int32)formatTexture, (uint32)formatFeaturesTexture.Support); + LOG(Warning, "Format: {0}, features support: {1}", ScriptingEnum::ToString(SHADOW_MAPS_FORMAT), (uint32)formatFeaturesDepth.Support); + LOG(Warning, "Format: {0}, features support: {1}", ScriptingEnum::ToString(formatTexture), (uint32)formatFeaturesTexture.Support); } return false; diff --git a/Source/Engine/Scripting/Internal/EngineInternalCalls.cpp b/Source/Engine/Scripting/Internal/EngineInternalCalls.cpp index 27bb8aa47..e3e5b4342 100644 --- a/Source/Engine/Scripting/Internal/EngineInternalCalls.cpp +++ b/Source/Engine/Scripting/Internal/EngineInternalCalls.cpp @@ -190,7 +190,6 @@ DEFINE_INTERNAL_CALL(bool) ScriptingInternal_IsTypeFromGameScripts(MTypeObject* DEFINE_INTERNAL_CALL(void) ScriptingInternal_FlushRemovedObjects() { - ASSERT(IsInMainThread()); ObjectsRemovalService::Flush(); } diff --git a/Source/Engine/Scripting/Internal/ManagedDictionary.cpp b/Source/Engine/Scripting/Internal/ManagedDictionary.cpp index 7cb4d1cfb..6d3508b3d 100644 --- a/Source/Engine/Scripting/Internal/ManagedDictionary.cpp +++ b/Source/Engine/Scripting/Internal/ManagedDictionary.cpp @@ -1,5 +1,8 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + #include "ManagedDictionary.h" +#if USE_CSHARP Dictionary ManagedDictionary::CachedDictionaryTypes; #if !USE_MONO_AOT ManagedDictionary::MakeGenericTypeThunk ManagedDictionary::MakeGenericType; @@ -12,3 +15,4 @@ MMethod* ManagedDictionary::CreateInstance; MMethod* ManagedDictionary::AddDictionaryItem; MMethod* ManagedDictionary::GetDictionaryKeys; #endif +#endif diff --git a/Source/Engine/Scripting/ManagedCLR/MAssembly.h b/Source/Engine/Scripting/ManagedCLR/MAssembly.h index 3862627d0..ff1645e5e 100644 --- a/Source/Engine/Scripting/ManagedCLR/MAssembly.h +++ b/Source/Engine/Scripting/ManagedCLR/MAssembly.h @@ -25,8 +25,8 @@ private: MonoAssembly* _monoAssembly = nullptr; MonoImage* _monoImage = nullptr; #elif USE_NETCORE - StringAnsi _fullname; void* _handle = nullptr; + StringAnsi _fullname; #endif MDomain* _domain; @@ -50,6 +50,7 @@ public: /// The assembly name. MAssembly(MDomain* domain, const StringAnsiView& name); +#if USE_NETCORE /// /// Initializes a new instance of the class. /// @@ -58,6 +59,7 @@ public: /// The assembly full name. /// The managed handle of the assembly. MAssembly(MDomain* domain, const StringAnsiView& name, const StringAnsiView& fullname, void* handle); +#endif /// /// Finalizes an instance of the class. diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.cpp index d9cc6f863..02a834a9f 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MCore.cpp @@ -48,18 +48,22 @@ MAssembly::MAssembly(MDomain* domain, const StringAnsiView& name) { } +#if USE_NETCORE + MAssembly::MAssembly(MDomain* domain, const StringAnsiView& name, const StringAnsiView& fullname, void* handle) - : _domain(domain) + : _handle(handle) + , _fullname(fullname) + , _domain(domain) , _isLoaded(false) , _isLoading(false) , _hasCachedClasses(false) , _reloadCount(0) , _name(name) - , _fullname(fullname) - , _handle(handle) { } +#endif + MAssembly::~MAssembly() { Unload(); diff --git a/Source/Engine/Scripting/Plugins/PluginManager.cpp b/Source/Engine/Scripting/Plugins/PluginManager.cpp index 328afa69c..a8198474c 100644 --- a/Source/Engine/Scripting/Plugins/PluginManager.cpp +++ b/Source/Engine/Scripting/Plugins/PluginManager.cpp @@ -90,7 +90,7 @@ class PluginManagerService : public EngineService { public: PluginManagerService() - : EngineService(TEXT("Plugin Manager"), 130) + : EngineService(TEXT("Plugin Manager"), 100) { } diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 7f82f8dfc..b3e5a0072 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -41,12 +41,18 @@ #include #include #include +#include #include #include +#include #include #include typedef char char_t; #define DOTNET_HOST_MONO_DEBUG 0 +#ifdef USE_MONO_AOT_MODULE +void* MonoAotModuleHandle = nullptr; +#endif +MonoDomain* MonoDomainHandle = nullptr; #else #error "Unknown .NET runtime host." #endif @@ -180,7 +186,7 @@ void* GetStaticMethodPointer(const String& methodName); /// Calls the managed static method in NativeInterop class with given parameters. /// template -inline RetType CallStaticMethodByName(const String& methodName, Args... args) +FORCE_INLINE RetType CallStaticMethodByName(const String& methodName, Args... args) { typedef RetType (CORECLR_DELEGATE_CALLTYPE* fun)(Args...); return ((fun)GetStaticMethodPointer(methodName))(args...); @@ -190,16 +196,16 @@ inline RetType CallStaticMethodByName(const String& methodName, Args... args) /// Calls the managed static method with given parameters. /// template -inline RetType CallStaticMethod(void* methodPtr, Args... args) +FORCE_INLINE RetType CallStaticMethod(void* methodPtr, Args... args) { typedef RetType (CORECLR_DELEGATE_CALLTYPE* fun)(Args...); return ((fun)methodPtr)(args...); } -void RegisterNativeLibrary(const char* moduleName, const char* modulePath) +void RegisterNativeLibrary(const char* moduleName, const Char* modulePath) { static void* RegisterNativeLibraryPtr = GetStaticMethodPointer(TEXT("RegisterNativeLibrary")); - CallStaticMethod(RegisterNativeLibraryPtr, moduleName, modulePath); + CallStaticMethod(RegisterNativeLibraryPtr, moduleName, modulePath); } bool InitHostfxr(); @@ -281,7 +287,7 @@ bool MCore::LoadEngine() flaxLibraryPath = ::String(StringUtils::GetDirectoryName(Platform::GetExecutableFilePath())) / StringUtils::GetFileName(flaxLibraryPath); } #endif - RegisterNativeLibrary("FlaxEngine", StringAnsi(flaxLibraryPath).Get()); + RegisterNativeLibrary("FlaxEngine", flaxLibraryPath.Get()); MRootDomain = New("Root"); MDomains.Add(MRootDomain); @@ -516,6 +522,12 @@ void MCore::GC::FreeMemory(void* ptr, bool coTaskMem) void MCore::Thread::Attach() { +#if DOTNET_HOST_MONO + if (!IsInMainThread() && !mono_domain_get()) + { + mono_thread_attach(MonoDomainHandle); + } +#endif } void MCore::Thread::Exit() @@ -617,14 +629,33 @@ bool MCore::Type::IsReference(MType* type) void MCore::ScriptingObject::SetInternalValues(MClass* klass, MObject* object, void* unmanagedPtr, const Guid* id) { +#if PLATFORM_DESKTOP && !USE_MONO_AOT static void* ScriptingObjectSetInternalValuesPtr = GetStaticMethodPointer(TEXT("ScriptingObjectSetInternalValues")); CallStaticMethod(ScriptingObjectSetInternalValuesPtr, object, unmanagedPtr, id); +#else + const MField* monoUnmanagedPtrField = klass->GetField("__unmanagedPtr"); + if (monoUnmanagedPtrField) + monoUnmanagedPtrField->SetValue(object, &unmanagedPtr); + const MField* monoIdField = klass->GetField("__internalId"); + if (id != nullptr && monoIdField) + monoIdField->SetValue(object, (void*)id); +#endif } MObject* MCore::ScriptingObject::CreateScriptingObject(MClass* klass, void* unmanagedPtr, const Guid* id) { +#if PLATFORM_DESKTOP && !USE_MONO_AOT static void* ScriptingObjectSetInternalValuesPtr = GetStaticMethodPointer(TEXT("ScriptingObjectCreate")); return CallStaticMethod(ScriptingObjectSetInternalValuesPtr, klass->_handle, unmanagedPtr, id); +#else + MObject* object = MCore::Object::New(klass); + if (object) + { + MCore::ScriptingObject::SetInternalValues(klass, object, unmanagedPtr, id); + MCore::Object::Init(object); + } + return object; +#endif } const MAssembly::ClassesDictionary& MAssembly::GetClasses() const @@ -699,7 +730,6 @@ DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* man StringAnsi assemblyName; StringAnsi assemblyFullName; GetAssemblyName(assemblyHandle, assemblyName, assemblyFullName); - assembly = New(nullptr, assemblyName, assemblyFullName, assemblyHandle); CachedAssemblyHandles.Add(assemblyHandle, assembly); } @@ -766,13 +796,13 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa if (nativePath.HasChars()) { StringAnsi nativeName = _name.EndsWith(".CSharp") ? StringAnsi(_name.Get(), _name.Length() - 7) : StringAnsi(_name); - RegisterNativeLibrary(nativeName.Get(), StringAnsi(nativePath).Get()); + RegisterNativeLibrary(nativeName.Get(), nativePath.Get()); } #if USE_EDITOR // Register the editor module location for Assembly resolver else { - RegisterNativeLibrary(_name.Get(), StringAnsi(assemblyPath).Get()); + RegisterNativeLibrary(_name.Get(), assemblyPath.Get()); } #endif @@ -1192,9 +1222,9 @@ MException::~MException() MField::MField(MClass* parentClass, void* handle, const char* name, void* type, int fieldOffset, MFieldAttributes attributes) : _handle(handle) , _type(type) + , _fieldOffset(fieldOffset) , _parentClass(parentClass) , _name(name) - , _fieldOffset(fieldOffset) , _hasCachedAttributes(false) { switch (attributes & MFieldAttributes::FieldAccessMask) @@ -1241,8 +1271,8 @@ void MField::GetValue(MObject* instance, void* result) const void MField::GetValueReference(MObject* instance, void* result) const { - static void* FieldGetValueReferencePtr = GetStaticMethodPointer(TEXT("FieldGetValueReferenceWithOffset")); - CallStaticMethod(FieldGetValueReferencePtr, instance, _fieldOffset, result); + static void* FieldGetValueReferencePtr = GetStaticMethodPointer(TEXT("FieldGetValueReference")); + CallStaticMethod(FieldGetValueReferencePtr, instance, _handle, _fieldOffset, result); } MObject* MField::GetValueBoxed(MObject* instance) const @@ -1335,19 +1365,22 @@ MMethod::MMethod(MClass* parentClass, StringAnsi&& name, void* handle, int32 par void MMethod::CacheSignature() const { - _hasCachedSignature = true; + ScopeLock lock(BinaryModule::Locker); + if (_hasCachedSignature) + return; static void* GetMethodReturnTypePtr = GetStaticMethodPointer(TEXT("GetMethodReturnType")); static void* GetMethodParameterTypesPtr = GetStaticMethodPointer(TEXT("GetMethodParameterTypes")); - _returnType = CallStaticMethod(GetMethodReturnTypePtr, _handle); + if (_paramsCount != 0) + { + void** parameterTypeHandles; + CallStaticMethod(GetMethodParameterTypesPtr, _handle, ¶meterTypeHandles); + _parameterTypes.Set(parameterTypeHandles, _paramsCount); + MCore::GC::FreeMemory(parameterTypeHandles); + } - if (_paramsCount == 0) - return; - void** parameterTypeHandles; - CallStaticMethod(GetMethodParameterTypesPtr, _handle, ¶meterTypeHandles); - _parameterTypes.Set(parameterTypeHandles, _paramsCount); - MCore::GC::FreeMemory(parameterTypeHandles); + _hasCachedSignature = true; } MObject* MMethod::Invoke(void* instance, void** params, MObject** exception) const @@ -1403,7 +1436,7 @@ MType* MMethod::GetParameterType(int32 paramIdx) const if (!_hasCachedSignature) CacheSignature(); ASSERT_LOW_LAYER(paramIdx >= 0 && paramIdx < _paramsCount); - return (MType*)_parameterTypes[paramIdx]; + return (MType*)_parameterTypes.Get()[paramIdx]; } bool MMethod::GetParameterIsOut(int32 paramIdx) const @@ -1767,11 +1800,6 @@ void* GetStaticMethodPointer(const String& methodName) #elif DOTNET_HOST_MONO -#ifdef USE_MONO_AOT_MODULE -void* MonoAotModuleHandle = nullptr; -#endif -MonoDomain* MonoDomainHandle = nullptr; - void OnLogCallback(const char* logDomain, const char* logLevel, const char* message, mono_bool fatal, void* userData) { String currentDomain(logDomain); diff --git a/Source/Engine/Scripting/Runtime/Mono.cpp b/Source/Engine/Scripting/Runtime/Mono.cpp index ef0c42818..00f16fd44 100644 --- a/Source/Engine/Scripting/Runtime/Mono.cpp +++ b/Source/Engine/Scripting/Runtime/Mono.cpp @@ -2157,7 +2157,7 @@ MObject* MCore::ScriptingObject::CreateScriptingObject(MClass* klass, void* unma if (managedInstance) { // Set unmanaged object handle and id - MCore::ScriptingObject::SetInternalValues(klass, managedInstance, unmanagedPtr, _id); + MCore::ScriptingObject::SetInternalValues(klass, managedInstance, unmanagedPtr, id); // Initialize managed instance (calls constructor) MCore::Object::Init(managedInstance); diff --git a/Source/Engine/Scripting/Runtime/None.cpp b/Source/Engine/Scripting/Runtime/None.cpp index a25d59c59..414a3e666 100644 --- a/Source/Engine/Scripting/Runtime/None.cpp +++ b/Source/Engine/Scripting/Runtime/None.cpp @@ -1,9 +1,8 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "Engine/Scripting/Types.h" - #if !USE_CSHARP - +#include "Engine/Core/Types/Span.h" #include "Engine/Scripting/ManagedCLR/MCore.h" #include "Engine/Scripting/ManagedCLR/MDomain.h" #include "Engine/Scripting/ManagedCLR/MAssembly.h" diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index cce66cd98..a57833314 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -474,28 +474,32 @@ bool Scripting::Load() // Load FlaxEngine const String flaxEnginePath = Globals::BinariesFolder / TEXT("FlaxEngine.CSharp.dll"); auto* flaxEngineModule = (NativeBinaryModule*)GetBinaryModuleFlaxEngine(); - if (flaxEngineModule->Assembly->Load(flaxEnginePath)) + if (!flaxEngineModule->Assembly->IsLoaded()) { - LOG(Error, "Failed to load FlaxEngine C# assembly."); - return true; - } + if (flaxEngineModule->Assembly->Load(flaxEnginePath)) + { + LOG(Error, "Failed to load FlaxEngine C# assembly."); + return true; + } + onEngineLoaded(flaxEngineModule->Assembly); - onEngineLoaded(flaxEngineModule->Assembly); - - // Insert type aliases for vector types that don't exist in C++ but are just typedef (properly redirect them to actual types) - // TODO: add support for automatic typedef aliases setup for scripting module to properly lookup type from the alias typename + // Insert type aliases for vector types that don't exist in C++ but are just typedef (properly redirect them to actual types) + // TODO: add support for automatic typedef aliases setup for scripting module to properly lookup type from the alias typename #if USE_LARGE_WORLDS - flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double2"]; - flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double3"]; - flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double4"]; + flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double2"]; + flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double3"]; + flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double4"]; #else - flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float2"]; - flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float3"]; - flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float4"]; + flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float2"]; + flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float3"]; + flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float4"]; #endif - flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector2")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"]; - flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector3")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"]; - flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector4")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"]; +#if USE_CSHARP + flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector2")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"]; + flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector3")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"]; + flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector4")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"]; +#endif + } #if USE_EDITOR // Skip loading game modules in Editor on startup - Editor loads them later during splash screen (eg. after first compilation) diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index 8347fe7f8..67ba854e0 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -278,19 +278,30 @@ namespace FlaxEngine BackgroundNormal = Color.FromBgra(0xFF3F3F46), BorderNormal = Color.FromBgra(0xFF54545C), TextBoxBackground = Color.FromBgra(0xFF333337), - ProgressNormal = Color.FromBgra(0xFF0ad328), TextBoxBackgroundSelected = Color.FromBgra(0xFF3F3F46), CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC), - SharedTooltip = new Tooltip(), - Statusbar = new Style.StatusbarStyle() + ProgressNormal = Color.FromBgra(0xFF0ad328), + Statusbar = new Style.StatusbarStyle { PlayMode = Color.FromBgra(0xFF2F9135), Failed = Color.FromBgra(0xFF9C2424), - Loading = Color.FromBgra(0xFF2D2D30) - } + Loading = Color.FromBgra(0xFF2D2D30), + }, + + SharedTooltip = new Tooltip(), }; style.DragWindow = style.BackgroundSelected * 0.7f; + // Use optionally bundled default font (matches Editor) + var defaultFont = Content.LoadAsyncInternal("Editor/Fonts/Roboto-Regular"); + if (defaultFont) + { + style.FontTitle = defaultFont.CreateFont(18); + style.FontLarge = defaultFont.CreateFont(14); + style.FontMedium = defaultFont.CreateFont(9); + style.FontSmall = defaultFont.CreateFont(9); + } + Style.Current = style; } diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index c32f37066..59ac26484 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -6,6 +6,7 @@ #include "BinaryModule.h" #include "Engine/Level/Actor.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Types/Pair.h" #include "Engine/Utilities/StringConverter.h" #include "Engine/Content/Asset.h" #include "Engine/Content/Content.h" @@ -25,7 +26,8 @@ #define ScriptingObject_id "__internalId" // TODO: don't leak memory (use some kind of late manual GC for those wrapper objects) -Dictionary ScriptingObjectsInterfaceWrappers; +typedef Pair ScriptingObjectsInterfaceKey; +Dictionary ScriptingObjectsInterfaceWrappers; SerializableScriptingObject::SerializableScriptingObject(const SpawnParams& params) : ScriptingObject(params) @@ -202,10 +204,10 @@ ScriptingObject* ScriptingObject::FromInterface(void* interfaceObj, const Script } // Special case for interface wrapper object - for (auto& e : ScriptingObjectsInterfaceWrappers) + for (const auto& e : ScriptingObjectsInterfaceWrappers) { if (e.Value == interfaceObj) - return e.Key; + return e.Key.First; } return nullptr; @@ -226,10 +228,11 @@ void* ScriptingObject::ToInterface(ScriptingObject* obj, const ScriptingTypeHand else if (interface) { // Interface implemented in scripting (eg. C# class inherits C++ interface) - if (!ScriptingObjectsInterfaceWrappers.TryGet(obj, result)) + const ScriptingObjectsInterfaceKey key(obj, interfaceType); + if (!ScriptingObjectsInterfaceWrappers.TryGet(key, result)) { result = interfaceType.GetType().Interface.GetInterfaceWrapper(obj); - ScriptingObjectsInterfaceWrappers.Add(obj, result); + ScriptingObjectsInterfaceWrappers.Add(key, result); } } return result; @@ -241,7 +244,7 @@ ScriptingObject* ScriptingObject::ToNative(MObject* obj) #if USE_CSHARP if (obj) { -#if USE_MONO +#if USE_MONO || USE_MONO_AOT const auto ptrField = MCore::Object::GetClass(obj)->GetField(ScriptingObject_unmanagedPtr); CHECK_RETURN(ptrField, nullptr); ptrField->GetValue(obj, &ptr); diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index 7a27a74fd..c36e00b6a 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -73,6 +73,7 @@ namespace FlaxEngine.Json if (IsWriting) { // Reset writing state (eg if previous serialization got exception) + SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer); JsonWriter = new JsonTextWriter(StringWriter) { IndentChar = '\t', diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h index 94acd1d79..f9dc0870a 100644 --- a/Source/Engine/Serialization/Serialization.h +++ b/Source/Engine/Serialization/Serialization.h @@ -405,14 +405,14 @@ namespace Serialization // ISerializable - inline bool ShouldSerialize(ISerializable& v, const void* otherObj) + inline bool ShouldSerialize(const ISerializable& v, const void* otherObj) { return true; } - inline void Serialize(ISerializable::SerializeStream& stream, ISerializable& v, const void* otherObj) + inline void Serialize(ISerializable::SerializeStream& stream, const ISerializable& v, const void* otherObj) { stream.StartObject(); - v.Serialize(stream, otherObj); + const_cast(&v)->Serialize(stream, otherObj); stream.EndObject(); } inline void Deserialize(ISerializable::DeserializeStream& stream, ISerializable& v, ISerializeModifier* modifier) @@ -421,15 +421,15 @@ namespace Serialization } template - inline typename TEnableIf::Value, bool>::Type ShouldSerialize(ISerializable& v, const void* otherObj) + inline typename TEnableIf::Value, bool>::Type ShouldSerialize(const ISerializable& v, const void* otherObj) { return true; } template - inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, ISerializable& v, const void* otherObj) + inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, const ISerializable& v, const void* otherObj) { stream.StartObject(); - v.Serialize(stream, otherObj); + const_cast(&v)->Serialize(stream, otherObj); stream.EndObject(); } template @@ -441,12 +441,12 @@ namespace Serialization // Scripting Object template - inline typename TEnableIf::Value, bool>::Type ShouldSerialize(T*& v, const void* otherObj) + inline typename TEnableIf::Value, bool>::Type ShouldSerialize(const T*& v, const void* otherObj) { return !otherObj || v != *(T**)otherObj; } template - inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, T*& v, const void* otherObj) + inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, const T*& v, const void* otherObj) { stream.Guid(v ? v->GetID() : Guid::Empty); } @@ -568,12 +568,13 @@ namespace Serialization { if (!otherObj) return true; - const auto other = (Array*)otherObj; + const auto other = (const Array*)otherObj; if (v.Count() != other->Count()) return true; + const T* vPtr = v.Get(); for (int32 i = 0; i < v.Count(); i++) { - if (ShouldSerialize((T&)v[i], (const void*)&other->At(i))) + if (ShouldSerialize(vPtr[i], (const void*)&other->At(i))) return true; } return false; @@ -582,8 +583,9 @@ namespace Serialization inline void Serialize(ISerializable::SerializeStream& stream, const Array& v, const void* otherObj) { stream.StartArray(); + const T* vPtr = v.Get(); for (int32 i = 0; i < v.Count(); i++) - Serialize(stream, (T&)v[i], nullptr); + Serialize(stream, vPtr[i], nullptr); stream.EndArray(); } template @@ -593,8 +595,9 @@ namespace Serialization { const auto& streamArray = stream.GetArray(); v.Resize(streamArray.Size()); + T* vPtr = v.Get(); for (int32 i = 0; i < v.Count(); i++) - Deserialize(streamArray[i], v[i], modifier); + Deserialize(streamArray[i], vPtr[i], modifier); } else if (TIsPODType::Value && stream.IsString()) { diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp index ce4116134..de2ba6e54 100644 --- a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp +++ b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp @@ -28,6 +28,8 @@ #include "Engine/ContentImporters/AssetsImportingManager.h" #include "Engine/Platform/FileSystemWatcher.h" #include "Engine/Platform/FileSystem.h" +#include "Engine/Platform/File.h" +#include "Engine/Engine/Globals.h" #include "Editor/Editor.h" #include "Editor/ProjectInfo.h" #endif @@ -143,9 +145,22 @@ bool ShadersCompilation::Compile(ShaderCompilationOptions& options) #endif } - // Print info if succeed - if (result == false) + if (result) { +#if USE_EDITOR + // Output shader source to easily investigate errors (eg. for generated shaders like materials or particles) + const String outputSourceFolder = Globals::ProjectCacheFolder / TEXT("/Shaders/Source"); + const String outputSourcePath = outputSourceFolder / options.TargetName + TEXT(".hlsl"); + if (!FileSystem::DirectoryExists(outputSourceFolder)) + FileSystem::CreateDirectory(outputSourceFolder); + File::WriteAllBytes(outputSourcePath, (const byte*)options.Source, options.SourceLength); + LOG(Error, "Shader compilation '{0}' failed (profile: {1})", options.TargetName, ::ToString(options.Profile)); + LOG(Error, "Source: {0}", outputSourcePath); +#endif + } + else + { + // Success const DateTime endTime = DateTime::NowUTC(); LOG(Info, "Shader compilation '{0}' succeed in {1} ms (profile: {2})", options.TargetName, Math::CeilToInt(static_cast((endTime - startTime).GetTotalMilliseconds())), ::ToString(options.Profile)); } diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp index 8db8a0d0d..fe45b8b96 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp @@ -147,7 +147,7 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value) case 8: { const Value defaultValue = MaterialValue::InitForZero(VariantType::Void); - const Value alpha = tryGetValue(node->GetBox(2), 0, Value::Zero); + Value alpha = tryGetValue(node->GetBox(2), 0, Value::Zero).AsFloat(); if (alpha.IsZero()) { // Bottom-only @@ -178,6 +178,7 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value) auto topHeightScaled = writeLocal(VariantType::Float, String::Format(TEXT("{0} * {1}"), topHeight.Value, alpha.Value), node); auto heightStart = writeLocal(VariantType::Float, String::Format(TEXT("max({0}, {1}) - 0.05"), bottomHeightScaled.Value, topHeightScaled.Value), node); auto bottomLevel = writeLocal(VariantType::Float, String::Format(TEXT("max({0} - {1}, 0.0001)"), topHeightScaled.Value, heightStart.Value), node); + alpha = writeLocal(VariantType::Float, alpha.Value, node); _writer.Write(TEXT("\t{0} = {1} / (max({2} - {3}, 0) + {4});\n"), alpha.Value, bottomLevel.Value, bottomHeightScaled.Value, heightStart.Value, bottomLevel.Value); } #define EAT_BOX(type) writeBlending(MaterialGraphBoxes::type, value, bottom, top, alpha) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index b703b47d1..116f63407 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -22,6 +22,7 @@ #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Types/Pair.h" +#include "Engine/Core/Types/Variant.h" #include "Engine/Graphics/Models/SkeletonUpdater.h" #include "Engine/Graphics/Models/SkeletonMapping.h" #include "Engine/Core/Utilities.h" @@ -396,6 +397,7 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj SERIALIZE(SDFResolution); SERIALIZE(SplitObjects); SERIALIZE(ObjectIndex); + SERIALIZE(SubAssetFolder); } void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -441,6 +443,7 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi DESERIALIZE(SDFResolution); DESERIALIZE(SplitObjects); DESERIALIZE(ObjectIndex); + DESERIALIZE(SubAssetFolder); // [Deprecated on 23.11.2021, expires on 21.11.2023] int32 AnimationIndex = -1; @@ -744,6 +747,32 @@ void MeshOptDeallocate(void* ptr) Allocator::Free(ptr); } +void TrySetupMaterialParameter(MaterialInstance* instance, Span paramNames, const Variant& value, MaterialParameterType type) +{ + for (const Char* name : paramNames) + { + for (MaterialParameter& param : instance->Params) + { + const MaterialParameterType paramType = param.GetParameterType(); + if (type != paramType) + { + if (type == MaterialParameterType::Color) + { + if (paramType != MaterialParameterType::Vector3 || + paramType != MaterialParameterType::Vector4) + continue; + } + else + continue; + } + if (StringUtils::CompareIgnoreCase(name, param.GetName().Get()) != 0) + continue; + param.SetValue(value); + return; + } + } +} + bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& options, String& errorMsg, const String& autoImportOutput) { LOG(Info, "Importing model from \'{0}\'", path); @@ -1014,9 +1043,18 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op { // Create material instance AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialInstanceTag, assetPath, material.AssetID); - if (MaterialInstance* materialInstance = Content::Load(assetPath)) + if (auto* materialInstance = Content::Load(assetPath)) { materialInstance->SetBaseMaterial(options.InstanceToImportAs); + + // Customize base material based on imported material (blind guess based on the common names used in materials) + const Char* diffuseColorNames[] = { TEXT("color"), TEXT("col"), TEXT("diffuse"), TEXT("basecolor"), TEXT("base color") }; + TrySetupMaterialParameter(materialInstance, ToSpan(diffuseColorNames, ARRAY_COUNT(diffuseColorNames)), material.Diffuse.Color, MaterialParameterType::Color); + const Char* emissiveColorNames[] = { TEXT("emissive"), TEXT("emission"), TEXT("light") }; + TrySetupMaterialParameter(materialInstance, ToSpan(emissiveColorNames, ARRAY_COUNT(emissiveColorNames)), material.Emissive.Color, MaterialParameterType::Color); + const Char* opacityValueNames[] = { TEXT("opacity"), TEXT("alpha") }; + TrySetupMaterialParameter(materialInstance, ToSpan(opacityValueNames, ARRAY_COUNT(opacityValueNames)), material.Opacity.Value, MaterialParameterType::Float); + materialInstance->Save(); } else diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 36bb9b12c..bad10b86e 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -370,6 +370,12 @@ public: API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Splitting\")") int32 ObjectIndex = -1; + public: // Other + + // If specified, will be used as sub-directory name for automatically imported sub assets such as textures and materials. Set to whitespace (single space) to import to the same directory. + API_FIELD(Attributes="EditorOrder(3030), EditorDisplay(\"Other\")") + String SubAssetFolder = TEXT(""); + // Runtime data for objects splitting during import (used internally) void* SplitContext = nullptr; Function OnSplitImport; diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs index c08b59f48..55b58634d 100644 --- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs @@ -1399,6 +1399,12 @@ namespace FlaxEngine.GUI } case KeyboardKeys.Escape: { + if (IsReadOnly) + { + SetSelection(_selectionEnd); + return true; + } + RestoreTextFromStart(); if (!IsNavFocused) diff --git a/Source/Engine/UI/SpriteRender.cpp b/Source/Engine/UI/SpriteRender.cpp index fb4f54e7f..25446c9c5 100644 --- a/Source/Engine/UI/SpriteRender.cpp +++ b/Source/Engine/UI/SpriteRender.cpp @@ -113,7 +113,7 @@ void SpriteRender::Draw(RenderContext& renderContext) auto model = _quadModel.As(); if (model->GetLoadedLODs() == 0) return; - const auto& view = renderContext.View; + const auto& view = (renderContext.LodProxyView ? *renderContext.LodProxyView : renderContext.View); Matrix m1, m2, m3, world; Matrix::Scaling(_size.X, _size.Y, 1.0f, m2); Matrix::RotationY(PI, m3); diff --git a/Source/Engine/UI/UICanvas.cpp b/Source/Engine/UI/UICanvas.cpp index b3318c75c..a83d61f7a 100644 --- a/Source/Engine/UI/UICanvas.cpp +++ b/Source/Engine/UI/UICanvas.cpp @@ -56,6 +56,7 @@ UICanvas::UICanvas(const SpawnParams& params) UICanvas_EndPlay = mclass->GetMethod("EndPlay"); UICanvas_ParentChanged = mclass->GetMethod("ParentChanged"); UICanvas_Serialize = mclass->GetMethod("Serialize"); + Platform::MemoryBarrier(); } #endif } diff --git a/Source/Engine/Visject/ShaderGraphValue.cpp b/Source/Engine/Visject/ShaderGraphValue.cpp index 56d7c9d06..6564cde2b 100644 --- a/Source/Engine/Visject/ShaderGraphValue.cpp +++ b/Source/Engine/Visject/ShaderGraphValue.cpp @@ -40,11 +40,15 @@ ShaderGraphValue::ShaderGraphValue(const Variant& v) break; case VariantType::Float: Type = VariantType::Types::Float; - Value = String::Format(TEXT("{:.8f}"), v.AsFloat); + Value = String::Format(TEXT("{}"), v.AsFloat); + if (Value.Find('.') == -1) + Value = String::Format(TEXT("{:.1f}"), v.AsFloat); break; case VariantType::Double: Type = VariantType::Types::Float; - Value = String::Format(TEXT("{:.8f}"), (float)v.AsDouble); + Value = String::Format(TEXT("{}"), (float)v.AsDouble); + if (Value.Find('.') == -1) + Value = String::Format(TEXT("{:.1f}"), (float)v.AsDouble); break; case VariantType::Float2: { @@ -134,6 +138,29 @@ bool ShaderGraphValue::IsOne() const } } +bool ShaderGraphValue::IsLiteral() const +{ + switch (Type) + { + case VariantType::Types::Bool: + case VariantType::Types::Int: + case VariantType::Types::Uint: + case VariantType::Types::Float: + if (Value.HasChars()) + { + for (int32 i = 0; i < Value.Length(); i++) + { + const Char c = Value[i]; + if (!StringUtils::IsDigit(c) && c != '.') + return false; + } + return true; + } + default: + return false; + } +} + ShaderGraphValue ShaderGraphValue::InitForZero(VariantType::Types type) { const Char* v; diff --git a/Source/Engine/Visject/ShaderGraphValue.h b/Source/Engine/Visject/ShaderGraphValue.h index 7c7e25154..439b59077 100644 --- a/Source/Engine/Visject/ShaderGraphValue.h +++ b/Source/Engine/Visject/ShaderGraphValue.h @@ -143,8 +143,7 @@ public: /// /// Returns true if value is valid. /// - /// True if is valid, otherwise false. - bool IsValid() const + FORCE_INLINE bool IsValid() const { return Type != VariantType::Types::Null; } @@ -152,8 +151,7 @@ public: /// /// Returns true if value is invalid. /// - /// True if is invalid, otherwise false. - bool IsInvalid() const + FORCE_INLINE bool IsInvalid() const { return Type == VariantType::Types::Null; } @@ -161,15 +159,18 @@ public: /// /// Checks if value contains static part with zero. /// - /// True if contains zero number. bool IsZero() const; /// /// Checks if value contains static part with one. /// - /// True if contains one number. bool IsOne() const; + /// + /// Checks if value is a compile-time constant literal (eg. int, bool or float). + /// + bool IsLiteral() const; + /// /// Clears this instance. /// diff --git a/Source/Engine/Visject/VisjectGraph.h b/Source/Engine/Visject/VisjectGraph.h index 5836afb2f..085c05de0 100644 --- a/Source/Engine/Visject/VisjectGraph.h +++ b/Source/Engine/Visject/VisjectGraph.h @@ -262,11 +262,11 @@ protected: FORCE_INLINE Value tryGetValue(Box* box) { - return box && box->HasConnection() ? eatBox(box->GetParent(), box->FirstConnection()) : Value::Zero; + return box && box->Connections.HasItems() ? eatBox(box->GetParent(), (VisjectGraphBox*)box->Connections.Get()[0]) : Value::Zero; } FORCE_INLINE Value tryGetValue(Box* box, const Value& defaultValue) { - return box && box->HasConnection() ? eatBox(box->GetParent(), box->FirstConnection()) : defaultValue; + return box && box->Connections.HasItems() ? eatBox(box->GetParent(), (VisjectGraphBox*)box->Connections.Get()[0]) : defaultValue; } }; diff --git a/Source/Shaders/GlobalSignDistanceField.shader b/Source/Shaders/GlobalSignDistanceField.shader index 3d4f7dd46..bc4f272fc 100644 --- a/Source/Shaders/GlobalSignDistanceField.shader +++ b/Source/Shaders/GlobalSignDistanceField.shader @@ -211,7 +211,7 @@ float SampleSDF(uint3 voxelCoordMip, int3 offset) float result = GlobalSDFTex[voxelCoordMip].r; // Extend by distance to the sampled texel location - float distanceInWorldUnits = length(offset) * (MaxDistance / (float)GenerateMipTexResolution); + float distanceInWorldUnits = length((float3)offset) * (MaxDistance / (float)GenerateMipTexResolution); float distanceToVoxel = distanceInWorldUnits / MaxDistance; result = CombineDistanceToSDF(result, distanceToVoxel); diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs index c64ee38b3..6e9fbea83 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs @@ -128,7 +128,7 @@ namespace Flax.Build sourceFiles.Sort(); // Build assembly - BuildDotNet(graph, buildData, targetBuildOptions, target.OutputName, sourceFiles); + BuildDotNet(graph, buildData, targetBuildOptions, target.OutputName, sourceFiles, optimizeAssembly: buildData.TargetOptions.ScriptingAPI.Optimization); } // Deploy files @@ -152,7 +152,7 @@ namespace Flax.Build } } - private static void BuildDotNet(TaskGraph graph, BuildData buildData, BuildOptions buildOptions, string name, List sourceFiles, HashSet fileReferences = null, IGrouping binaryModule = null) + private static void BuildDotNet(TaskGraph graph, BuildData buildData, BuildOptions buildOptions, string name, List sourceFiles, HashSet fileReferences = null, IGrouping binaryModule = null, bool? optimizeAssembly = null) { // Setup build options var buildPlatform = Platform.BuildTargetPlatform; @@ -257,10 +257,8 @@ namespace Flax.Build if (buildOptions.ScriptingAPI.IgnoreMissingDocumentationWarnings) args.Add("-nowarn:1591"); - // Optimizations prevent debugging, only enable in release builds - var optimize = buildData.Configuration == TargetConfiguration.Release; - if (buildData.TargetOptions.ScriptingAPI.Optimization.HasValue) - optimize = buildData.TargetOptions.ScriptingAPI.Optimization.Value; + // Optimizations prevent debugging, only enable in release builds by default + var optimize = optimizeAssembly.HasValue ? optimizeAssembly.Value : buildData.Configuration == TargetConfiguration.Release; args.Add(optimize ? "/optimize+" : "/optimize-"); #if !USE_NETCORE args.Add(string.Format("/reference:\"{0}mscorlib.dll\"", referenceAssemblies)); @@ -374,11 +372,15 @@ namespace Flax.Build // Get references fileReferences.Clear(); + bool? optimizeAssembly = null; foreach (var module in binaryModule) { if (!buildData.Modules.TryGetValue(module, out var moduleBuildOptions)) continue; + if (moduleBuildOptions.ScriptingAPI.Optimization.HasValue) + optimizeAssembly |= moduleBuildOptions.ScriptingAPI.Optimization; + // Find references based on the modules dependencies foreach (var dependencyName in moduleBuildOptions.PublicDependencies.Concat(moduleBuildOptions.PrivateDependencies)) { @@ -408,7 +410,7 @@ namespace Flax.Build } // Build assembly - BuildDotNet(graph, buildData, buildOptions, binaryModuleName + ".CSharp", sourceFiles, fileReferences, binaryModule); + BuildDotNet(graph, buildData, buildOptions, binaryModuleName + ".CSharp", sourceFiles, fileReferences, binaryModule, optimizeAssembly); } } } diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs index 72cefbfca..e161024b4 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs @@ -225,7 +225,7 @@ namespace Flax.Build.NativeCpp public CSharpNullableReferences CSharpNullableReferences = CSharpNullableReferences.Disable; /// - /// Enable code optimization. + /// Enable code optimizations for the managed module assembly. /// public bool? Optimization; @@ -237,19 +237,13 @@ namespace Flax.Build.NativeCpp /// Adds the other options into this. /// /// The other. - public void Add(ScriptingAPIOptions other, bool addBuildOptions = true) + public void Add(ScriptingAPIOptions other) { Defines.AddRange(other.Defines); SystemReferences.AddRange(other.SystemReferences); FileReferences.AddRange(other.FileReferences); Analyzers.AddRange(other.Analyzers); IgnoreMissingDocumentationWarnings |= other.IgnoreMissingDocumentationWarnings; - - if (addBuildOptions) - { - if (other.Optimization.HasValue) - Optimization |= other.Optimization; - } } } diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs index 1e8550c4e..225e46ba7 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs @@ -403,7 +403,7 @@ namespace Flax.Build moduleOptions.PrivateIncludePaths.AddRange(dependencyOptions.PublicIncludePaths); moduleOptions.Libraries.AddRange(dependencyOptions.Libraries); moduleOptions.DelayLoadLibraries.AddRange(dependencyOptions.DelayLoadLibraries); - moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI, false); + moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI); moduleOptions.ExternalModules.AddRange(dependencyOptions.ExternalModules); } } @@ -418,7 +418,7 @@ namespace Flax.Build moduleOptions.PublicIncludePaths.AddRange(dependencyOptions.PublicIncludePaths); moduleOptions.Libraries.AddRange(dependencyOptions.Libraries); moduleOptions.DelayLoadLibraries.AddRange(dependencyOptions.DelayLoadLibraries); - moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI, false); + moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI); moduleOptions.ExternalModules.AddRange(dependencyOptions.ExternalModules); } } diff --git a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs index 887197258..a044adc0a 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs @@ -166,7 +166,7 @@ namespace Flax.Build.Projects.VisualStudioCode foreach (var configuration in project.Configurations) { var target = configuration.Target; - var name = project.Name + '|' + configuration.Name; + var name = solution.Name + '|' + configuration.Name; json.BeginObject(); @@ -294,132 +294,121 @@ namespace Flax.Build.Projects.VisualStudioCode json.BeginArray("configurations"); { - foreach (var project in solution.Projects) + var cppProject = solution.Projects.FirstOrDefault(x => x.BaseName == solution.Name || x.Name == solution.Name); + var csharpProject = solution.Projects.FirstOrDefault(x => x.BaseName == solution.MainProject.Targets[0].Modules[0] || x.Name == solution.MainProject.Targets[0].Modules[0]); + + if (cppProject != null) { - if (project.Name == "BuildScripts") - continue; // C++ project - if (project.Type == TargetType.NativeCpp) + foreach (var configuration in cppProject.Configurations) { - // Skip generating launch profiles for plugins and dependencies - if (solution.MainProject.Name != "Flax" && project.Name != "Flax.Build" && solution.MainProject.WorkspaceRootPath != project.WorkspaceRootPath) - continue; + var name = solution.Name + '|' + configuration.Name + " (C++)"; + var target = configuration.Target; + var outputType = cppProject.OutputType ?? target.OutputType; + var outputTargetFilePath = target.GetOutputFilePath(configuration.TargetBuildOptions, TargetOutputType.Executable); - foreach (var configuration in project.Configurations) + json.BeginObject(); { - var name = project.Name + '|' + configuration.Name; - var target = configuration.Target; + if (configuration.Platform == TargetPlatform.Windows) + json.AddField("type", "cppvsdbg"); + else + json.AddField("type", "cppdbg"); + json.AddField("name", name); + json.AddField("request", "launch"); + json.AddField("preLaunchTask", solution.Name + '|' + configuration.Name); + json.AddField("cwd", buildToolWorkspace); - var outputType = project.OutputType ?? target.OutputType; - var outputTargetFilePath = target.GetOutputFilePath(configuration.TargetBuildOptions, TargetOutputType.Executable); + WriteNativePlatformLaunchSettings(json, configuration.Platform); - json.BeginObject(); + if (outputType != TargetOutputType.Executable && configuration.Name.StartsWith("Editor.")) { if (configuration.Platform == TargetPlatform.Windows) - json.AddField("type", "cppvsdbg"); - else - json.AddField("type", "cppdbg"); - json.AddField("name", name); - json.AddField("request", "launch"); - json.AddField("preLaunchTask", name); - json.AddField("cwd", buildToolWorkspace); - - WriteNativePlatformLaunchSettings(json, configuration.Platform); - - if (outputType != TargetOutputType.Executable && configuration.Name.StartsWith("Editor.")) { - if (configuration.Platform == TargetPlatform.Windows) - { - var editorFolder = configuration.Architecture == TargetArchitecture.x64 ? "Win64" : "Win32"; - json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", editorFolder, configuration.ConfigurationName, "FlaxEditor.exe")); - } - else if (configuration.Platform == TargetPlatform.Linux) - json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", "Linux", configuration.ConfigurationName, "FlaxEditor")); - else if (configuration.Platform == TargetPlatform.Mac) - json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", "Mac", configuration.ConfigurationName, "FlaxEditor")); - - json.BeginArray("args"); - { - json.AddUnnamedField("-project"); - json.AddUnnamedField(buildToolWorkspace); - json.AddUnnamedField("-skipCompile"); - if (hasMonoProjects) - { - json.AddUnnamedField("-debug"); - json.AddUnnamedField("127.0.0.1:55555"); - } - } - json.EndArray(); + var editorFolder = configuration.Architecture == TargetArchitecture.x64 ? "Win64" : "Win32"; + json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", editorFolder, configuration.ConfigurationName, "FlaxEditor.exe")); } - else + else if (configuration.Platform == TargetPlatform.Linux) + json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", "Linux", configuration.ConfigurationName, "FlaxEditor")); + else if (configuration.Platform == TargetPlatform.Mac) + json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", "Mac", configuration.ConfigurationName, "FlaxEditor")); + + json.BeginArray("args"); { - json.AddField("program", outputTargetFilePath); - json.BeginArray("args"); + json.AddUnnamedField("-project"); + json.AddUnnamedField(buildToolWorkspace); + json.AddUnnamedField("-skipCompile"); + if (hasMonoProjects) { - if (configuration.Platform == TargetPlatform.Linux || configuration.Platform == TargetPlatform.Mac) - json.AddUnnamedField("--std"); - if (hasMonoProjects) - { - json.AddUnnamedField("-debug"); - json.AddUnnamedField("127.0.0.1:55555"); - } + json.AddUnnamedField("-debug"); + json.AddUnnamedField("127.0.0.1:55555"); } - json.EndArray(); } - - json.AddField("visualizerFile", Path.Combine(Globals.EngineRoot, "Source", "flax.natvis")); + json.EndArray(); } - json.EndObject(); - } - } - // C# project - else if (project.Type == TargetType.DotNetCore) - { - // Skip generating launch profiles for plugins and dependencies - if (solution.MainProject.WorkspaceRootPath != project.WorkspaceRootPath) - continue; - - foreach (var configuration in project.Configurations) - { - var name = project.Name + '|' + configuration.Name + " (C#)"; - var outputTargetFilePath = configuration.Target.GetOutputFilePath(configuration.TargetBuildOptions, TargetOutputType.Executable); - - json.BeginObject(); + else { - json.AddField("type", "coreclr"); - json.AddField("name", name); - json.AddField("request", "launch"); - json.AddField("preLaunchTask", solution.Name + '|' + configuration.Name); - json.AddField("cwd", buildToolWorkspace); - if (configuration.Platform == Platform.BuildPlatform.Target) + json.AddField("program", outputTargetFilePath); + json.BeginArray("args"); { - var editorFolder = configuration.Platform == TargetPlatform.Windows ? (configuration.Architecture == TargetArchitecture.x64 ? "Win64" : "Win32") : configuration.Platform.ToString(); - json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", editorFolder, configuration.ConfigurationName, "FlaxEditor")); - json.BeginArray("args"); + if (configuration.Platform == TargetPlatform.Linux || configuration.Platform == TargetPlatform.Mac) + json.AddUnnamedField("--std"); + if (hasMonoProjects) { - json.AddUnnamedField("-project"); - json.AddUnnamedField(buildToolWorkspace); - json.AddUnnamedField("-skipCompile"); + json.AddUnnamedField("-debug"); + json.AddUnnamedField("127.0.0.1:55555"); } - json.EndArray(); - } - else - { - json.AddField("program", configuration.Target.GetOutputFilePath(configuration.TargetBuildOptions, TargetOutputType.Executable)); - } - - switch (configuration.Platform) - { - case TargetPlatform.Windows: - json.AddField("stopAtEntry", false); - json.AddField("externalConsole", true); - break; - case TargetPlatform.Linux: - break; } + json.EndArray(); } - json.EndObject(); + + json.AddField("visualizerFile", Path.Combine(Globals.EngineRoot, "Source", "flax.natvis")); } + json.EndObject(); + } + } + if (csharpProject != null) + { + // C# project + foreach (var configuration in csharpProject.Configurations) + { + var name = solution.Name + '|' + configuration.Name + " (C#)"; + var outputTargetFilePath = configuration.Target.GetOutputFilePath(configuration.TargetBuildOptions, TargetOutputType.Executable); + + json.BeginObject(); + { + json.AddField("type", "coreclr"); + json.AddField("name", name); + json.AddField("request", "launch"); + json.AddField("preLaunchTask", solution.Name + '|' + configuration.Name); + json.AddField("cwd", buildToolWorkspace); + if (configuration.Platform == Platform.BuildPlatform.Target) + { + var editorFolder = configuration.Platform == TargetPlatform.Windows ? (configuration.Architecture == TargetArchitecture.x64 ? "Win64" : "Win32") : configuration.Platform.ToString(); + json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", editorFolder, configuration.ConfigurationName, "FlaxEditor")); + json.BeginArray("args"); + { + json.AddUnnamedField("-project"); + json.AddUnnamedField(buildToolWorkspace); + json.AddUnnamedField("-skipCompile"); + } + json.EndArray(); + } + else + { + json.AddField("program", configuration.Target.GetOutputFilePath(configuration.TargetBuildOptions, TargetOutputType.Executable)); + } + + switch (configuration.Platform) + { + case TargetPlatform.Windows: + json.AddField("stopAtEntry", false); + json.AddField("externalConsole", true); + break; + case TargetPlatform.Linux: + break; + } + } + json.EndObject(); } } } @@ -428,6 +417,9 @@ namespace Flax.Build.Projects.VisualStudioCode { foreach (var platform in solution.Projects.SelectMany(x => x.Configurations).Select(x => x.Platform).Distinct()) { + if (platform != TargetPlatform.Windows && platform != TargetPlatform.Linux && platform != TargetPlatform.Mac) + continue; + json.BeginObject(); { if (platform == TargetPlatform.Windows)