diff --git a/Content/Editor/IconsAtlas.flax b/Content/Editor/IconsAtlas.flax index cd1c745a2..01baadfed 100644 --- a/Content/Editor/IconsAtlas.flax +++ b/Content/Editor/IconsAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2248c3069f16a3b1eb62aa4660c81427fd6effa364f8f0694ba751be8e60114c -size 5612622 +oid sha256:c2a7d0c6969a180d59a32fbc908fe432bddb393437e9c5b64ddb25737e4aab94 +size 5609840 diff --git a/Content/Editor/IconsAtlas.psd b/Content/Editor/IconsAtlas.psd new file mode 100644 index 000000000..b062fc0fa --- /dev/null +++ b/Content/Editor/IconsAtlas.psd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ee4e46c2b39cf6def6be079a898204e283253bf5841ccf3985fa9d49834b9a0 +size 2766306 diff --git a/Source/Editor/Content/Import/AudioImportSettings.cs b/Source/Editor/Content/Import/AudioImportSettings.cs index b645c1509..a0af8c154 100644 --- a/Source/Editor/Content/Import/AudioImportSettings.cs +++ b/Source/Editor/Content/Import/AudioImportSettings.cs @@ -12,7 +12,7 @@ namespace FlaxEngine.Tools { partial struct Options { - private bool ShowBtiDepth => Format != AudioFormat.Vorbis; + private bool ShowBitDepth => Format != AudioFormat.Vorbis; } } } diff --git a/Source/Editor/Content/Import/ModelImportEntry.cs b/Source/Editor/Content/Import/ModelImportEntry.cs index 5e17d7ecf..eed8db6ba 100644 --- a/Source/Editor/Content/Import/ModelImportEntry.cs +++ b/Source/Editor/Content/Import/ModelImportEntry.cs @@ -19,6 +19,7 @@ namespace FlaxEngine.Tools private bool ShowRootMotion => ShowAnimation && RootMotion != RootMotionMode.None; private bool ShowSmoothingNormalsAngle => ShowGeometry && CalculateNormals; private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents; + private bool ShowGenerateLODs => ShowGeometry && GenerateLODs; private bool ShowFramesRange => ShowAnimation && Duration == AnimationDuration.Custom; private bool ShowSplitting => Type != ModelType.Prefab; } diff --git a/Source/Editor/Content/Items/AssetItem.cs b/Source/Editor/Content/Items/AssetItem.cs index eaa93561a..78519b400 100644 --- a/Source/Editor/Content/Items/AssetItem.cs +++ b/Source/Editor/Content/Items/AssetItem.cs @@ -98,12 +98,12 @@ namespace FlaxEditor.Content } /// - /// Reloads the asset (if it's loaded). + /// Reloads the asset (if it's loaded or failed to load). /// public void Reload() { var asset = FlaxEngine.Content.GetAsset(ID); - if (asset != null && asset.IsLoaded) + if (asset != null && (asset.IsLoaded || asset.LastLoadFailed)) { asset.Reload(); } diff --git a/Source/Editor/Content/Proxy/MaterialBaseProxy.cs b/Source/Editor/Content/Proxy/MaterialBaseProxy.cs new file mode 100644 index 000000000..83c98ee6e --- /dev/null +++ b/Source/Editor/Content/Proxy/MaterialBaseProxy.cs @@ -0,0 +1,109 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +using FlaxEditor.Content.Thumbnails; +using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.Viewport.Previews; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.Content +{ + /// + /// A base class for asset proxy object. + /// + /// + public abstract class MaterialBaseProxy : BinaryAssetProxy + { + /// + /// The material preview drawer. + /// + protected MaterialPreview _preview; + + /// + public override bool CanCreate(ContentFolder targetLocation) + { + return targetLocation.CanHaveAssets; + } + + /// + public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item) + { + base.OnContentWindowContextMenu(menu, item); + + if (item is BinaryAssetItem binaryAssetItem) + { + var button = menu.AddButton("Create Material Instance", CreateMaterialInstanceClicked); + button.Tag = binaryAssetItem; + } + } + + private void CreateMaterialInstanceClicked(ContextMenuButton button) + { + var binaryAssetItem = (BinaryAssetItem)button.Tag; + CreateMaterialInstance(binaryAssetItem); + } + + /// + /// Creates the material instance from the given material. + /// + /// The material item to use as a base material. + public static void CreateMaterialInstance(BinaryAssetItem materialItem) + { + var materialInstanceName = materialItem.ShortName + " Instance"; + var materialInstanceProxy = Editor.Instance.ContentDatabase.GetProxy(); + Editor.Instance.Windows.ContentWin.NewItem(materialInstanceProxy, null, item => OnMaterialInstanceCreated(item, materialItem), materialInstanceName); + } + + private static void OnMaterialInstanceCreated(ContentItem item, BinaryAssetItem materialItem) + { + var assetItem = (AssetItem)item; + var materialInstance = FlaxEngine.Content.LoadAsync(assetItem.ID); + if (materialInstance == null || materialInstance.WaitForLoaded()) + { + Editor.LogError("Failed to load created material instance."); + return; + } + materialInstance.BaseMaterial = FlaxEngine.Content.LoadAsync(materialItem.ID); + materialInstance.Save(); + } + + /// + public override void OnThumbnailDrawPrepare(ThumbnailRequest request) + { + if (_preview == null) + { + _preview = new MaterialPreview(false); + InitAssetPreview(_preview); + } + } + + /// + public override void OnThumbnailDrawBegin(ThumbnailRequest request, ContainerControl guiRoot, GPUContext context) + { + _preview.Material = (MaterialBase)request.Asset; + _preview.Parent = guiRoot; + _preview.SyncBackbufferSize(); + + _preview.Task.OnDraw(); + } + + /// + public override void OnThumbnailDrawEnd(ThumbnailRequest request, ContainerControl guiRoot) + { + _preview.Material = null; + _preview.Parent = null; + } + + /// + public override void Dispose() + { + if (_preview != null) + { + _preview.Dispose(); + _preview = null; + } + + base.Dispose(); + } + } +} diff --git a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs index fc4fcdbc1..212417e9f 100644 --- a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs @@ -2,23 +2,18 @@ using System; using FlaxEditor.Content.Thumbnails; -using FlaxEditor.Viewport.Previews; using FlaxEditor.Windows; using FlaxEditor.Windows.Assets; using FlaxEngine; -using FlaxEngine.GUI; namespace FlaxEditor.Content { /// /// A asset proxy object. /// - /// [ContentContextMenu("New/Material/Material Instance")] - public class MaterialInstanceProxy : BinaryAssetProxy + public class MaterialInstanceProxy : MaterialBaseProxy { - private MaterialPreview _preview; - /// public override string Name => "Material Instance"; @@ -34,12 +29,6 @@ namespace FlaxEditor.Content /// public override Type AssetType => typeof(MaterialInstance); - /// - public override bool CanCreate(ContentFolder targetLocation) - { - return targetLocation.CanHaveAssets; - } - /// public override void Create(string outputPath, object arg) { @@ -47,49 +36,10 @@ namespace FlaxEditor.Content throw new Exception("Failed to create new asset."); } - /// - public override void OnThumbnailDrawPrepare(ThumbnailRequest request) - { - if (_preview == null) - { - _preview = new MaterialPreview(false); - InitAssetPreview(_preview); - } - } - /// public override bool CanDrawThumbnail(ThumbnailRequest request) { return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((MaterialInstance)request.Asset); } - - /// - public override void OnThumbnailDrawBegin(ThumbnailRequest request, ContainerControl guiRoot, GPUContext context) - { - _preview.Material = (MaterialInstance)request.Asset; - _preview.Parent = guiRoot; - _preview.SyncBackbufferSize(); - - _preview.Task.OnDraw(); - } - - /// - public override void OnThumbnailDrawEnd(ThumbnailRequest request, ContainerControl guiRoot) - { - _preview.Material = null; - _preview.Parent = null; - } - - /// - public override void Dispose() - { - if (_preview != null) - { - _preview.Dispose(); - _preview = null; - } - - base.Dispose(); - } } } diff --git a/Source/Editor/Content/Proxy/MaterialProxy.cs b/Source/Editor/Content/Proxy/MaterialProxy.cs index 4769ca548..58d34299c 100644 --- a/Source/Editor/Content/Proxy/MaterialProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialProxy.cs @@ -2,24 +2,18 @@ using System; using FlaxEditor.Content.Thumbnails; -using FlaxEditor.GUI.ContextMenu; -using FlaxEditor.Viewport.Previews; using FlaxEditor.Windows; using FlaxEditor.Windows.Assets; using FlaxEngine; -using FlaxEngine.GUI; namespace FlaxEditor.Content { /// /// A asset proxy object. /// - /// [ContentContextMenu("New/Material/Material")] - public class MaterialProxy : BinaryAssetProxy + public class MaterialProxy : MaterialBaseProxy { - private MaterialPreview _preview; - /// public override string Name => "Material"; @@ -35,12 +29,6 @@ namespace FlaxEditor.Content /// public override Type AssetType => typeof(Material); - /// - public override bool CanCreate(ContentFolder targetLocation) - { - return targetLocation.CanHaveAssets; - } - /// public override void Create(string outputPath, object arg) { @@ -48,92 +36,10 @@ namespace FlaxEditor.Content throw new Exception("Failed to create new asset."); } - /// - public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item) - { - base.OnContentWindowContextMenu(menu, item); - - if (item is BinaryAssetItem binaryAssetItem) - { - var button = menu.AddButton("Create Material Instance", CreateMaterialInstanceClicked); - button.Tag = binaryAssetItem; - } - } - - private void CreateMaterialInstanceClicked(ContextMenuButton obj) - { - var binaryAssetItem = (BinaryAssetItem)obj.Tag; - CreateMaterialInstance(binaryAssetItem); - } - - /// - /// Creates the material instance from the given material. - /// - /// The material item to use as a base material. - public static void CreateMaterialInstance(BinaryAssetItem materialItem) - { - var materialInstanceName = materialItem.ShortName + " Instance"; - var materialInstanceProxy = Editor.Instance.ContentDatabase.GetProxy(); - Editor.Instance.Windows.ContentWin.NewItem(materialInstanceProxy, null, item => OnMaterialInstanceCreated(item, materialItem), materialInstanceName); - } - - private static void OnMaterialInstanceCreated(ContentItem item, BinaryAssetItem materialItem) - { - var assetItem = (AssetItem)item; - var materialInstance = FlaxEngine.Content.LoadAsync(assetItem.ID); - if (materialInstance == null || materialInstance.WaitForLoaded()) - { - Editor.LogError("Failed to load created material instance."); - return; - } - - materialInstance.BaseMaterial = FlaxEngine.Content.LoadAsync(materialItem.ID); - materialInstance.Save(); - } - - /// - public override void OnThumbnailDrawPrepare(ThumbnailRequest request) - { - if (_preview == null) - { - _preview = new MaterialPreview(false); - InitAssetPreview(_preview); - } - } - /// public override bool CanDrawThumbnail(ThumbnailRequest request) { return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Material)request.Asset); } - - /// - public override void OnThumbnailDrawBegin(ThumbnailRequest request, ContainerControl guiRoot, GPUContext context) - { - _preview.Material = (Material)request.Asset; - _preview.Parent = guiRoot; - _preview.SyncBackbufferSize(); - - _preview.Task.OnDraw(); - } - - /// - public override void OnThumbnailDrawEnd(ThumbnailRequest request, ContainerControl guiRoot) - { - _preview.Material = null; - _preview.Parent = null; - } - - /// - public override void Dispose() - { - if (_preview != null) - { - _preview.Dispose(); - _preview = null; - } - - base.Dispose(); - } } } diff --git a/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp b/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp index a2c35f38f..e38a7310c 100644 --- a/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp @@ -191,7 +191,7 @@ bool WebPlatformTools::OnPostProcess(CookingData& data) FileSystem::GetChildDirectories(pythons, emscriptenSdk / TEXT("/python")); if (pythons.HasItems()) { - procSettings.Arguments = procSettings.FileName + TEXT(".py ") + procSettings.Arguments; + procSettings.Arguments = String::Format(TEXT("\"{}.py\" {}"), procSettings.FileName, procSettings.Arguments); #if PLATFORM_WINDOWS procSettings.FileName = pythons[0] / TEXT("/python.exe"); #else diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 50c08e9ba..5f09342c7 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -589,6 +589,8 @@ namespace FlaxEditor.GUI // Get the next item bool controlDown = Root.GetKey(KeyboardKeys.Control); var items = GetVisibleItems(!controlDown); + if (items.Count == 0) + return true; var focusedIndex = items.IndexOf(focusedItem); int delta = key == KeyboardKeys.ArrowDown ? -1 : 1; diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index 1fb19e9ec..861bd7e44 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -229,6 +229,7 @@ namespace FlaxEditor.GUI.Timeline private List _mediaMoveStartTracks; private byte[][] _mediaMoveStartData; private float _zoom = 1.0f; + private float _tracksVScrollTarget; private bool _isMovingPositionHandle; private bool _canPlayPause = true, _canStop = true; private List _batchedUndoActions; @@ -1301,10 +1302,13 @@ namespace FlaxEditor.GUI.Timeline if (track.ParentTrack != null) OnTracksOrderChanged(); track.OnSpawned(); - _tracksPanelArea.ScrollViewTo(track); MarkAsEdited(); if (withUndo) Undo?.AddAction(new AddRemoveTrackAction(this, track, true)); + + // Scroll to track + _tracksPanelArea.ScrollViewTo(track); + _tracksVScrollTarget = _tracksPanelArea.VScrollBar.TargetValue; } /// @@ -2033,12 +2037,24 @@ namespace FlaxEditor.GUI.Timeline base.Update(deltaTime); // Synchronize scroll vertical bars for tracks and media panels to keep the view in sync - var scroll1 = _tracksPanelArea.VScrollBar; - var scroll2 = _backgroundArea.VScrollBar; - if (scroll1.IsThumbClicked || _tracksPanelArea.IsMouseOver) - scroll2.TargetValue = scroll1.Value; + var tracksVScroll = _tracksPanelArea.VScrollBar; + var backgroundVScroll = _backgroundArea.VScrollBar; + bool forceBackgroundToTracksScroll = _tracksVScrollTarget > 0; + if (forceBackgroundToTracksScroll) + { + backgroundVScroll.TargetValue = tracksVScroll.Value; + + if (Mathf.Abs(tracksVScroll.Value - _tracksVScrollTarget) < 0.5f) + _tracksVScrollTarget = 0f; + } + else if (tracksVScroll.IsThumbClicked || _tracksPanelArea.IsMouseOver) + { + backgroundVScroll.TargetValue = tracksVScroll.Value; + } else - scroll1.TargetValue = scroll2.Value; + { + tracksVScroll.TargetValue = backgroundVScroll.Value; + } // Batch undo actions if (_batchedUndoActions != null && _batchedUndoActions.Count != 0) diff --git a/Source/Editor/GUI/WindowDecorations.cs b/Source/Editor/GUI/WindowDecorations.cs index 74dc38273..7d7bc5dd5 100644 --- a/Source/Editor/GUI/WindowDecorations.cs +++ b/Source/Editor/GUI/WindowDecorations.cs @@ -181,7 +181,7 @@ public class WindowDecorations : ContainerControl return WindowHitCodes.NoWhere; var dpiScale = _window.DpiScale; - var pos = _window.ScreenToClient(mouse * dpiScale); // pos is not DPI adjusted + var pos = _window.ScreenToClient(mouse * dpiScale); // pos is DPI adjusted in window space if (!_window.IsMaximized) { var winSize = _window.Size; @@ -214,6 +214,7 @@ public class WindowDecorations : ContainerControl return WindowHitCodes.Bottom; } + pos /= dpiScale; // The position should not be DPI adjusted in control space var controlUnderMouse = GetChildAt(pos, control => control != _title); if (_title.Bounds.Contains(pos) && controlUnderMouse == null) return WindowHitCodes.Caption; diff --git a/Source/Editor/Gizmo/DirectionGizmo.cs b/Source/Editor/Gizmo/DirectionGizmo.cs index aedfe73d0..0eee9f916 100644 --- a/Source/Editor/Gizmo/DirectionGizmo.cs +++ b/Source/Editor/Gizmo/DirectionGizmo.cs @@ -36,8 +36,10 @@ internal class DirectionGizmo : ContainerControl private List _axisData = new List(); private int _hoveredAxisIndex = -1; + private bool _mouseDown; + private Float2 _mouseDownLocation; + private SpriteHandle _posHandle; - private SpriteHandle _negHandle; private FontReference _fontReference; @@ -110,7 +112,6 @@ internal class DirectionGizmo : ContainerControl var editor = Editor.Instance; _posHandle = editor.Icons.VisjectBoxClosed32; - _negHandle = editor.Icons.VisjectBoxOpen32; _fontReference = new FontReference(Style.Current.FontSmall); @@ -142,7 +143,25 @@ internal class DirectionGizmo : ContainerControl public override void OnMouseMove(Float2 location) { _hoveredAxisIndex = -1; + + if (_mouseDown) + { + StartMouseCapture(true); + Cursor = CursorType.Hidden; + const float sensitivity = 0.125f; + Float2 delta = Input.MousePositionDelta; + delta *= Mathf.DegreesToRadians; + delta *= sensitivity; + + const float orbitRadius = 500f; + Quaternion newOrientation = _viewport.ViewOrientation * Quaternion.RotationYawPitchRoll(delta.X , delta.Y, 0f); + Vector3 orbitCenter = _viewport.ViewPosition + _viewport.ViewDirection * orbitRadius; + _viewport.ViewportCamera.SetArcBallView(newOrientation, orbitCenter, orbitRadius); + + return; + } + // Check which axis is being hovered - check from closest to farthest for proper layering for (int i = _spritePositions.Count - 1; i >= 0; i--) { @@ -156,9 +175,30 @@ internal class DirectionGizmo : ContainerControl base.OnMouseMove(location); } + public override bool OnMouseDown(Float2 location, MouseButton button) + { + _mouseDown = true; + _mouseDownLocation = location; + return true; + } + /// public override bool OnMouseUp(Float2 location, MouseButton button) { + if (_mouseDown) + { + _mouseDown = false; + + if (_mouseDownLocation != location) + { + _mouseDown = false; + EndMouseCapture(); + Root.MousePosition = PointToParent(Root, _mouseDownLocation); + Cursor = CursorType.Default; + return true; + } + } + if (base.OnMouseUp(location, button)) return true; @@ -269,7 +309,12 @@ internal class DirectionGizmo : ContainerControl // Rebuild sprite positions list for hover detection _spritePositions.Clear(); - Render2D.DrawSprite(_posHandle, new Rectangle(0, 0, Size), Color.Black.AlphaMultiplied(_backgroundOpacity)); + if (IsMouseOver) + { + Rectangle backgroundRect = new Rectangle(0, 0, Size); + Color backgroundColor = Color.DarkGray.AlphaMultiplied(_backgroundOpacity); + Render2D.DrawSprite(_posHandle, backgroundRect, backgroundColor); + } // Draw in order from farthest to closest for (int i = 0; i < _axisData.Count; i++) diff --git a/Source/Editor/Options/ViewportOptions.cs b/Source/Editor/Options/ViewportOptions.cs index c7f6ba544..2dd08b6d1 100644 --- a/Source/Editor/Options/ViewportOptions.cs +++ b/Source/Editor/Options/ViewportOptions.cs @@ -187,7 +187,7 @@ namespace FlaxEditor.Options public float DirectionGizmoScale { get; set; } = 1f; /// - /// Gets or sets a value for the opacity of the main viewports background. + /// Gets or sets a value for the opacity of the main viewports background. Background will only show when the gizmo is hovered. /// [DefaultValue(0.1f), Limit(0.0f, 1.0f)] [EditorDisplay("Direction Gizmo"), EditorOrder(502), Tooltip("The background opacity of the of the direction gizmo in the main viewport.")] diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp index b63815dce..de6001efd 100644 --- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp @@ -14,6 +14,9 @@ #if PLATFORM_WINDOWS #include "Engine/Platform/Win32/IncludeWindowsHeaders.h" +#elif PLATFORM_MAC +#include "Engine/Platform/Apple/AppleUtils.h" +#include #endif namespace @@ -68,10 +71,14 @@ namespace if (!launcherPath.HasChars() || !FileSystem::FileExists(exePath)) return; - if (launchOverridePath != String::Empty) - installations->Add(New(launchOverridePath, versionMember->value.GetText())); - else - installations->Add(New(exePath, versionMember->value.GetText())); + String installPath = launchOverridePath != String::Empty ? launchOverridePath : exePath; + StringUtils::PathRemoveRelativeParts(installPath); + for (RiderInstallation* installation : *installations) + { + if (installation->path == installPath) + return; + } + installations->Add(New(installPath, versionMember->value.GetText())); } #if PLATFORM_WINDOWS @@ -221,17 +228,29 @@ void RiderCodeEditor::FindEditors(Array* output) String applicationSupportFolder; FileSystem::GetSpecialFolderPath(SpecialFolder::ProgramData, applicationSupportFolder); + NSURL* appURL = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:@"com.jetbrains.rider"]; + if (appURL != nullptr) + { + const String appPath = AppleUtils::ToString((CFStringRef)[appURL path]); + SearchDirectory(&installations, appPath / TEXT("Contents/Resources"), appPath); + } + Array subMacDirectories; FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/Rider/ch-0/")); FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/Rider/ch-1/")); for (const String& directory : subMacDirectories) { - String riderAppDirectory = directory / TEXT("Rider.app/Contents/Resources"); - SearchDirectory(&installations, riderAppDirectory); + String riderAppPath = directory / TEXT("Rider.app"); + SearchDirectory(&installations, riderAppPath / TEXT("Contents/Resources"), riderAppPath); } // Check the local installer version - SearchDirectory(&installations, TEXT("/Applications/Rider.app/Contents/Resources")); + SearchDirectory(&installations, TEXT("/Applications/Rider.app/Contents/Resources"), TEXT("/Applications/Rider.app")); + + String userFolder; + FileSystem::GetSpecialFolderPath(SpecialFolder::Documents, userFolder); + String riderAppPath = userFolder / TEXT("../Applications/Rider.app"); + SearchDirectory(&installations, riderAppPath / TEXT("Contents/Resources"), riderAppPath); #endif for (const String& directory : subDirectories) diff --git a/Source/Editor/Tools/VertexPainting.cs b/Source/Editor/Tools/VertexPainting.cs index 643441c46..dea5247f4 100644 --- a/Source/Editor/Tools/VertexPainting.cs +++ b/Source/Editor/Tools/VertexPainting.cs @@ -8,6 +8,7 @@ using FlaxEditor.Gizmo; using FlaxEditor.GUI.Tabs; using FlaxEditor.Modules; using FlaxEditor.SceneGraph; +using FlaxEditor.Utilities; using FlaxEditor.Viewport.Modes; using FlaxEngine; using FlaxEngine.GUI; @@ -307,7 +308,7 @@ namespace FlaxEditor.Tools public VertexPaintingGizmo Gizmo; public VertexColorsPreviewMode PreviewMode = VertexColorsPreviewMode.RGB; - public float PreviewVertexSize = 6.0f; + public float PreviewVertexSize = 4.0f; public float BrushSize = 100.0f; public float BrushStrength = 1.0f; public float BrushFalloff = 1.0f; @@ -402,7 +403,7 @@ namespace FlaxEditor.Tools if (meshDatas == null) throw new Exception("Missing mesh data of the model to paint."); var instanceTransform = _selectedModel.Transform; - var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize); + var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize * 0.5f); if (_paintUpdateCount == 0 && !_selectedModel.HasVertexColors) { // Initialize the instance vertex colors with originals from the asset @@ -509,6 +510,13 @@ namespace FlaxEditor.Tools return; } + // Increase or decrease brush size with scroll + if (Input.GetKey(KeyboardKeys.Shift) && !Input.GetMouseButton(MouseButton.Right)) + { + _gizmoMode.BrushSize += dt * _gizmoMode.BrushSize * Input.Mouse.ScrollDelta * 5f; + _gizmoMode.BrushSize = Mathf.Clamp(_gizmoMode.BrushSize, 0.0001f, 100000.0f); + } + // Perform detailed tracing to find cursor location for the brush var ray = Owner.MouseRay; var view = new Ray(Owner.ViewPosition, Owner.ViewDirection); @@ -570,7 +578,7 @@ namespace FlaxEditor.Tools } if (_brushModel && _brushMaterial) { - _brushMaterial.SetParameterValue("Color", new Color(1.0f, 0.85f, 0.0f)); // TODO: expose to editor options + _brushMaterial.SetParameterValue("Color", new Color(1.0f, 0.85f, 0.0f)); _brushMaterial.SetParameterValue("DepthBuffer", Owner.RenderTask.Buffers.DepthBuffer); Quaternion rotation = RootNode.RaycastNormalRotation(ref _hitNormal); Matrix transform = Matrix.Scaling(_gizmoMode.BrushSize * 0.01f) * Matrix.RotationQuaternion(rotation) * Matrix.Translation(_hitLocation - viewOrigin); @@ -586,8 +594,10 @@ namespace FlaxEditor.Tools _verticesPreviewMaterial = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.WiresDebugMaterial); } var instanceTransform = _selectedModel.Transform; - var modelScaleMatrix = Matrix.Scaling(_gizmoMode.PreviewVertexSize * 0.01f); - var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize); + var distanceScale = (float)Vector3.Distance(instanceTransform.Translation, renderContext.View.Position) / (10.0f * Units.Meters2Units); + var vertexScale = Mathf.Lerp(0.005f, 0.01f, Mathf.Saturate(distanceScale)); + var modelScaleMatrix = Matrix.Scaling(_gizmoMode.PreviewVertexSize * vertexScale); + var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize * 0.5f); var lodIndex = _gizmoMode.ModelLOD == -1 ? RenderTools.ComputeModelLOD(_selectedModel.Model, ref renderContext.View.Position, (float)_selectedModel.Sphere.Radius, ref renderContext) : _gizmoMode.ModelLOD; lodIndex = Mathf.Clamp(lodIndex, 0, meshDatas.Length - 1); var lodData = meshDatas[lodIndex]; diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index debec1a14..ab49f0e20 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -714,7 +714,7 @@ namespace FlaxEditor.Viewport { base.OnLeftMouseButtonDown(); - if (!IsAltKeyDown) + if (!IsAltKeyDown && !_directionGizmo.IsMouseOver) _rubberBandSelector.TryStartingRubberBandSelection(_viewMousePos); } @@ -722,7 +722,7 @@ namespace FlaxEditor.Viewport protected override void OnLeftMouseButtonUp() { // Skip if was controlling mouse or mouse is not over the area - if (_prevInput.IsControllingMouse || !Bounds.Contains(ref _viewMousePos)) + if (_prevInput.IsControllingMouse || !Bounds.Contains(ref _viewMousePos) || _directionGizmo.IsMouseOver) return; // Select rubberbanded rect actor nodes or pick with gizmo diff --git a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs index 10dcdf671..a5ff3758b 100644 --- a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs +++ b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs @@ -2,6 +2,7 @@ using System; using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.GUI.Input; using FlaxEngine; using Object = FlaxEngine.Object; @@ -14,7 +15,7 @@ namespace FlaxEditor.Viewport.Previews public class AnimatedModelPreview : AssetPreview { private AnimatedModel _previewModel; - private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton; + private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton, _nodeNameSizeButton; private bool _showNodes, _showBounds, _showFloor, _showNodesNames; private StaticModel _floorModel; private bool _playAnimation, _playAnimationOnce, _autoAdjustCamera = true; @@ -110,9 +111,16 @@ namespace FlaxEditor.Viewport.Previews ShowDebugDraw = true; if (_showNodesNamesButton != null) _showNodesNamesButton.Checked = value; + if (_nodeNameSizeButton != null) + _nodeNameSizeButton.Enabled = value; } } + /// + /// The font size used in the node name debug draw. + /// + public int NodeNamesSize = 10; + /// /// Gets or sets a value indicating whether show animated model bounding box debug view. /// @@ -208,6 +216,16 @@ namespace FlaxEditor.Viewport.Previews _showFloorButton = ViewWidgetShowMenu.AddButton("Floor", button => ShowFloor = !ShowFloor); _showFloorButton.IndexInParent = 1; _showFloorButton.CloseMenuOnClick = false; + + // Skeleton Names Size + _nodeNameSizeButton = ViewWidgetButtonMenu.AddButton("Skeleton Names Size"); + _nodeNameSizeButton.CloseMenuOnClick = false; + var nodeNameSizeValue = new IntValueBox(NodeNamesSize, 118, 2, 70.0f, 1, 32) + { + Parent = _nodeNameSizeButton + }; + _nodeNameSizeButton.Enabled = ShowNodesNames; + nodeNameSizeValue.ValueChanged += () => NodeNamesSize = nodeNameSizeValue.Value; } // Enable shadows @@ -378,7 +396,7 @@ namespace FlaxEditor.Viewport.Previews if (nodesMask != null && !nodesMask[nodeIndex]) continue; //var t = new Transform(pose[nodeIndex].TranslationVector, Quaternion.Identity, new Float3(0.1f)); - DebugDraw.DrawText(nodes[nodeIndex].Name, pose[nodeIndex].TranslationVector, Color.White, 20, 0.0f, 0.1f); + DebugDraw.DrawText(nodes[nodeIndex].Name, pose[nodeIndex].TranslationVector, Color.White, NodeNamesSize, 0.0f, 0.25f); } } } diff --git a/Source/Editor/Viewport/Previews/AnimationPreview.cs b/Source/Editor/Viewport/Previews/AnimationPreview.cs index 8431821d6..b8942f2bf 100644 --- a/Source/Editor/Viewport/Previews/AnimationPreview.cs +++ b/Source/Editor/Viewport/Previews/AnimationPreview.cs @@ -1,6 +1,5 @@ // Copyright (c) Wojciech Figat. All rights reserved. -using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; using FlaxEngine; using FlaxEditor.Viewport.Widgets; @@ -14,6 +13,7 @@ namespace FlaxEditor.Viewport.Previews /// public class AnimationPreview : AnimatedModelPreview { + private bool _baseModelMissing; private ViewportWidgetButton _playPauseButton; /// @@ -94,14 +94,23 @@ namespace FlaxEditor.Viewport.Previews var style = Style.Current; var skinnedModel = SkinnedModel; + var baseModelMissing = false; if (skinnedModel == null) { Render2D.DrawText(style.FontLarge, "Missing Base Model", new Rectangle(Float2.Zero, Size), Color.Red, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapWords); + baseModelMissing = true; } else if (!skinnedModel.IsLoaded) { Render2D.DrawText(style.FontLarge, skinnedModel.LastLoadFailed ? "Failed to load" : "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + baseModelMissing = true; } + if (_baseModelMissing && !baseModelMissing) + { + // Focus model when base model appears + ResetCamera(); + } + _baseModelMissing = baseModelMissing; } /// diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index f4985eb7a..22ef54bad 100644 --- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs +++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs @@ -132,7 +132,8 @@ namespace FlaxEditor.Windows if (item is AssetItem assetItem) { - if (assetItem.IsLoaded) + var asset = FlaxEngine.Content.GetAsset(assetItem.ID); + if (asset != null && (asset.IsLoaded || asset.LastLoadFailed)) cm.AddButton("Reload", assetItem.Reload); cm.AddButton("Copy asset ID", () => Clipboard.Text = JsonSerializer.GetStringID(assetItem.ID)); cm.AddButton("Select actors using this asset", () => Editor.SceneEditing.SelectActorsUsingAsset(assetItem.ID)); diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index e95fb61b2..7b7370c26 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -123,7 +123,7 @@ namespace FlaxEditor.Windows private readonly ScaledRenderOutputControl _viewport; private readonly GameRoot _guiRoot; private bool _showGUI = true, _editGUI = true; - private bool _showDebugDraw = false; + private bool _showDebugDraw = true; private bool _audioMuted = false; private float _audioVolume = 1; private bool _isMaximized = false, _isUnlockingMouse = false; diff --git a/Source/Editor/Windows/GraphicsQualityWindow.cs b/Source/Editor/Windows/GraphicsQualityWindow.cs index 27b131404..02e2da9bf 100644 --- a/Source/Editor/Windows/GraphicsQualityWindow.cs +++ b/Source/Editor/Windows/GraphicsQualityWindow.cs @@ -98,10 +98,10 @@ namespace FlaxEditor.Windows [NoSerialize, DefaultValue(1.0f), Limit(0.05f, 5, 0)] [EditorOrder(1400), EditorDisplay("Quality")] [Tooltip("The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer.")] - public float RenderingPercentage + public float RenderScale { - get => MainRenderTask.Instance.RenderingPercentage; - set => MainRenderTask.Instance.RenderingPercentage = value; + get => MainRenderTask.Instance.RenderScale; + set => MainRenderTask.Instance.RenderScale = value; } [NoSerialize, DefaultValue(RenderingUpscaleLocation.AfterAntiAliasingPass), VisibleIf(nameof(UpscaleLocation_Visible))] @@ -113,7 +113,7 @@ namespace FlaxEditor.Windows set => MainRenderTask.Instance.UpscaleLocation = value; } - private bool UpscaleLocation_Visible => MainRenderTask.Instance.RenderingPercentage < 1.0f; + private bool UpscaleLocation_Visible => MainRenderTask.Instance.RenderScale < 1.0f; [NoSerialize, DefaultValue(1.0f), Limit(0, 1)] [EditorOrder(1500), EditorDisplay("Quality"), Tooltip("The global density scale for all foliage instances. The default value is 1. Use values from range 0-1. Lower values decrease amount of foliage instances in-game. Use it to tweak game performance for slower devices.")] diff --git a/Source/Engine/AI/BehaviorKnowledge.cpp b/Source/Engine/AI/BehaviorKnowledge.cpp index 4d8e2eed9..872a12834 100644 --- a/Source/Engine/AI/BehaviorKnowledge.cpp +++ b/Source/Engine/AI/BehaviorKnowledge.cpp @@ -205,10 +205,9 @@ bool BehaviorKnowledge::Set(const StringAnsiView& path, const Variant& value) bool BehaviorKnowledge::HasGoal(ScriptingTypeHandle type) const { - for (int32 i = 0; i < Goals.Count(); i++) + for (const Variant& goal : Goals) { - const ScriptingTypeHandle goalType = Scripting::FindScriptingType(Goals[i].Type.GetTypeName()); - if (goalType == type) + if (goal.Type.GetScriptingType() == type) return true; } return false; @@ -218,8 +217,7 @@ const Variant& BehaviorKnowledge::GetGoal(ScriptingTypeHandle type) const { for (const Variant& goal : Goals) { - const ScriptingTypeHandle goalType = Scripting::FindScriptingType(goal.Type.GetTypeName()); - if (goalType == type) + if (goal.Type.GetScriptingType() == type) return goal; } return Variant::Null; @@ -242,10 +240,9 @@ void BehaviorKnowledge::RemoveGoal(ScriptingTypeHandle type) { for (int32 i = 0; i < Goals.Count(); i++) { - const ScriptingTypeHandle goalType = Scripting::FindScriptingType(Goals[i].Type.GetTypeName()); - if (goalType == type) + if (Goals[i].Type.GetScriptingType() == type) { - Goals.RemoveAt(i); + Goals.RemoveAtKeepOrder(i); break; } } diff --git a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp index 1cc9c66a4..8f1d494ec 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp @@ -61,14 +61,14 @@ void AnimGraphBase::Clear() StateTransitions.Resize(0); // Base - GraphType::Clear(); + VisjectGraph::Clear(); } #if USE_EDITOR void AnimGraphBase::GetReferences(Array& output) const { - GraphType::GetReferences(output); + VisjectGraph::GetReferences(output); // Collect references from nested graph (assets used in state machines) for (const auto* subGraph : SubGraphs) diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index b07b5a132..afebe60aa 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -873,65 +873,41 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu switch (param->Type.Type) { case VariantType::Float2: - switch (box->ID) - { - case 1: - case 2: + if (box->ID >= 1 && box->ID <= 2) value = value.AsFloat2().Raw[box->ID - 1]; - break; - } break; case VariantType::Float3: - switch (box->ID) - { - case 1: - case 2: - case 3: + if (box->ID >= 1 && box->ID <= 3) value = value.AsFloat3().Raw[box->ID - 1]; - break; - } break; case VariantType::Float4: case VariantType::Color: - switch (box->ID) - { - case 1: - case 2: - case 3: - case 4: + if (box->ID >= 1 && box->ID <= 4) value = value.AsFloat4().Raw[box->ID - 1]; - break; - } break; case VariantType::Double2: - switch (box->ID) - { - case 1: - case 2: + if (box->ID >= 1 && box->ID <= 2) value = value.AsDouble2().Raw[box->ID - 1]; - break; - } break; case VariantType::Double3: - switch (box->ID) - { - case 1: - case 2: - case 3: + if (box->ID >= 1 && box->ID <= 3) value = value.AsDouble3().Raw[box->ID - 1]; - break; - } break; case VariantType::Double4: - switch (box->ID) - { - case 1: - case 2: - case 3: - case 4: + if (box->ID >= 1 && box->ID <= 4) value = value.AsDouble4().Raw[box->ID - 1]; - break; - } + break; + case VariantType::Int2: + if (box->ID >= 1 && box->ID <= 2) + value = value.AsInt2().Raw[box->ID - 1]; + break; + case VariantType::Int3: + if (box->ID >= 1 && box->ID <= 3) + value = value.AsInt3().Raw[box->ID - 1]; + break; + case VariantType::Int4: + if (box->ID >= 1 && box->ID <= 4) + value = value.AsInt4().Raw[box->ID - 1]; break; case VariantType::Matrix: { diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index 66ff7a93e..0fdf1e9a5 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -272,6 +272,7 @@ String Asset::ToString() const void Asset::OnDeleteObject() { + PROFILE_CPU_NAMED("Asset.Unload"); ASSERT(IsInMainThread()); // Send event to the gameplay so it can release handle to this asset diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index a7e132bdc..26b896b02 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -163,7 +163,7 @@ VisjectExecutor::Value VisualScriptExecutor::eatBox(Node* caller, Box* box) // Add to the calling stack VisualScripting::StackFrame frame = *stack.Stack; - frame.Node = parentNode; + frame.Node = (VisualScriptGraphNode*)parentNode; frame.Box = box; frame.PreviousFrame = stack.Stack; stack.Stack = &frame; @@ -189,7 +189,7 @@ VisjectExecutor::Value VisualScriptExecutor::eatBox(Node* caller, Box* box) VisjectExecutor::Graph* VisualScriptExecutor::GetCurrentGraph() const { auto& stack = ThreadStacks.Get(); - return stack.Stack && stack.Stack->Script ? &stack.Stack->Script->Graph : nullptr; + return stack.Stack && stack.Stack->Script ? (Graph*)&stack.Stack->Script->Graph : nullptr; } void VisualScriptExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value) @@ -339,7 +339,7 @@ void VisualScriptExecutor::ProcessGroupTools(Box* box, Node* node, Value& value) obj = Value::Null; #else const ScriptingTypeHandle type = Scripting::FindScriptingType(StringAnsiView(typeNameAnsi.Get(), typeName.Length())); - const ScriptingTypeHandle objType = Scripting::FindScriptingType(obj.Type.GetTypeName()); + const ScriptingTypeHandle objType = obj.Type.GetScriptingType(); if (!type || !objType || !objType.IsSubclassOf(type)) obj = Value::Null; #endif @@ -432,7 +432,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& // Call Impulse or Pure Method if (boxBase->ID == 0 || (bool)node->Values[3]) { - auto& cache = node->Data.InvokeMethod; + auto& cache = ((VisualScriptGraphNode*)node)->Data.InvokeMethod; if (!cache.Method) { // Load method signature @@ -667,7 +667,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& // Get Field case 7: { - auto& cache = node->Data.GetSetField; + auto& cache = ((VisualScriptGraphNode*)node)->Data.GetSetField; if (!cache.Field) { const auto typeName = (StringView)node->Values[0]; @@ -753,7 +753,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& // Get Field case 8: { - auto& cache = node->Data.GetSetField; + auto& cache = ((VisualScriptGraphNode*)node)->Data.GetSetField; if (!cache.Field) { const auto typeName = (StringView)node->Values[0]; diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h index 9e7af97cd..91b9506fa 100644 --- a/Source/Engine/Content/Assets/VisualScript.h +++ b/Source/Engine/Content/Assets/VisualScript.h @@ -12,11 +12,47 @@ #define VISUAL_SCRIPT_GRAPH_MAX_CALL_STACK 250 #define VISUAL_SCRIPT_DEBUGGING USE_EDITOR -#define VisualScriptGraphNode VisjectGraphNode<> - class VisualScripting; class VisualScriptingBinaryModule; +/// +/// Visual Script graph node. +/// +class VisualScriptGraphNode : public VisjectGraphNode<> +{ +public: + struct InvokeMethodData + { + void* Method; + BinaryModule* Module; + int32 ParamsCount; + uint32 OutParamsMask; + bool IsStatic; + }; + + struct GetSetFieldData + { + void* Field; + BinaryModule* Module; + bool IsStatic; + }; + + /// + /// Custom cached data per node type. Compact to use as small amount of memory as possible. + /// + struct AdditionalData + { + union + { + InvokeMethodData InvokeMethod; + GetSetFieldData GetSetField; + }; + }; + + // The custom per-node data. Used to cache data for faster usage at runtime. + AdditionalData Data; +}; + /// /// The Visual Script graph data. /// diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp index 04fb3c3cf..c79e011b2 100644 --- a/Source/Engine/Content/BinaryAsset.cpp +++ b/Source/Engine/Content/BinaryAsset.cpp @@ -564,10 +564,7 @@ ContentLoadTask* BinaryAsset::createLoadingTask() loadTask = preLoadChunksTask; } - // Before asset loading we have to initialize storage - // TODO: maybe in build game we could do it in place? - // This step is only for opening asset files in background and upgrading them - // In build game we have only a few packages which are ready to use + // Before asset loading we have to initialize storage and pull the asset header auto initTask = New(this); initTask->ContinueWith(loadTask); loadTask = initTask; diff --git a/Source/Engine/Content/Cache/AssetsCache.cpp b/Source/Engine/Content/Cache/AssetsCache.cpp index 96083509e..96da061f1 100644 --- a/Source/Engine/Content/Cache/AssetsCache.cpp +++ b/Source/Engine/Content/Cache/AssetsCache.cpp @@ -113,7 +113,7 @@ void AssetsCache::Init() } // Use only valid entries - if (IsEntryValid(e)) + if (IsEntryValid(e) != EntryValidation::Invalid) _registry.Add(e.Info.ID, e); else rejectedCount++; @@ -295,14 +295,23 @@ bool AssetsCache::FindAsset(const StringView& path, AssetInfo& info) auto& e = i->Value; if (e.Info.Path == path) { - if (!IsEntryValid(e)) + const auto validation = IsEntryValid(e); + if (validation == EntryValidation::Invalid) { LOG(Warning, "Missing file from registry: \'{0}\':{1}:{2}", e.Info.Path, e.Info.ID, e.Info.TypeName); _registry.Remove(i); } else { - // Found +#if ENABLE_ASSETS_DISCOVERY + if (validation == EntryValidation::Inaccessible && !e.WarnedInaccessible) + { + e.WarnedInaccessible = true; + LOG(Warning, "Asset file locked, keeping cached entry: \'{0}\':{1}:{2}", e.Info.Path, e.Info.ID, e.Info.TypeName); + } +#endif + + // Found valid or inaccessible but return cached info either way result = true; info = e.Info; } @@ -322,13 +331,22 @@ bool AssetsCache::FindAsset(const Guid& id, AssetInfo& info) auto e = _registry.TryGet(id); if (e != nullptr) { - if (!IsEntryValid(*e)) + const auto validation = IsEntryValid(*e); + if (validation == EntryValidation::Invalid) { LOG(Warning, "Missing file from registry: \'{0}\':{1}:{2}", e->Info.Path, e->Info.ID, e->Info.TypeName); _registry.Remove(id); } else { +#if ENABLE_ASSETS_DISCOVERY + if (validation == EntryValidation::Inaccessible && !e->WarnedInaccessible) + { + e->WarnedInaccessible = true; + LOG(Warning, "Asset file locked, keeping cached entry: \'{0}\':{1}:{2}", e->Info.Path, e->Info.ID, e->Info.TypeName); + } +#endif + // Found result = true; info = e->Info; @@ -360,13 +378,13 @@ void AssetsCache::GetAllByTypeName(const StringView& typeName, Array& resu void AssetsCache::RegisterAssets(FlaxStorage* storage) { PROFILE_CPU(); - ASSERT(storage); // Get all entries Array entries; storage->GetEntries(entries); - ASSERT(entries.HasItems()); + if (entries.IsEmpty()) + return; ASSETS_CACHE_LOCK(); auto storagePath = storage->GetPath(); @@ -567,60 +585,70 @@ bool AssetsCache::RenameAsset(const StringView& oldPath, const StringView& newPa #endif -bool AssetsCache::IsEntryValid(Entry& e) +AssetsCache::EntryValidation AssetsCache::IsEntryValid(Entry& e) { #if ENABLE_ASSETS_DISCOVERY // Check if file exists - if (FileSystem::FileExists(e.Info.Path)) + if (!FileSystem::FileExists(e.Info.Path)) + return EntryValidation::Invalid; + + // Check if file hasn't been modified + const auto fileModified = FileSystem::GetFileLastEditTime(e.Info.Path); + if (fileModified == e.FileModified) { - // Check if file hasn't been modified - const auto fileModified = FileSystem::GetFileLastEditTime(e.Info.Path); - if (fileModified == e.FileModified) - return true; - - const auto extension = FileSystem::GetExtension(e.Info.Path).ToLower(); - - // Check if it's a binary asset - if (ContentStorageManager::IsFlaxStorageExtension(extension)) - { - // Validate ID within storage container - const auto storage = ContentStorageManager::GetStorage(e.Info.Path); - if (storage) - { - // Check if storage at given location contains that asset - const bool isValid = storage->HasAsset(e.Info); - - // Update entry and mark cache as dirty - e.FileModified = fileModified; - _isDirty = true; - - return isValid; - } - } - // Check for json resource - else if (JsonStorageProxy::IsValidExtension(extension)) - { - // Check Json storage layer - Guid jsonId; - String jsonTypeName; - if (JsonStorageProxy::GetAssetInfo(e.Info.Path, jsonId, jsonTypeName)) - { - const bool isValid = e.Info.ID == jsonId && e.Info.TypeName == jsonTypeName; - - // Update entry and mark cache as dirty - e.FileModified = fileModified; - _isDirty = true; - - return isValid; - } - } + e.WarnedInaccessible = false; + return EntryValidation::Valid; } - return false; + const auto extension = FileSystem::GetExtension(e.Info.Path).ToLower(); + // Check if it's a binary asset + if (ContentStorageManager::IsFlaxStorageExtension(extension)) + { + // Validate ID within storage container + const auto storage = ContentStorageManager::GetStorage(e.Info.Path); + if (storage) + { + // Check if storage at given location contains that asset + const bool isValid = storage->HasAsset(e.Info); + + // Update entry and mark cache as dirty + e.FileModified = fileModified; + e.WarnedInaccessible = false; + _isDirty = true; + + return isValid ? EntryValidation::Valid : EntryValidation::Invalid; + } + } + // Check for json resource + else if (JsonStorageProxy::IsValidExtension(extension)) + { + // Check Json storage layer + Guid jsonId; + String jsonTypeName; + if (JsonStorageProxy::GetAssetInfo(e.Info.Path, jsonId, jsonTypeName)) + { + const bool isValid = e.Info.ID == jsonId && e.Info.TypeName == jsonTypeName; + + // Update entry and mark cache as dirty + e.FileModified = fileModified; + e.WarnedInaccessible = false; + _isDirty = true; + + return isValid ? EntryValidation::Valid : EntryValidation::Invalid; + } + } + else + { + // Unknown file type + return EntryValidation::Invalid; + } + + // File exists but cannot be read (likely locked by git or another process) + return EntryValidation::Inaccessible; #else // In game we don't care about it because all cached asset entries are valid (precached) // Skip only entries with missing file - return e.Info.Path.HasChars(); + return e.Info.Path.HasChars() ? EntryValidation::Valid : EntryValidation::Invalid; #endif } diff --git a/Source/Engine/Content/Cache/AssetsCache.h b/Source/Engine/Content/Cache/AssetsCache.h index 42f05a2aa..28380b69d 100644 --- a/Source/Engine/Content/Cache/AssetsCache.h +++ b/Source/Engine/Content/Cache/AssetsCache.h @@ -58,6 +58,11 @@ public: /// The file modified date. /// DateTime FileModified; + + /// + /// True if a warning about this entry being inaccessible has already been logged (prevents log spam). Runtime-only, not serialized. + /// + bool WarnedInaccessible = false; #endif Entry() @@ -73,6 +78,27 @@ public: } }; + /// + /// Result of validating an asset cache entry. + /// + enum class EntryValidation + { + /// + /// File verified, contains this asset. + /// + Valid, + + /// + /// File missing or contains a different asset. + /// + Invalid, + + /// + /// File exists but cannot be opened (locked by another process). + /// + Inaccessible, + }; + typedef Dictionary Registry; typedef Dictionary PathsMapping; @@ -232,6 +258,6 @@ public: /// Determines whether cached asset entry is valid. /// /// The asset entry. - /// True if is valid, otherwise false. - bool IsEntryValid(Entry& e); + /// The validation result. + EntryValidation IsEntryValid(Entry& e); }; diff --git a/Source/Engine/Content/Storage/ContentStorageManager.cpp b/Source/Engine/Content/Storage/ContentStorageManager.cpp index 8aa36c7be..33a16e031 100644 --- a/Source/Engine/Content/Storage/ContentStorageManager.cpp +++ b/Source/Engine/Content/Storage/ContentStorageManager.cpp @@ -54,6 +54,7 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b Locker.Lock(); // Try fast lookup + bool wasCached = true; FlaxStorage* storage; if (!StorageMap.TryGet(path, storage)) { @@ -74,6 +75,7 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b // Register storage container StorageMap.Add(path, storage); + wasCached = false; } // Build reference (before releasing the lock so ContentStorageSystem::Job won't delete it when running from async thread) @@ -90,6 +92,8 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b if (loadFailed) { LOG(Error, "Failed to load {0}.", path); + if (wasCached) + return result; Locker.Lock(); StorageMap.Remove(path); if (storage->IsPackage()) diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index 8dee911e4..4eaea48f8 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -243,9 +243,9 @@ FlaxStorage::~FlaxStorage() { // Validate if has been disposed ASSERT(IsDisposed()); - CHECK(_chunksLock == 0); - CHECK(_refCount == 0); - CHECK(_isUnloadingData == 0); + CHECK_NO_RETURN(_chunksLock == 0); + CHECK_NO_RETURN(_refCount == 0); + CHECK_NO_RETURN(_isUnloadingData == 0); ASSERT(_chunks.IsEmpty()); #if USE_EDITOR diff --git a/Source/Engine/Core/Math/Vector2.cpp b/Source/Engine/Core/Math/Vector2.cpp index c6abd7589..790a27bcc 100644 --- a/Source/Engine/Core/Math/Vector2.cpp +++ b/Source/Engine/Core/Math/Vector2.cpp @@ -9,6 +9,8 @@ // Float static_assert(sizeof(Float2) == 8, "Invalid Float2 type size."); +static_assert(sizeof(Int2) == 8, "Invalid Int2 type size."); +static_assert(sizeof(Double2) == 16, "Invalid Double2 type size."); template<> const Float2 Float2::Zero(0.0f); diff --git a/Source/Engine/Core/Math/Vector3.cpp b/Source/Engine/Core/Math/Vector3.cpp index 6c0c3f1f8..f96954d97 100644 --- a/Source/Engine/Core/Math/Vector3.cpp +++ b/Source/Engine/Core/Math/Vector3.cpp @@ -13,6 +13,8 @@ // Float static_assert(sizeof(Float3) == 12, "Invalid Float3 type size."); +static_assert(sizeof(Int3) == 12, "Invalid Int3 type size."); +static_assert(sizeof(Double3) == 24, "Invalid Double3 type size."); template<> const Float3 Float3::Zero(0.0f); diff --git a/Source/Engine/Core/Math/Vector4.cpp b/Source/Engine/Core/Math/Vector4.cpp index 699d2d550..afae743c6 100644 --- a/Source/Engine/Core/Math/Vector4.cpp +++ b/Source/Engine/Core/Math/Vector4.cpp @@ -11,6 +11,8 @@ // Float static_assert(sizeof(Float4) == 16, "Invalid Float4 type size."); +static_assert(sizeof(Int4) == 16, "Invalid Int4 type size."); +static_assert(sizeof(Double4) == 32, "Invalid Double4 type size."); template<> const Float4 Float4::Zero(0.0f); diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index cf2d3b9fc..5fcfea63a 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -4,6 +4,7 @@ #include "Engine/Core/Collections/HashFunctions.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Content/Asset.h" +#include "Engine/Content/Deprecated.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Mathd.h" #include "Engine/Core/Math/BoundingBox.h" @@ -120,7 +121,7 @@ VariantType::VariantType(Types type, const StringAnsiView& typeName, bool static VariantType::VariantType(Types type, const ScriptingType& sType) : VariantType(type) { - SetTypeName(sType.Fullname, sType.Module->CanReload); + SetTypeName(sType.Fullname, !sType.Module->CanReload); } VariantType::VariantType(Types type, const MClass* klass) @@ -340,13 +341,13 @@ void VariantType::SetTypeName(const StringAnsiView& typeName, bool staticName) void VariantType::SetTypeName(const ScriptingType& type) { - SetTypeName(type.Fullname, type.Module->CanReload); + SetTypeName(type.Fullname, !type.Module->CanReload); } void VariantType::SetTypeName(const MClass& klass) { #if USE_CSHARP - SetTypeName(klass.GetFullName(), klass.GetAssembly()->CanReload()); + SetTypeName(klass.GetFullName(), !klass.GetAssembly()->CanReload()); #endif } @@ -357,6 +358,11 @@ const char* VariantType::GetTypeName() const return InBuiltTypesTypeNames[Type]; } +ScriptingTypeHandle VariantType::GetScriptingType() const +{ + return Scripting::FindScriptingType(GetTypeName()); +} + VariantType VariantType::GetElementType() const { if (Type == Array) @@ -3189,6 +3195,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Int2: + case VariantType::Int3: + case VariantType::Int4: case VariantType::Enum: return true; default: @@ -3212,6 +3221,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Int2: + case VariantType::Int3: + case VariantType::Int4: case VariantType::Enum: return true; default: @@ -3235,6 +3247,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Int2: + case VariantType::Int3: + case VariantType::Int4: case VariantType::Enum: return true; default: @@ -3258,6 +3273,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Int2: + case VariantType::Int3: + case VariantType::Int4: case VariantType::Enum: return true; default: @@ -3281,6 +3299,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Int2: + case VariantType::Int3: + case VariantType::Int4: case VariantType::Enum: return true; default: @@ -3304,6 +3325,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Int2: + case VariantType::Int3: + case VariantType::Int4: case VariantType::Enum: return true; default: @@ -3327,6 +3351,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Int2: + case VariantType::Int3: + case VariantType::Int4: case VariantType::Enum: return true; default: @@ -3350,6 +3377,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Int2: + case VariantType::Int3: + case VariantType::Int4: case VariantType::Enum: return true; default: @@ -3373,6 +3403,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Int2: + case VariantType::Int3: + case VariantType::Int4: case VariantType::Enum: return true; default: @@ -3516,6 +3549,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Double3(v.AsBool ? 1.0 : 0.0)); case VariantType::Double4: return Variant(Double4(v.AsBool ? 1.0 : 0.0)); + case VariantType::Int2: + return Variant(Int2(v.AsBool ? 1 : 0)); + case VariantType::Int3: + return Variant(Int3(v.AsBool ? 1 : 0)); + case VariantType::Int4: + return Variant(Int4(v.AsBool ? 1 : 0)); case VariantType::Enum: return Enum(to, v.AsBool ? 1 : 0); default: ; @@ -3554,6 +3593,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Double3((double)v.AsInt16)); case VariantType::Double4: return Variant(Double4((double)v.AsInt16)); + case VariantType::Int2: + return Variant(Int2(v.AsInt16)); + case VariantType::Int3: + return Variant(Int3(v.AsInt16)); + case VariantType::Int4: + return Variant(Int4(v.AsInt16)); case VariantType::Enum: return Enum(to, (int64)v.AsInt16); default: ; @@ -3584,6 +3629,18 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Float3((float)v.AsInt)); case VariantType::Float4: return Variant(Float4((float)v.AsInt)); + case VariantType::Double2: + return Variant(Double2((double)v.AsInt)); + case VariantType::Double3: + return Variant(Double3((double)v.AsInt)); + case VariantType::Double4: + return Variant(Double4((double)v.AsInt)); + case VariantType::Int2: + return Variant(Int2(v.AsInt)); + case VariantType::Int3: + return Variant(Int3(v.AsInt)); + case VariantType::Int4: + return Variant(Int4(v.AsInt)); case VariantType::Color: return Variant(Color((float)v.AsInt)); case VariantType::Enum: @@ -3624,6 +3681,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Double3((double)v.AsUint16)); case VariantType::Double4: return Variant(Double4((double)v.AsUint16)); + case VariantType::Int2: + return Variant(Int2(v.AsUint16)); + case VariantType::Int3: + return Variant(Int3(v.AsUint16)); + case VariantType::Int4: + return Variant(Int4(v.AsUint16)); case VariantType::Enum: return Enum(to, (int64)v.AsUint16); default: ; @@ -3662,6 +3725,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Double3((double)v.AsUint)); case VariantType::Double4: return Variant(Double4((double)v.AsUint)); + case VariantType::Int2: + return Variant(Int2((int32)v.AsUint)); + case VariantType::Int3: + return Variant(Int3((int32)v.AsUint)); + case VariantType::Int4: + return Variant(Int4((int32)v.AsUint)); case VariantType::Enum: return Enum(to, (int64)v.AsUint); default: ; @@ -3700,6 +3769,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Double3((double)v.AsInt64)); case VariantType::Double4: return Variant(Double4((double)v.AsInt64)); + case VariantType::Int2: + return Variant(Int2((int32)v.AsInt64)); + case VariantType::Int3: + return Variant(Int3((int32)v.AsInt64)); + case VariantType::Int4: + return Variant(Int4((int32)v.AsInt64)); case VariantType::Enum: return Enum(to, (int64)v.AsInt64); default: ; @@ -3719,25 +3794,31 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) case VariantType::Uint16: return Variant((uint16)v.AsUint16); case VariantType::Uint: - return Variant((uint32)v.AsUint); + return Variant((uint32)v.AsUint64); case VariantType::Float: return Variant((float)v.AsUint64); case VariantType::Double: return Variant((double)v.AsUint64); case VariantType::Float2: - return Variant(Float2((float)v.AsInt)); + return Variant(Float2((float)v.AsUint64)); case VariantType::Float3: - return Variant(Float3((float)v.AsInt)); + return Variant(Float3((float)v.AsUint64)); case VariantType::Float4: - return Variant(Float4((float)v.AsInt)); + return Variant(Float4((float)v.AsUint64)); case VariantType::Color: - return Variant(Color((float)v.AsInt)); + return Variant(Color((float)v.AsUint64)); case VariantType::Double2: - return Variant(Double2((double)v.AsInt)); + return Variant(Double2((double)v.AsUint64)); case VariantType::Double3: - return Variant(Double3((double)v.AsInt)); + return Variant(Double3((double)v.AsUint64)); case VariantType::Double4: - return Variant(Double4((double)v.AsInt)); + return Variant(Double4((double)v.AsUint64)); + case VariantType::Int2: + return Variant(Int2((int32)v.AsUint64)); + case VariantType::Int3: + return Variant(Int3((int32)v.AsUint64)); + case VariantType::Int4: + return Variant(Int4((int32)v.AsUint64)); case VariantType::Enum: return Enum(to, (int64)v.AsInt); default: ; @@ -3776,6 +3857,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Double3(v.AsFloat)); case VariantType::Double4: return Variant(Double4(v.AsFloat)); + case VariantType::Int2: + return Variant(Int2((int32)v.AsFloat)); + case VariantType::Int3: + return Variant(Int3((int32)v.AsFloat)); + case VariantType::Int4: + return Variant(Int4((int32)v.AsFloat)); case VariantType::Enum: return Enum(to, (int64)v.AsFloat); default: ; @@ -3814,6 +3901,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Double3(v.AsDouble)); case VariantType::Double4: return Variant(Double4(v.AsDouble)); + case VariantType::Int2: + return Variant(Int2((int32)v.AsFloat)); + case VariantType::Int3: + return Variant(Int3((int32)v.AsFloat)); + case VariantType::Int4: + return Variant(Int4((int32)v.AsFloat)); case VariantType::Enum: return Enum(to, (int64)v.AsDouble); default: ; @@ -4242,6 +4335,7 @@ void Variant::AllocStructure() AsBlob.Length = 2; AsBlob.Data = Allocator::Allocate(AsBlob.Length); *((int16*)AsBlob.Data) = 0; + MARK_CONTENT_DEPRECATED(); } #if USE_CSHARP else if (const auto mclass = Scripting::FindClass(typeName)) @@ -4367,6 +4461,13 @@ uint32 GetHash(const Variant& key) return GetHash((void*)key.AsObject); case VariantType::Structure: case VariantType::Blob: + case VariantType::Transform: + case VariantType::Matrix: +#if USE_LARGE_WORLDS + case VariantType::BoundingSphere: + case VariantType::BoundingBox: + case VariantType::Ray: +#endif return Crc::MemCrc32(key.AsBlob.Data, key.AsBlob.Length); case VariantType::Asset: return GetHash((void*)key.AsAsset); @@ -4376,6 +4477,24 @@ uint32 GetHash(const Variant& key) return GetHash(*(Guid*)key.AsData); case VariantType::Typename: return GetHash((const char*)key.AsBlob.Data); + case VariantType::Float2: + return GetHash(*(const Float2*)key.AsData); + case VariantType::Float3: + return GetHash(*(const Float3*)key.AsData); + case VariantType::Float4: + return GetHash(*(const Float4*)key.AsData); + case VariantType::Int2: + return GetHash(*(const Int2*)key.AsData); + case VariantType::Int3: + return GetHash(*(const Int3*)key.AsData); + case VariantType::Int4: + return GetHash(*(const Int4*)key.AsData); + case VariantType::Double2: + return GetHash(*(const Double2*)key.AsData); + case VariantType::Double3: + return GetHash(*(const Double3*)key.AsData); + case VariantType::Double4: + return GetHash(*(const Double4*)key.AsBlob.Data); case VariantType::ManagedObject: #if USE_CSHARP return key.MANAGED_GC_HANDLE ? (uint32)MCore::Object::GetHashCode(MCore::GCHandle::GetTarget(key.MANAGED_GC_HANDLE)) : 0; diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h index 13b928171..9532d4ec4 100644 --- a/Source/Engine/Core/Types/Variant.h +++ b/Source/Engine/Core/Types/Variant.h @@ -151,6 +151,7 @@ public: void SetTypeName(const ScriptingType& type); void SetTypeName(const MClass& klass); const char* GetTypeName() const; + ScriptingTypeHandle GetScriptingType() const; VariantType GetElementType() const; // Drops custom type name into the name allocated by the scripting module to reduce memory allocations when referencing types. void Inline(); @@ -420,6 +421,15 @@ public: return MoveTemp(v); } + template + static typename TEnableIf::Value, Variant>::Type Enum(const T value) + { + Variant v; + v.SetType(VariantType(VariantType::Enum, StaticType().GetType())); + v.AsUint64 = (uint64)value; + return MoveTemp(v); + } + template static typename TEnableIf::Value && !TIsPointer::Value, Variant>::Type Structure(VariantType&& type, const T& value) { diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index 5349afdb8..f4cfa1d86 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -85,7 +85,7 @@ struct CommandData else if (value.Type.Type == VariantType::Structure) { // Prettify structure printing - ScriptingTypeHandle resultType = Scripting::FindScriptingType(value.Type.GetTypeName()); + ScriptingTypeHandle resultType = value.Type.GetScriptingType(); if (resultType) { Array fields; diff --git a/Source/Engine/Graphics/Async/GPUTasksContext.cpp b/Source/Engine/Graphics/Async/GPUTasksContext.cpp index fe2198865..4fbe88510 100644 --- a/Source/Engine/Graphics/Async/GPUTasksContext.cpp +++ b/Source/Engine/Graphics/Async/GPUTasksContext.cpp @@ -76,9 +76,10 @@ void GPUTasksContext::OnFrameBegin() { auto task = _tasksSyncing[i]; auto state = task->GetState(); + if (EnumHasAllFlags(task->Flags, ObjectFlags::WasMarkedToDelete)) + state = TaskState::Finished; if (task->GetSyncPoint() <= _currentSyncPoint && state != TaskState::Finished) { - // TODO: add stats counter and count performed jobs, print to log on exit. task->Sync(); } if (state == TaskState::Failed || state == TaskState::Canceled) diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index 082ac497b..3157f3552 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -460,6 +460,18 @@ void MaterialParameter::Bind(BindMeta& meta) const ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Float4))); *((Float4*)(meta.Constants.Get() + _offset)) = (Float4)e->Value.AsDouble4(); break; + case VariantType::Int2: + ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Int2))); + *((Int2*)(meta.Constants.Get() + _offset)) = (Int2)e->Value.AsInt2(); + break; + case VariantType::Int3: + ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Int3))); + *((Int3*)(meta.Constants.Get() + _offset)) = (Int3)e->Value.AsInt3(); + break; + case VariantType::Int4: + ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Int4))); + *((Int4*)(meta.Constants.Get() + _offset)) = (Int4)e->Value.AsInt4(); + break; default: ; } } diff --git a/Source/Engine/Graphics/Models/MeshAccessor.cs b/Source/Engine/Graphics/Models/MeshAccessor.cs index cd216cdf5..fccb4e518 100644 --- a/Source/Engine/Graphics/Models/MeshAccessor.cs +++ b/Source/Engine/Graphics/Models/MeshAccessor.cs @@ -621,7 +621,7 @@ namespace FlaxEngine { ibData = dataPtr[IB]; use16BitIndexBuffer = _formats[IB] == PixelFormat.R16_UInt; - triangles = (uint)(_data[IB].Length / PixelFormatExtensions.SizeInBytes(_formats[IB])); + triangles = (uint)(_data[IB].Length / (PixelFormatExtensions.SizeInBytes(_formats[IB]) * 3)); } if (mesh.Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout)) @@ -643,11 +643,16 @@ namespace FlaxEngine else { Float3 min = Float3.Maximum, max = Float3.Minimum; - for (int i = 0; i < vertices; i++) + PixelFormatSampler.Get(positionStream.Format, out var positionSampler); + int positionStride = positionStream.Stride; + fixed (byte* data = positionStream.Data) { - Float3 pos = positionStream.GetFloat3(i); - Float3.Min(ref min, ref pos, out min); - Float3.Max(ref max, ref pos, out max); + for (int i = 0; i < vertices; i++) + { + Float3 pos = new Float3(positionSampler.Read(data + i * positionStride)); + Float3.Min(ref min, ref pos, out min); + Float3.Max(ref max, ref pos, out max); + } } bounds = new BoundingBox(min, max); } diff --git a/Source/Engine/Graphics/Models/ModelInstanceEntry.cpp b/Source/Engine/Graphics/Models/ModelInstanceEntry.cpp index 1c9440b39..55448e1a3 100644 --- a/Source/Engine/Graphics/Models/ModelInstanceEntry.cpp +++ b/Source/Engine/Graphics/Models/ModelInstanceEntry.cpp @@ -21,6 +21,13 @@ bool ModelInstanceEntries::HasContentLoaded() const return result; } +bool ModelInstanceEntries::ShouldSerialize(const void* otherObj) const +{ + if (!otherObj) + return true; + return !(*this == *(const ModelInstanceEntries*)otherObj); +} + void ModelInstanceEntries::Serialize(SerializeStream& stream, const void* otherObj) { SERIALIZE_GET_OTHER_OBJ(ModelInstanceEntries); @@ -43,12 +50,13 @@ void ModelInstanceEntries::Serialize(SerializeStream& stream, const void* otherO void ModelInstanceEntries::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) { PROFILE_MEM(Graphics); - const DeserializeStream& entries = stream["Entries"]; - ASSERT(entries.IsArray()); - Resize(entries.Size()); - for (rapidjson::SizeType i = 0; i < entries.Size(); i++) + const DeserializeStream& entriesData = stream[rapidjson_flax::Value(rapidjson::StringRef("Entries", 7))]; + CHECK(entriesData.IsArray()); + Resize(entriesData.Size()); + ModelInstanceEntry* entries = Get(); + for (int32 i = 0; i < Count(); i++) { - At(i).Deserialize((DeserializeStream&)entries[i], modifier); + entries[i].Deserialize((DeserializeStream&)entriesData[i], modifier); } } diff --git a/Source/Engine/Graphics/Models/ModelInstanceEntry.h b/Source/Engine/Graphics/Models/ModelInstanceEntry.h index 8f8a2ca3c..79403cce1 100644 --- a/Source/Engine/Graphics/Models/ModelInstanceEntry.h +++ b/Source/Engine/Graphics/Models/ModelInstanceEntry.h @@ -115,6 +115,7 @@ public: public: // [ISerializable] + bool ShouldSerialize(const void* otherObj) const override; void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; }; diff --git a/Source/Engine/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp index ad8bbbdc0..31639c39d 100644 --- a/Source/Engine/Graphics/RenderTask.cpp +++ b/Source/Engine/Graphics/RenderTask.cpp @@ -353,8 +353,11 @@ Viewport SceneRenderTask::GetViewport() const viewport = Buffers->GetViewport(); else viewport = Viewport(0, 0, 1280, 720); - viewport.Width *= RenderingPercentage; - viewport.Height *= RenderingPercentage; +PRAGMA_DISABLE_DEPRECATION_WARNINGS + float renderScale = RenderingPercentage * RenderScale; +PRAGMA_ENABLE_DEPRECATION_WARNINGS + viewport.Width *= renderScale; + viewport.Height *= renderScale; return viewport; } @@ -394,13 +397,16 @@ void SceneRenderTask::OnBegin(GPUContext* context) } // Setup render buffers for the output rendering resolution +PRAGMA_DISABLE_DEPRECATION_WARNINGS + float renderScale = RenderingPercentage * RenderScale; +PRAGMA_ENABLE_DEPRECATION_WARNINGS if (Output) { - Buffers->Init((int32)((float)Output->Width() * RenderingPercentage), (int32)((float)Output->Height() * RenderingPercentage)); + Buffers->Init((int32)((float)Output->Width() * renderScale), (int32)((float)Output->Height() * renderScale)); } else if (SwapChain) { - Buffers->Init((int32)((float)SwapChain->GetWidth() * RenderingPercentage), (int32)((float)SwapChain->GetHeight() * RenderingPercentage)); + Buffers->Init((int32)((float)SwapChain->GetWidth() * renderScale), (int32)((float)SwapChain->GetHeight() * renderScale)); } } @@ -434,7 +440,10 @@ bool SceneRenderTask::Resize(int32 width, int32 height) PROFILE_MEM(Graphics); if (Output && Output->Resize(width, height)) return true; - if (Buffers && Buffers->Init((int32)((float)width * RenderingPercentage), (int32)((float)height * RenderingPercentage))) +PRAGMA_DISABLE_DEPRECATION_WARNINGS + float renderScale = RenderingPercentage * RenderScale; +PRAGMA_ENABLE_DEPRECATION_WARNINGS + if (Buffers && Buffers->Init((int32)((float)width * renderScale), (int32)((float)height * renderScale))) return true; return false; } @@ -477,12 +486,6 @@ void MainRenderTask::OnBegin(GPUContext* context) // Use the main camera for the game (can be later overriden in Begin event by external code) Camera = Camera::GetMainCamera(); -#if !USE_EDITOR - // Sync render buffers size with the backbuffer - const auto size = Screen::GetSize(); - Buffers->Init((int32)(size.X * RenderingPercentage), (int32)(size.Y * RenderingPercentage)); -#endif - SceneRenderTask::OnBegin(context); } @@ -507,3 +510,10 @@ RenderContextBatch::RenderContextBatch(const RenderContext& context) Contexts.Add(context); EnableAsync = JobSystem::GetThreadsCount() > 1; } + +void RenderContextBatch::FlushWaitLabels() +{ + for (const int64 label : WaitLabels) + JobSystem::Wait(label); + WaitLabels.Clear(); +} diff --git a/Source/Engine/Graphics/RenderTask.h b/Source/Engine/Graphics/RenderTask.h index 8cba1006e..a4dbb5151 100644 --- a/Source/Engine/Graphics/RenderTask.h +++ b/Source/Engine/Graphics/RenderTask.h @@ -268,8 +268,14 @@ public: /// /// The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer. + /// [Deprecated in v1.13] /// - API_FIELD() float RenderingPercentage = 1.0f; + API_FIELD() DEPRECATED("Use RenderScale instead.") float RenderingPercentage = 1.0f; + + /// + /// The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer. + /// + API_FIELD() float RenderScale = 1.0f; /// /// The image resolution upscale location within rendering pipeline. Unused if RenderingPercentage is 1. @@ -533,4 +539,9 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API RenderContextBatch { return Contexts.Get()[0]; } + + /// + /// Waits for all scheduled async jobs to complete and clears WaitLabels. + /// + void FlushWaitLabels(); }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp index 48daecc83..3721abbb2 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp @@ -57,7 +57,7 @@ void CmdBufferVulkan::End() if (vkCmdEndDebugUtilsLabelEXT) vkCmdEndDebugUtilsLabelEXT(GetHandle()); #endif -#if GPU_ENABLE_TRACY +#if VULKAN_USE_TRACY_GPU tracy::EndVkZoneScope(_tracyZones.Last().Data); _tracyZones.RemoveLast(); #endif @@ -101,7 +101,7 @@ void CmdBufferVulkan::BeginEvent(const Char* name, void* tracyContext) char buffer[60]; int32 bufferSize = StringUtils::Copy(buffer, name, sizeof(buffer)); -#if GPU_ENABLE_TRACY +#if VULKAN_USE_TRACY_GPU auto& zone = _tracyZones.AddOne(); tracy::BeginVkZoneScope(zone.Data, tracyContext, GetHandle(), buffer, bufferSize); #endif @@ -128,7 +128,7 @@ void CmdBufferVulkan::EndEvent() vkCmdEndDebugUtilsLabelEXT(GetHandle()); #endif -#if GPU_ENABLE_TRACY +#if VULKAN_USE_TRACY_GPU tracy::EndVkZoneScope(_tracyZones.Last().Data); _tracyZones.RemoveLast(); #endif diff --git a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.h index 925f7a40f..a67964e08 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.h @@ -43,8 +43,10 @@ private: FenceVulkan* _fence; #if GPU_ALLOW_PROFILE_EVENTS int32 _eventsBegin = 0; +#if VULKAN_USE_TRACY_GPU struct TracyZone { byte Data[TracyVulkanZoneSize]; }; Array> _tracyZones; +#endif #endif // The latest value when command buffer was submitted. diff --git a/Source/Engine/GraphicsDevice/Vulkan/Config.h b/Source/Engine/GraphicsDevice/Vulkan/Config.h index e73e24df9..3769e0358 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/Config.h +++ b/Source/Engine/GraphicsDevice/Vulkan/Config.h @@ -49,6 +49,10 @@ #define VULKAN_USE_TIMER_QUERIES 1 #endif +#ifndef VULKAN_USE_TRACY_GPU +#define VULKAN_USE_TRACY_GPU (GPU_ENABLE_TRACY && VULKAN_USE_TIMER_QUERIES) +#endif + // Fence wait operation timeout in seconds #ifndef VULKAN_WAIT_TIMEOUT #define VULKAN_WAIT_TIMEOUT 5.0f diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index de6ab9e46..e835b2f80 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -112,7 +112,7 @@ GPUContextVulkan::GPUContextVulkan(GPUDeviceVulkan* device, QueueVulkan* queue) _handlesSizes[(int32)SpirvShaderResourceBindingType::UAV] = GPU_MAX_UA_BINDED; #endif -#if GPU_ENABLE_TRACY +#if VULKAN_USE_TRACY_GPU #if VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset && !PLATFORM_SWITCH // Use calibrated timestamps extension if (vkResetQueryPoolEXT && vkGetCalibratedTimestampsEXT && _device->PhysicalDeviceFeatures12.hostQueryReset) @@ -139,7 +139,7 @@ GPUContextVulkan::GPUContextVulkan(GPUDeviceVulkan* device, QueueVulkan* queue) GPUContextVulkan::~GPUContextVulkan() { -#if GPU_ENABLE_TRACY +#if VULKAN_USE_TRACY_GPU tracy::DestroyVkContext(_tracyContext); #endif for (int32 i = 0; i < _descriptorPools.Count(); i++) @@ -921,7 +921,7 @@ void GPUContextVulkan::FrameEnd() // Execute any queued layout transitions that weren't already handled by the render pass FlushBarriers(); -#if GPU_ENABLE_TRACY +#if VULKAN_USE_TRACY_GPU if (cmdBuffer) tracy::CollectVkContext(_tracyContext, cmdBuffer->GetHandle()); #endif @@ -935,7 +935,7 @@ void GPUContextVulkan::FrameEnd() void GPUContextVulkan::EventBegin(const Char* name) { const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); -#if COMPILE_WITH_PROFILER +#if VULKAN_USE_TRACY_GPU void* tracyContext = _tracyContext; #else void* tracyContext = nullptr; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp index 02e9f3782..94d7042dc 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp @@ -44,7 +44,7 @@ static const char* GInstanceExtensions[] = #if defined(VK_KHR_display) && 0 VK_KHR_DISPLAY_EXTENSION_NAME, #endif -#if GPU_ENABLE_TRACY && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset +#if VULKAN_USE_TRACY_GPU && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, // Required by VK_EXT_host_query_reset (unless using Vulkan 1.1 or newer) #endif nullptr @@ -65,7 +65,7 @@ static const char* GDeviceExtensions[] = #if VK_KHR_sampler_mirror_clamp_to_edge VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME, #endif -#if GPU_ENABLE_TRACY && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset +#if VULKAN_USE_TRACY_GPU && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME, VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME, #endif diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 140ca1a12..1d9cb005c 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -1732,7 +1732,7 @@ bool GPUDeviceVulkan::Init() VulkanPlatform::RestrictEnabledPhysicalDeviceFeatures(PhysicalDeviceFeatures, enabledFeatures); deviceInfo.pEnabledFeatures = &enabledFeatures; -#if GPU_ENABLE_TRACY && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset +#if VULKAN_USE_TRACY_GPU && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset VkPhysicalDeviceHostQueryResetFeatures resetFeatures; if (PhysicalDeviceFeatures12.hostQueryReset) { diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index ce106dc83..ba3a2025d 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -820,8 +820,7 @@ void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationD void AnimatedModel::BeginPlay(SceneBeginData* data) { - if (SkinnedModel && SkinnedModel->IsLoaded()) - PreInitSkinningData(); + PreInitSkinningData(); // Base ModelInstanceActor::BeginPlay(data); @@ -1263,9 +1262,7 @@ void AnimatedModel::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE(ShadowsMode); PRAGMA_ENABLE_DEPRECATION_WARNINGS SERIALIZE(RootMotionTarget); - - stream.JKEY("Buffer"); - stream.Object(&Entries, other ? &other->Entries : nullptr); + SERIALIZE_MEMBER(Buffer, Entries); } void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -1290,8 +1287,7 @@ void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* m DESERIALIZE(ShadowsMode); PRAGMA_ENABLE_DEPRECATION_WARNINGS DESERIALIZE(RootMotionTarget); - - Entries.DeserializeIfExists(stream, "Buffer", modifier); + DESERIALIZE_MEMBER(Buffer, Entries); // [Deprecated on 07.02.2022, expires on 07.02.2024] if (modifier->EngineBuild <= 6330) diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index 5e549e4a8..b6d922744 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -98,99 +98,99 @@ public: /// /// The skinned model asset used for rendering. /// - API_FIELD(Attributes="EditorOrder(10), DefaultValue(null), EditorDisplay(\"Skinned Model\")") + API_FIELD(Attributes="EditorOrder(10), DefaultValue(null), EditorDisplay(\"Skeleton\")") AssetReference SkinnedModel; /// /// The animation graph asset used for the skinned mesh skeleton bones evaluation (controls the animation). /// - API_FIELD(Attributes="EditorOrder(15), DefaultValue(null), EditorDisplay(\"Skinned Model\")") + API_FIELD(Attributes="EditorOrder(15), DefaultValue(null), EditorDisplay(\"Skeleton\")") AssetReference AnimationGraph; /// /// If true, use per-bone motion blur on this skeletal model. It requires additional rendering, can be disabled to save performance. /// - API_FIELD(Attributes="EditorOrder(20), DefaultValue(true), EditorDisplay(\"Skinned Model\")") + API_FIELD(Attributes="EditorOrder(20), DefaultValue(true), EditorDisplay(\"Drawing\")") bool PerBoneMotionBlur = true; /// /// If true, animation speed will be affected by the global timescale parameter. /// - API_FIELD(Attributes="EditorOrder(30), DefaultValue(true), EditorDisplay(\"Skinned Model\")") + API_FIELD(Attributes="EditorOrder(30), DefaultValue(true), EditorDisplay(\"Updating\")") bool UseTimeScale = true; /// /// If true, the animation will be updated even when an actor cannot be seen by any camera. Otherwise, the animations themselves will also stop running when the actor is off-screen. /// - API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"Skinned Model\")") + API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"Updating\")") bool UpdateWhenOffscreen = false; /// /// The animation update delta timescale. Can be used to speed up animation playback or create slow motion effect. /// - API_FIELD(Attributes="EditorOrder(45), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Skinned Model\")") + API_FIELD(Attributes="EditorOrder(45), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Updating\")") float UpdateSpeed = 1.0f; /// /// The animation update mode. Can be used to optimize the performance. /// - API_FIELD(Attributes="EditorOrder(50), DefaultValue(AnimationUpdateMode.Auto), EditorDisplay(\"Skinned Model\")") + API_FIELD(Attributes="EditorOrder(50), DefaultValue(AnimationUpdateMode.Auto), EditorDisplay(\"Updating\")") AnimationUpdateMode UpdateMode = AnimationUpdateMode::Auto; /// /// The master scale parameter for the actor bounding box. Helps to reduce mesh flickering effect on screen edges. /// - API_FIELD(Attributes="EditorOrder(60), DefaultValue(1.5f), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Skinned Model\")") + API_FIELD(Attributes="EditorOrder(60), DefaultValue(1.5f), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Drawing\")") float BoundsScale = 1.5f; /// /// The custom bounds(in actor local space). If set to empty bounds then source skinned model bind pose bounds will be used. /// - API_FIELD(Attributes="EditorOrder(70), EditorDisplay(\"Skinned Model\")") + API_FIELD(Attributes="EditorOrder(70), EditorDisplay(\"Drawing\")") BoundingBox CustomBounds = BoundingBox::Zero; /// /// The model Level Of Detail bias value. Allows to increase or decrease rendered model quality. /// - API_FIELD(Attributes="EditorOrder(80), DefaultValue(0), Limit(-100, 100, 0.1f), EditorDisplay(\"Skinned Model\", \"LOD Bias\")") + API_FIELD(Attributes="EditorOrder(80), DefaultValue(0), Limit(-100, 100, 0.1f), EditorDisplay(\"Drawing\", \"LOD Bias\")") int32 LODBias = 0; /// /// Gets the model forced Level Of Detail index. Allows to bind the given model LOD to show. Value -1 disables this feature. /// - API_FIELD(Attributes="EditorOrder(90), DefaultValue(-1), Limit(-1, 100, 0.1f), EditorDisplay(\"Skinned Model\", \"Forced LOD\")") + API_FIELD(Attributes="EditorOrder(90), DefaultValue(-1), Limit(-1, 100, 0.1f), EditorDisplay(\"Drawing\", \"Forced LOD\")") int32 ForcedLOD = -1; /// /// The draw passes to use for rendering this object. /// - API_FIELD(Attributes="EditorOrder(100), DefaultValue(DrawPass.Default), EditorDisplay(\"Skinned Model\")") + API_FIELD(Attributes="EditorOrder(100), DefaultValue(DrawPass.Default), EditorDisplay(\"Drawing\")") DrawPass DrawModes = DrawPass::Default; /// /// The object sort order key used when sorting drawable objects during rendering. Use lower values to draw object before others, higher values are rendered later (on top). Can be used to control transparency drawing. /// - API_FIELD(Attributes="EditorDisplay(\"Skinned Model\"), EditorOrder(110), DefaultValue(0)") + API_FIELD(Attributes="EditorOrder(110), DefaultValue(0), EditorDisplay(\"Drawing\")") int8 SortOrder = 0; /// /// The shadows casting mode. /// [Deprecated on 26.10.2022, expires on 26.10.2024] /// - API_FIELD(Attributes="EditorOrder(110), DefaultValue(ShadowsCastingMode.All), EditorDisplay(\"Skinned Model\")") + API_FIELD(Attributes="EditorOrder(110), DefaultValue(ShadowsCastingMode.All), EditorDisplay(\"Drawing\")") DEPRECATED() ShadowsCastingMode ShadowsMode = ShadowsCastingMode::All; /// /// The animation root motion apply target. If not specified the animated model will apply it itself. /// - API_FIELD(Attributes="EditorOrder(120), DefaultValue(null), EditorDisplay(\"Skinned Model\")") + API_FIELD(Attributes="EditorOrder(120), DefaultValue(null), EditorDisplay(\"Skeleton\")") ScriptingObjectReference RootMotionTarget; #if USE_EDITOR /// - /// If checked, the skeleton pose will be shawn during debug shapes drawing. + /// If checked, the skeleton pose will be shown during debug shapes drawing. /// - API_FIELD(Attributes="EditorOrder(200), EditorDisplay(\"Skinned Model\")") bool ShowDebugDrawSkeleton = false; + API_FIELD(Attributes="EditorOrder(200), EditorDisplay(\"Skeleton\"), VisibleIf(nameof(ShowDebugDrawOptions))") bool ShowDebugDrawSkeleton = false; #endif public: @@ -440,6 +440,16 @@ public: API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim = nullptr); private: +#if USE_EDITOR + /// + /// Used to hide options if when the skinned model or animation graph is null. + /// + API_PROPERTY(Attributes="HideInEditor, NoSerialize") bool GetShowDebugDrawOptions() const + { + return SkinnedModel != nullptr && AnimationGraph != nullptr; + } +#endif + void ApplyRootMotion(const Transform& rootMotionDelta); void SyncParameters(); void RunBlendShapeDeformer(const MeshBase* mesh, struct MeshDeformationData& deformation); diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 0da4a3d70..fdda6941e 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -497,9 +497,7 @@ void SplineModel::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_MEMBER(PreTransform, _preTransform) SERIALIZE(Model); SERIALIZE(DrawModes); - - stream.JKEY("Buffer"); - stream.Object(&Entries, other ? &other->Entries : nullptr); + SERIALIZE_MEMBER(Buffer, Entries); } void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -514,8 +512,7 @@ void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DESERIALIZE_MEMBER(PreTransform, _preTransform); DESERIALIZE(Model); DESERIALIZE(DrawModes); - - Entries.DeserializeIfExists(stream, "Buffer", modifier); + DESERIALIZE_MEMBER(Buffer, Entries); // [Deprecated on 07.02.2022, expires on 07.02.2024] if (modifier->EngineBuild <= 6330) diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index e10117915..501421310 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -486,8 +486,7 @@ void StaticModel::Serialize(SerializeStream& stream, const void* otherObj) Lightmap.Serialize(stream); } - stream.JKEY("Buffer"); - stream.Object(&Entries, other ? &other->Entries : nullptr); + SERIALIZE_MEMBER(Buffer, Entries); if (_vertexColorsCount) { @@ -525,7 +524,7 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DESERIALIZE_MEMBER(SortOrder, _sortOrder); DESERIALIZE_MEMBER(DrawModes, _drawModes); Lightmap.Deserialize(stream); - Entries.DeserializeIfExists(stream, "Buffer", modifier); + DESERIALIZE_MEMBER(Buffer, Entries); { const auto member = stream.FindMember("VertexColors"); diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp index cb6c82b5d..d972ee520 100644 --- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp @@ -24,6 +24,7 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Threading/MainThreadTask.h" #include "Editor/Editor.h" +#include "FlaxEngine.Gen.h" // Apply flow: // - collect all prefabs using this prefab (load and create default instances) @@ -772,7 +773,13 @@ bool Prefab::ApplyAll(Actor* targetActor) if (ApplyAllInternal(targetActor, true, thisPrefabInstancesData)) return true; - SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData); + // Sync nested prefabs + if (allPrefabs.HasItems()) + { + LOG(Info, "Updating referencing prefabs"); + HashSet synced; + SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData, synced); + } const auto endTime = DateTime::NowUTC(); LOG(Info, "Prefab updated! {0} ms", (int32)(endTime - startTime).GetTotalMilliseconds()); @@ -1027,8 +1034,14 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr rapidjson_flax::Document targetDataDocument; if (NestedPrefabs.HasItems()) { + // Use initial data buffer (unstripped) but reorder objects to match the sequence (eg. when new object was added to the nested prefab) targetDataDocument.Parse(dataBuffer.GetString(), dataBuffer.GetSize()); - SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, targetDataDocument, modifier.Value); + Array reorderedObjects = *sceneObjects.Value; + newPrefabInstanceIdToDataIndexCounter = 0; + for (auto i = newPrefabInstanceIdToDataIndex.Begin(); i.IsNotEnd(); ++i) + reorderedObjects.Insert(i->Value, sceneObjects->At(newPrefabInstanceIdToDataIndexStart + newPrefabInstanceIdToDataIndexCounter++)); + reorderedObjects.Resize(sceneObjects.Value->Count()); // reorderedObjects matches order in targetDataDocument + SceneObjectsFactory::PrefabSyncData prefabSyncData(reorderedObjects, targetDataDocument, modifier.Value); SceneObjectsFactory::SetupPrefabInstances(context, prefabSyncData); if (context.Instances.HasItems()) @@ -1236,7 +1249,7 @@ bool Prefab::UpdateInternal(const Array& defaultInstanceObjects, r { return Init(TypeName, StringAnsiView(tmpBuffer.GetString(), (int32)tmpBuffer.GetSize())); } -#if 1 // Set to 0 to use memory-only reload that does not modifies the source file - useful for testing and debugging prefabs apply +#if 1 // Set to 0 to use memory-only reload that does not modify the source file - useful for testing and debugging prefabs apply #if COMPILE_WITH_ASSETS_IMPORTER Locker.Unlock(); @@ -1295,7 +1308,7 @@ bool Prefab::UpdateInternal(const Array& defaultInstanceObjects, r _defaultInstance->DeleteObject(); _defaultInstance = nullptr; } - _isLoaded = false; + _loadState = 0; // Update prefab data manually (to prevent updating source asset file - just for testing) Document.Parse(buffer.GetString(), buffer.GetSize()); @@ -1348,7 +1361,7 @@ bool Prefab::UpdateInternal(const Array& defaultInstanceObjects, r NestedPrefabs.Add(prefabId); } } - _isLoaded = true; + _loadState = 1; } #endif @@ -1395,34 +1408,31 @@ bool Prefab::SyncChangesInternal(PrefabInstancesData& prefabInstancesData) return ApplyAllInternal(targetActor, false, prefabInstancesData); } -void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array& allPrefabsInstancesData) const +void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array& allPrefabsInstancesData, HashSet& synced) const { PROFILE_CPU(); - LOG(Info, "Updating referencing prefabs"); - - // TODO: this may not work well for very complex prefab nesting -> loop order matters, maybe build a graph of dependencies? // Call recursive for all referencing prefab assets to refresh nested prefabs for (int32 i = 0; i < allPrefabs.Count(); i++) { - auto nestedPrefab = allPrefabs[i].Get(); - if (nestedPrefab) + Prefab* nestedPrefab = allPrefabs[i].Get(); + if (!nestedPrefab || synced.Contains(nestedPrefab->GetID())) + continue; + if (nestedPrefab->WaitForLoaded()) { - if (nestedPrefab->WaitForLoaded()) - { - LOG(Warning, "Waiting for prefab asset load failed."); - continue; - } + LOG(Warning, "Waiting for '{}' load failed.", nestedPrefab->ToString()); + continue; + } - // Sync only if prefab is used by this prefab (directly) and it has been captured before - const int32 nestedPrefabIndex = nestedPrefab->NestedPrefabs.Find(GetID()); - if (nestedPrefabIndex != -1) - { - if (nestedPrefab->SyncChangesInternal(allPrefabsInstancesData[i])) - continue; - nestedPrefab->SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData); - ObjectsRemovalService::Flush(); - } + // Sync only if prefab is used by this prefab (directly) and it has been captured before + const int32 nestedPrefabIndex = nestedPrefab->NestedPrefabs.Find(GetID()); + if (nestedPrefabIndex != -1) + { + synced.Add(nestedPrefab->GetID()); + if (nestedPrefab->SyncChangesInternal(allPrefabsInstancesData[i])) + continue; + nestedPrefab->SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData, synced); + ObjectsRemovalService::Flush(); } } } diff --git a/Source/Engine/Level/Prefabs/Prefab.cpp b/Source/Engine/Level/Prefabs/Prefab.cpp index a8dc997e5..0b5404b43 100644 --- a/Source/Engine/Level/Prefabs/Prefab.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.cpp @@ -7,6 +7,7 @@ #include "Engine/Core/Log.h" #include "Engine/Level/Prefabs/PrefabManager.h" #include "Engine/Level/Actor.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Threading/Threading.h" #include "Engine/Scripting/Scripting.h" @@ -22,6 +23,7 @@ Prefab::Prefab(const SpawnParams& params, const AssetInfo* info) Guid Prefab::GetRootObjectId() const { + PROFILE_CPU(); ASSERT(IsLoaded()); ScopeLock lock(Locker); @@ -57,11 +59,12 @@ Actor* Prefab::GetDefaultInstance() // Skip if already created (reuse cached result) if (_defaultInstance) return _defaultInstance; + PROFILE_CPU(); // Skip if not loaded if (!IsLoaded()) { - LOG(Warning, "Cannot instantiate object from not loaded prefab asset '{}'", GetPath()); + LOG(Warning, "Cannot instantiate object from not loaded prefab asset ({}, {})", GetPath(), GetID()); return nullptr; } diff --git a/Source/Engine/Level/Prefabs/Prefab.h b/Source/Engine/Level/Prefabs/Prefab.h index cde99a2cb..b1200ca29 100644 --- a/Source/Engine/Level/Prefabs/Prefab.h +++ b/Source/Engine/Level/Prefabs/Prefab.h @@ -104,7 +104,7 @@ private: bool ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab, PrefabInstancesData& prefabInstancesData); bool UpdateInternal(const Array& defaultInstanceObjects, rapidjson_flax::StringBuffer& tmpBuffer); bool SyncChangesInternal(PrefabInstancesData& prefabInstancesData); - void SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array& allPrefabsInstancesData) const; + void SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array& allPrefabsInstancesData, HashSet& synced) const; #endif void DeleteDefaultInstance(); diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 59d23a994..e9931eb20 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -752,7 +752,7 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn obj->SetOrderInParent(order); } - // Setup hierarchy for the prefab instances (ensure any new objects are connected) + // Setup hierarchy for the prefab instances (after adding new objects to ensure they are connected, eg. when reparenting existing prefab into a new root) for (const auto& instance : context.Instances) { const auto& prefabStartData = data.Data[instance.StatIndex]; diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp index 1a7ba37d1..e12b781b0 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp @@ -27,65 +27,41 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParameters(Box* box, Node* nod switch (param->Type.Type) { case VariantType::Float2: - switch (box->ID) - { - case 1: - case 2: + if (box->ID >= 1 && box->ID <= 2) value = value.AsFloat2().Raw[box->ID - 1]; - break; - } break; case VariantType::Float3: - switch (box->ID) - { - case 1: - case 2: - case 3: + if (box->ID >= 1 && box->ID <= 3) value = value.AsFloat3().Raw[box->ID - 1]; - break; - } break; case VariantType::Float4: case VariantType::Color: - switch (box->ID) - { - case 1: - case 2: - case 3: - case 4: + if (box->ID >= 1 && box->ID <= 4) value = value.AsFloat4().Raw[box->ID - 1]; - break; - } break; case VariantType::Double2: - switch (box->ID) - { - case 1: - case 2: + if (box->ID >= 1 && box->ID <= 2) value = value.AsDouble2().Raw[box->ID - 1]; - break; - } break; case VariantType::Double3: - switch (box->ID) - { - case 1: - case 2: - case 3: + if (box->ID >= 1 && box->ID <= 3) value = value.AsDouble3().Raw[box->ID - 1]; - break; - } break; case VariantType::Double4: - switch (box->ID) - { - case 1: - case 2: - case 3: - case 4: + if (box->ID >= 1 && box->ID <= 4) value = value.AsDouble4().Raw[box->ID - 1]; - break; - } + break; + case VariantType::Int2: + if (box->ID >= 1 && box->ID <= 2) + value = value.AsInt2().Raw[box->ID - 1]; + break; + case VariantType::Int3: + if (box->ID >= 1 && box->ID <= 3) + value = value.AsInt3().Raw[box->ID - 1]; + break; + case VariantType::Int4: + if (box->ID >= 1 && box->ID <= 4) + value = value.AsInt4().Raw[box->ID - 1]; break; case VariantType::Matrix: { diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index ecd7c1093..24bb4c1a6 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -184,6 +184,15 @@ PhysicsScene* Physics::FindScene(const StringView& name) return nullptr; } +void Physics::DeleteScene(PhysicsScene* scene) +{ + if (scene == nullptr || scene == DefaultScene) + return; + scene->CollectResults(); + Scenes.RemoveKeepOrder(scene); + Delete(scene); +} + bool Physics::GetAutoSimulation() { return !DefaultScene || DefaultScene->GetAutoSimulation(); diff --git a/Source/Engine/Physics/Physics.h b/Source/Engine/Physics/Physics.h index 85cd5e77b..11fd3fb80 100644 --- a/Source/Engine/Physics/Physics.h +++ b/Source/Engine/Physics/Physics.h @@ -32,6 +32,11 @@ API_CLASS(Static) class FLAXENGINE_API Physics /// API_FUNCTION() static PhysicsScene* FindScene(const StringView& name); + /// + /// Delete an existing scene (excluding the default one). + /// + API_FUNCTION() static void DeleteScene(PhysicsScene* scene); + public: /// /// The automatic simulation feature. True if perform physics simulation after on fixed update by auto, otherwise user should do it. diff --git a/Source/Engine/Platform/Platform.h b/Source/Engine/Platform/Platform.h index e2c6808c9..438cb6602 100644 --- a/Source/Engine/Platform/Platform.h +++ b/Source/Engine/Platform/Platform.h @@ -75,6 +75,12 @@ Platform::CheckFailed(#expression, __FILE__, __LINE__); \ return returnValue; \ } +// Performs a soft check of the expression. Logs the expression failure and continues execution. +#define CHECK_NO_RETURN(expression) \ + if (!(expression)) \ + { \ + Platform::CheckFailed(#expression, __FILE__, __LINE__); \ + } #if ENABLE_ASSERTION // Performs a soft check of the expression. Logs the expression failure and returns from the function call. diff --git a/Source/Engine/Platform/Windows/WindowsInput.cpp b/Source/Engine/Platform/Windows/WindowsInput.cpp index 64299b6a3..96151e894 100644 --- a/Source/Engine/Platform/Windows/WindowsInput.cpp +++ b/Source/Engine/Platform/Windows/WindowsInput.cpp @@ -189,6 +189,7 @@ bool WindowsMouse::WndProc(Window* window, const UINT msg, WPARAM wParam, LPARAM POINT p; p.x = static_cast(WINDOWS_GET_X_LPARAM(lParam)); p.y = static_cast(WINDOWS_GET_Y_LPARAM(lParam)); + const Float2 mousePosScreen(static_cast(p.x), static_cast(p.y)); ::ClientToScreen(window->GetHWND(), &p); const Float2 mousePos(static_cast(p.x), static_cast(p.y)); @@ -203,7 +204,8 @@ bool WindowsMouse::WndProc(Window* window, const UINT msg, WPARAM wParam, LPARAM } case WM_NCMOUSEMOVE: { - OnMouseMove(mousePos, window); + // The position in the message is already reported in screen-space + OnMouseMove(mousePosScreen, window); result = true; break; } diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index a9a7be311..b39fdb423 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -356,9 +356,7 @@ void Renderer::DrawActors(RenderContext& renderContext, const Array& cus Level::DrawActors(renderContextBatch, SceneRendering::DrawCategory::SceneDraw); Level::DrawActors(renderContextBatch, SceneRendering::DrawCategory::SceneDrawAsync); JobSystem::SetJobStartingOnDispatch(true); - for (const int64 label : renderContextBatch.WaitLabels) - JobSystem::Wait(label); - renderContextBatch.WaitLabels.Clear(); + renderContextBatch.FlushWaitLabels(); } } @@ -486,9 +484,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont // Wait for async jobs to finish JobSystem::SetJobStartingOnDispatch(true); - for (const int64 label : renderContextBatch.WaitLabels) - JobSystem::Wait(label); - renderContextBatch.WaitLabels.Clear(); + renderContextBatch.FlushWaitLabels(); // Perform custom post-scene drawing (eg. GPU dispatches used by VFX) for (int32 i = 0; i < renderContextBatch.Contexts.Count(); i++) @@ -741,7 +737,9 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont } // Upscaling after scene rendering but before post processing - bool useUpscaling = task->RenderingPercentage < 1.0f; +PRAGMA_DISABLE_DEPRECATION_WARNINGS + bool useUpscaling = task->RenderingPercentage * task->RenderScale < 1.0f; +PRAGMA_ENABLE_DEPRECATION_WARNINGS const Viewport outputViewport = task->GetOutputViewport(); if (useUpscaling && setup.UpscaleLocation == RenderingUpscaleLocation::BeforePostProcessingPass) { diff --git a/Source/Engine/Scripting/Attributes/EnumStringAttribute.cs b/Source/Engine/Scripting/Attributes/EnumStringAttribute.cs new file mode 100644 index 000000000..aa9222c8b --- /dev/null +++ b/Source/Engine/Scripting/Attributes/EnumStringAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine +{ + /// + /// Changes enum serialization to use string names instead of integer values. This makes saved data resilient to enum reordering or changes in values (but not to renaming enums). Deserialization accepts both string names and integer values for backward compatibility. + /// + /// + [AttributeUsage(AttributeTargets.Enum)] + public sealed class EnumStringAttribute : Attribute + { + } +} diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 02358fac0..029a245c3 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -204,7 +204,7 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul Struct.SetField = setField; } -ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items) +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items, bool stringSerialization) : ManagedClass(nullptr) , Module(module) , InitRuntime(DefaultInitRuntime) @@ -215,6 +215,7 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul , Size(size) { Enum.Items = items; + Enum.StringSerialization = stringSerialization; } ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, GetInterfaceWrapper getInterfaceWrapper) @@ -270,6 +271,7 @@ ScriptingType::ScriptingType(const ScriptingType& other) break; case ScriptingTypes::Enum: Enum.Items = other.Enum.Items; + Enum.StringSerialization = other.Enum.StringSerialization; break; case ScriptingTypes::Interface: Interface.SetupScriptVTable = other.Interface.SetupScriptVTable; @@ -323,6 +325,7 @@ ScriptingType::ScriptingType(ScriptingType&& other) break; case ScriptingTypes::Enum: Enum.Items = other.Enum.Items; + Enum.StringSerialization = other.Enum.StringSerialization; break; case ScriptingTypes::Interface: Interface.SetupScriptVTable = other.Interface.SetupScriptVTable; @@ -604,71 +607,57 @@ StringAnsiView ScriptingType::GetName() const return Fullname; } +#if BUILD_DEBUG || USE_EDITOR +#define INIT_TYPE(...) \ + module->Types.AddUninitialized(); \ + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, ##__VA_ARGS__); \ + if (module->TypeNameToTypeIndex.ContainsKey(fullname)) \ + LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); \ + module->TypeNameToTypeIndex[fullname] = TypeIndex; +#else +#define INIT_TYPE(...) \ + module->Types.AddUninitialized(); \ + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, ##__VA_ARGS__); \ + module->TypeNameToTypeIndex[fullname] = TypeIndex; +#endif + ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SpawnHandler spawn, ScriptingTypeInitializer* baseType, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { // Script - module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable, interfaces); -#if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(fullname)) - LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); -#endif - module->TypeNameToTypeIndex[fullname] = TypeIndex; + INIT_TYPE(size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable, interfaces); } ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { // Class - module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, baseType, interfaces); -#if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(fullname)) - LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); -#endif - module->TypeNameToTypeIndex[fullname] = TypeIndex; + INIT_TYPE(size, initRuntime, ctor, dtor, baseType, interfaces); } ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { // Structure - module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType, interfaces); -#if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(fullname)) - LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); -#endif - module->TypeNameToTypeIndex[fullname] = TypeIndex; + INIT_TYPE(size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType, interfaces); } -ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items) +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items, bool stringSerialization) : ScriptingTypeHandle(module, module->Types.Count()) { // Enum - module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, items); -#if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(fullname)) - LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); -#endif - module->TypeNameToTypeIndex[fullname] = TypeIndex; + INIT_TYPE(size, items, stringSerialization); } ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, ScriptingType::GetInterfaceWrapper getInterfaceWrapper) : ScriptingTypeHandle(module, module->Types.Count()) { // Interface - module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, initRuntime, setupScriptVTable, setupScriptObjectVTable, getInterfaceWrapper); -#if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(fullname)) - LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); -#endif - module->TypeNameToTypeIndex[fullname] = TypeIndex; + INIT_TYPE(initRuntime, setupScriptVTable, setupScriptObjectVTable, getInterfaceWrapper); } +#undef INIT_TYPE + CriticalSection BinaryModule::Locker; BinaryModule::BinaryModulesList& BinaryModule::GetModules() diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp index 791c25d80..4bc2045a4 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp @@ -609,6 +609,12 @@ MObject* MUtils::BoxVariant(const Variant& value) return MCore::Object::Box((void*)&value.AsData, Double3::TypeInitializer.GetClass()); case VariantType::Double4: return MCore::Object::Box((void*)&value.AsData, Double4::TypeInitializer.GetClass()); + case VariantType::Int2: + return MCore::Object::Box((void*)&value.AsData, Int2::TypeInitializer.GetClass()); + case VariantType::Int3: + return MCore::Object::Box((void*)&value.AsData, Int3::TypeInitializer.GetClass()); + case VariantType::Int4: + return MCore::Object::Box((void*)&value.AsData, Int4::TypeInitializer.GetClass()); case VariantType::Color: return MCore::Object::Box((void*)&value.AsData, stdTypes.ColorClass); case VariantType::Guid: @@ -880,6 +886,12 @@ MClass* MUtils::GetClass(const VariantType& value) return Double3::TypeInitializer.GetClass(); case VariantType::Double4: return Double4::TypeInitializer.GetClass(); + case VariantType::Int2: + return Int2::TypeInitializer.GetClass(); + case VariantType::Int3: + return Int3::TypeInitializer.GetClass(); + case VariantType::Int4: + return Int4::TypeInitializer.GetClass(); case VariantType::Color: return Color::TypeInitializer.GetClass(); case VariantType::Guid: @@ -970,6 +982,12 @@ MClass* MUtils::GetClass(const Variant& value) return Double3::TypeInitializer.GetClass(); case VariantType::Double4: return Double4::TypeInitializer.GetClass(); + case VariantType::Int2: + return Int2::TypeInitializer.GetClass(); + case VariantType::Int3: + return Int3::TypeInitializer.GetClass(); + case VariantType::Int4: + return Int4::TypeInitializer.GetClass(); case VariantType::Color: return stdTypes.ColorClass; case VariantType::Guid: @@ -1138,6 +1156,9 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, MType* type, bool& failed) CASE_IN_BUILD_TYPE(Double2, AsData); CASE_IN_BUILD_TYPE(Double3, AsData); CASE_IN_BUILD_TYPE(Double4, AsBlob.Data); + CASE_IN_BUILD_TYPE(Int2, AsData); + CASE_IN_BUILD_TYPE(Int3, AsData); + CASE_IN_BUILD_TYPE(Int4, AsData); #undef CASE_IN_BUILD_TYPE if (klass->IsValueType()) { diff --git a/Source/Engine/Scripting/ScriptingType.h b/Source/Engine/Scripting/ScriptingType.h index e1fb3dc04..8a15dd336 100644 --- a/Source/Engine/Scripting/ScriptingType.h +++ b/Source/Engine/Scripting/ScriptingType.h @@ -266,6 +266,8 @@ struct FLAXENGINE_API ScriptingType { // Enum items table (the last item name is null) EnumItem* Items; + // Enum uses string names serialization instead of integer values. + bool StringSerialization; } Enum; struct @@ -290,7 +292,7 @@ struct FLAXENGINE_API ScriptingType ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime = DefaultInitRuntime, SpawnHandler spawn = DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const InterfaceImplementation* interfaces = nullptr); ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); - ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items, bool stringSerialization); ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, GetInterfaceWrapper getInterfaceWrapper); ScriptingType(const ScriptingType& other); ScriptingType(ScriptingType&& other); @@ -339,7 +341,7 @@ struct FLAXENGINE_API ScriptingTypeInitializer : ScriptingTypeHandle ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime = ScriptingType::DefaultInitRuntime, ScriptingType::SpawnHandler spawn = ScriptingType::DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, ScriptingType::SetupScriptVTableHandler setupScriptVTable = nullptr, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); - ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items, bool stringSerialization); ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, ScriptingType::GetInterfaceWrapper getInterfaceWrapper); }; diff --git a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs index b8e07e448..4f2690863 100644 --- a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs +++ b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; namespace FlaxEngine.Json.JsonCustomSerializers @@ -44,6 +45,13 @@ namespace FlaxEngine.Json.JsonCustomSerializers ((JsonObjectContract)contract).ItemReferenceLoopHandling = ReferenceLoopHandling.Serialize; } + // Check if use enum serialization as string + var type = Nullable.GetUnderlyingType(objectType) ?? objectType; + if (type.IsEnum && type.GetCustomAttribute() != null) + { + contract.Converter = new StringEnumConverter(); + } + return contract; } @@ -53,19 +61,19 @@ namespace FlaxEngine.Json.JsonCustomSerializers var contract = base.CreateDictionaryContract(objectType); // Override contract to save enums keys as integer - if (contract.DictionaryKeyType?.IsEnum ?? false) + var keyType = contract.DictionaryKeyType; + if ((keyType?.IsEnum ?? false) && keyType.GetCustomAttribute() == null) { - var enumType = contract.DictionaryKeyType; contract.DictionaryKeyResolver = name => { try { - var e = Enum.Parse(enumType, name); + var e = Enum.Parse(keyType, name); name = Convert.ToInt32(e).ToString(); } - catch + catch (Exception ex) { - // Ignore errors + Debug.Logger.LogHandler.LogWrite(LogType.Warning, $"Failed to parse enum '{name}' as {keyType.Name}: {ex.Message}"); } return name; }; diff --git a/Source/Engine/Serialization/Serialization.cpp b/Source/Engine/Serialization/Serialization.cpp index 78fbf7ec5..bf3098140 100644 --- a/Source/Engine/Serialization/Serialization.cpp +++ b/Source/Engine/Serialization/Serialization.cpp @@ -49,6 +49,54 @@ void ISerializable::DeserializeIfExists(DeserializeStream& stream, const char* m var = defaultValue;\ } +void Serialization::SerializeEnum(ISerializable::SerializeStream& stream, uint32 v, ScriptingTypeHandle typeHandle) +{ + if (typeHandle) + { + // Check if serialize enum as string + const ScriptingType& type = typeHandle.GetType(); + if (type.Type == ScriptingTypes::Enum && type.Enum.StringSerialization) + { + const auto items = type.Enum.Items; + for (int32 i = 0; items[i].Name; i++) + { + if (items[i].Value == v) + { + stream.String(items[i].Name); + return; + } + } + } + } + stream.Uint(v); +} + +int32 Serialization::DeserializeEnum(ISerializable::DeserializeStream& stream, ScriptingTypeHandle typeHandle) +{ + if (stream.IsString() && typeHandle) + { + // Deserialize enum from string + const ScriptingType& type = typeHandle.GetType(); + if (type.Type == ScriptingTypes::Enum) + { + const auto str = stream.GetStringAnsiView(); + const auto items = type.Enum.Items; + for (int32 i = 0; items[i].Name; i++) + { + if (str == items[i].Name) + { + return (int32)items[i].Value; + } + } + int32 result; + if (!StringUtils::Parse(stream.GetString(), &result)) + return result; + LOG(Warning, "Failed to parse enum '{}' as {}", str.ToString(), type.Fullname.ToString()); + } + } + return DeserializeInt(stream); +} + bool Serialization::ShouldSerialize(const VariantType& v, const void* otherObj) { return !otherObj || v != *(VariantType*)otherObj; @@ -129,7 +177,6 @@ void Serialization::Serialize(ISerializable::SerializeStream& stream, const Vari stream.Int64(v.AsInt64); break; case VariantType::Uint64: - case VariantType::Enum: stream.Uint64(v.AsUint64); break; case VariantType::Float: @@ -222,6 +269,9 @@ void Serialization::Serialize(ISerializable::SerializeStream& stream, const Vari else stream.String("", 0); break; + case VariantType::Enum: + SerializeEnum(stream, (int32)v.AsUint64, v.Type.GetScriptingType()); + break; case VariantType::ManagedObject: case VariantType::Structure: { @@ -276,7 +326,6 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian v.AsInt64 = value.GetInt64(); break; case VariantType::Uint64: - case VariantType::Enum: v.AsUint64 = value.GetUint64(); break; case VariantType::Float: @@ -371,6 +420,9 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian CHECK(value.IsString()); v.SetTypename(value.GetStringAnsiView()); break; + case VariantType::Enum: + v.AsInt64 = DeserializeEnum(value, v.Type.GetScriptingType()); + break; case VariantType::ManagedObject: case VariantType::Structure: { diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h index 9af6d7be1..41ae4898a 100644 --- a/Source/Engine/Serialization/Serialization.h +++ b/Source/Engine/Serialization/Serialization.h @@ -38,12 +38,16 @@ namespace Serialization int32 result = 0; if (stream.IsInt()) result = stream.GetInt(); + else if (stream.IsInt64()) + result = (int32)stream.GetInt64(); else if (stream.IsFloat()) result = (int32)stream.GetFloat(); else if (stream.IsString()) StringUtils::Parse(stream.GetString(), &result); return result; } + FLAXENGINE_API void SerializeEnum(ISerializable::SerializeStream& stream, uint32 v, ScriptingTypeHandle typeHandle); + FLAXENGINE_API int32 DeserializeEnum(ISerializable::DeserializeStream& stream, ScriptingTypeHandle typeHandle); // In-build types @@ -226,12 +230,12 @@ namespace Serialization template inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, const T& v, const void* otherObj) { - stream.Uint((uint32)v); + SerializeEnum(stream, (uint32)v, StaticType()); } template inline typename TEnableIf::Value>::Type Deserialize(ISerializable::DeserializeStream& stream, T& v, ISerializeModifier* modifier) { - v = (T)DeserializeInt(stream); + v = (T)DeserializeEnum(stream, StaticType()); } // Common types diff --git a/Source/Engine/Serialization/Stream.cpp b/Source/Engine/Serialization/Stream.cpp index 5b109fd55..0ba3ced8b 100644 --- a/Source/Engine/Serialization/Stream.cpp +++ b/Source/Engine/Serialization/Stream.cpp @@ -288,12 +288,15 @@ void ReadStream::Read(Variant& data) break; } case VariantType::Float2: + case VariantType::Int2: ReadBytes(&data.AsData, sizeof(Float2)); break; case VariantType::Float3: + case VariantType::Int3: ReadBytes(&data.AsData, sizeof(Float3)); break; case VariantType::Float4: + case VariantType::Int4: ReadBytes(&data.AsData, sizeof(Float4)); break; case VariantType::Double2: @@ -687,12 +690,15 @@ void WriteStream::Write(const Variant& data) Write(id); break; case VariantType::Float2: + case VariantType::Int2: WriteBytes(data.AsData, sizeof(Float2)); break; case VariantType::Float3: + case VariantType::Int3: WriteBytes(data.AsData, sizeof(Float3)); break; case VariantType::Float4: + case VariantType::Int4: WriteBytes(data.AsData, sizeof(Float4)); break; case VariantType::Double2: diff --git a/Source/Engine/Tests/TestPrefabs.cpp b/Source/Engine/Tests/TestPrefabs.cpp index 91bce81cc..fb34a7284 100644 --- a/Source/Engine/Tests/TestPrefabs.cpp +++ b/Source/Engine/Tests/TestPrefabs.cpp @@ -8,6 +8,7 @@ #include "Engine/Level/Actors/EmptyActor.h" #include "Engine/Level/Actors/DirectionalLight.h" #include "Engine/Level/Actors/ExponentialHeightFog.h" +#include "Engine/Level/Actors/AnimatedModel.h" #include "Engine/Level/Prefabs/Prefab.h" #include "Engine/Level/Prefabs/PrefabManager.h" #include "Engine/Scripting/ScriptingObjectReference.h" @@ -905,4 +906,124 @@ TEST_CASE("Prefabs") instance1->DeleteObject(); instance2->DeleteObject(); } + SECTION("Test Adding Object To Base Prefab") + { + // https://github.com/LOOPDISK/FlaxEngine/pull/44 + + // Create inner prefab with 3 objects in hierarchy + AssetReference prefabInner = Content::CreateVirtualAsset(); + REQUIRE(prefabInner); + Guid id; + Guid::Parse("15dbe4b0416be0777a6ce59e8788b10f", id); + prefabInner->ChangeID(id); + auto prefabInnerInit = prefabInner->Init(Prefab::TypeName, + "[" + "{" + "\"ID\": \"3de462104f56f681c14650a0171f88fb\"," + "\"TypeName\" : \"FlaxEngine.SpotLight\"," + "\"Name\" : \"Inner.Root\"" + "}," + "{" + "\"ID\": \"19b181f846b6911635ffacb902c93c6a\"," + "\"TypeName\" : \"FlaxEngine.StaticModel\"," + "\"ParentID\" : \"3de462104f56f681c14650a0171f88fb\"," + "\"Name\" : \"Inner.Cube\"" + "}," + "{" + "\"ID\": \"8950889f4a2e752d55165fbf10eaf184\"," + "\"TypeName\" : \"FlaxEngine.AnimatedModel\"," + "\"ParentID\" : \"19b181f846b6911635ffacb902c93c6a\"," + "\"Name\" : \"Inner.Model\"" + "}" + "]"); + REQUIRE(!prefabInnerInit); + + // Create outer prefab with 2 instances of inner prefab + AssetReference prefabOuter = Content::CreateVirtualAsset(); + REQUIRE(prefabOuter); + SCOPE_EXIT{ Content::DeleteAsset(prefabOuter); }; + Guid::Parse("2ab744714f746e31855f41815612d14b", id); + prefabOuter->ChangeID(id); + auto prefabOuterInit = prefabOuter->Init(Prefab::TypeName, + "[" + "{" + "\"ID\": \"dba7f4bb4acfd62608b9a8bf550f31a5\"," + "\"TypeName\": \"FlaxEngine.EmptyActor\"," + "\"Name\": \"Outer.Root\"" + "}," + "{" + "\"ID\": \"a3b705284432bed9f043829c04a2bc8f\"," + "\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\"," + "\"PrefabObjectID\": \"3de462104f56f681c14650a0171f88fb\"," + "\"ParentID\": \"dba7f4bb4acfd62608b9a8bf550f31a5\"," + "\"Name\": \"Instance 1\"" + "}," + "{" + "\"ID\": \"06a8c15a41b822dd27f3ac9d79b142d3\"," + "\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\"," + "\"PrefabObjectID\": \"19b181f846b6911635ffacb902c93c6a\"," + "\"ParentID\": \"a3b705284432bed9f043829c04a2bc8f\"" + "}," + "{" + "\"ID\": \"4759fb9e4c4dda3b61ab5ab43949e42f\"," + "\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\"," + "\"PrefabObjectID\": \"8950889f4a2e752d55165fbf10eaf184\"," + "\"ParentID\": \"06a8c15a41b822dd27f3ac9d79b142d3\"" + "}," + "{" + "\"ID\": \"1225be664c0c081e714bbf93e09b99e4\"," + "\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\"," + "\"PrefabObjectID\": \"3de462104f56f681c14650a0171f88fb\"," + "\"ParentID\": \"dba7f4bb4acfd62608b9a8bf550f31a5\"," + "\"Name\": \"Instance 2\"" + "}," + "{" + "\"ID\": \"b397243540322182b806ad8339b7b617\"," + "\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\"," + "\"PrefabObjectID\": \"19b181f846b6911635ffacb902c93c6a\"," + "\"ParentID\": \"1225be664c0c081e714bbf93e09b99e4\"" + "}," + "{" + "\"ID\": \"2c3b8e824daf038a58df528a238ca2de\"," + "\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\"," + "\"PrefabObjectID\": \"8950889f4a2e752d55165fbf10eaf184\"," + "\"ParentID\": \"b397243540322182b806ad8339b7b617\"" + "}" + "]"); + REQUIRE(!prefabOuterInit); + + // Spawn test instances of both prefabs + ScriptingObjectReference instanceInner = PrefabManager::SpawnPrefab(prefabInner); + ScriptingObjectReference instanceOuter = PrefabManager::SpawnPrefab(prefabOuter); + + // Add new object to the inner prefab + instanceInner->Children[0]->GetOrAddChild(); + + // Apply changes + bool applyResult = PrefabManager::ApplyAll(instanceInner); + REQUIRE(!applyResult); + + // Check state of outer instance to properly reflect hierarchy + REQUIRE(instanceOuter); + REQUIRE(instanceOuter->Children.Count() == 2); + REQUIRE(instanceOuter->Children[0] != nullptr); + REQUIRE(instanceOuter->Children[0]->Children.Count() == 1); + REQUIRE(instanceOuter->Children[0]->Children[0]); + REQUIRE(instanceOuter->Children[0]->Children[0]->Children.Count() == 2); + REQUIRE(instanceOuter->Children[0]->Children[0]->Children[0]->Is()); + REQUIRE(instanceOuter->Children[0]->Children[0]->Children[1]->Is()); + REQUIRE(instanceOuter->Children[1] != nullptr); + REQUIRE(instanceOuter->Children[1]->Children.Count() == 1); + REQUIRE(instanceOuter->Children[1]->Children[0]); + REQUIRE(instanceOuter->Children[1]->Children[0]->Children.Count() == 2); + REQUIRE(instanceOuter->Children[1]->Children[0]->Children[0]->Is()); + REQUIRE(instanceOuter->Children[0]->Children[0]->Children[1]->Is()); + REQUIRE(instanceOuter->Children[0]->Children[0] != instanceOuter->Children[1]->Children[0]); + REQUIRE(instanceOuter->Children[0]->Children[0]->Children[0] != instanceOuter->Children[1]->Children[0]->Children[0]); + REQUIRE(instanceOuter->Children[0]->Children[0]->Children[1] != instanceOuter->Children[1]->Children[0]->Children[1]); + + // Cleanup + instanceInner->DeleteObject(); + instanceOuter->DeleteObject(); + } } diff --git a/Source/Engine/Tools/AudioTool/AudioTool.h b/Source/Engine/Tools/AudioTool/AudioTool.h index b0c9c4c76..289b61c72 100644 --- a/Source/Engine/Tools/AudioTool/AudioTool.h +++ b/Source/Engine/Tools/AudioTool/AudioTool.h @@ -70,7 +70,7 @@ public: /// /// The size of a single sample in bits. The clip will be converted to this bit depth on import. /// - API_FIELD(Attributes="EditorOrder(50), VisibleIf(nameof(ShowBtiDepth))") + API_FIELD(Attributes="EditorOrder(50), VisibleIf(nameof(ShowBitDepth))") BitDepth BitDepth = BitDepth::_16; String ToString() const; diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 73f1bf402..6bb50ae96 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -589,6 +589,10 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj SERIALIZE(TriangleReduction); SERIALIZE(SloppyOptimization); SERIALIZE(LODTargetError); + SERIALIZE(LODTargetErrorAbsolute); + SERIALIZE(LODLockBorder); + SERIALIZE(LODPreserveUVs); + SERIALIZE(LODPreserveUVsWeight); SERIALIZE(ImportMaterials); SERIALIZE(CreateEmptyMaterialSlots); SERIALIZE(ImportMaterialsAsInstances); @@ -646,6 +650,10 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi DESERIALIZE(TriangleReduction); DESERIALIZE(SloppyOptimization); DESERIALIZE(LODTargetError); + DESERIALIZE(LODTargetErrorAbsolute); + DESERIALIZE(LODLockBorder); + DESERIALIZE(LODPreserveUVs); + DESERIALIZE(LODPreserveUVsWeight); DESERIALIZE(ImportMaterials); DESERIALIZE(CreateEmptyMaterialSlots); DESERIALIZE(ImportMaterialsAsInstances); @@ -1945,8 +1953,9 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option // Automatic LOD generation if (options.GenerateLODs && options.LODCount > 1 && data.LODs.HasItems() && options.TriangleReduction < 1.0f - ZeroTolerance) { - InitMeshOpt(); + PROFILE_CPU_NAMED("GenerateLODs"); auto lodStartTime = DateTime::NowUTC(); + InitMeshOpt(); float triangleReduction = Math::Saturate(options.TriangleReduction); int32 lodCount = Math::Max(options.LODCount, data.LODs.Count()); int32 baseLOD = Math::Clamp(options.BaseLOD, 0, lodCount - 1); @@ -1983,13 +1992,51 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option continue; indices.Clear(); indices.Resize(srcMeshIndexCount); - int32 dstMeshIndexCount = {}; + int32 dstMeshIndexCount = 0; if (options.SloppyOptimization) + { + PROFILE_CPU_NAMED("meshopt_simplifySloppy"); dstMeshIndexCount = (int32)meshopt_simplifySloppy(indices.Get(), srcMesh->Indices.Get(), srcMeshIndexCount, (const float*)srcMesh->Positions.Get(), srcMeshVertexCount, sizeof(Float3), dstMeshIndexCountTarget, options.LODTargetError); + } else - dstMeshIndexCount = (int32)meshopt_simplify(indices.Get(), srcMesh->Indices.Get(), srcMeshIndexCount, (const float*)srcMesh->Positions.Get(), srcMeshVertexCount, sizeof(Float3), dstMeshIndexCountTarget, options.LODTargetError); - if (dstMeshIndexCount <= 0 || dstMeshIndexCount > indices.Count()) - continue; + { + // Build simplification flags + unsigned int simplifyOptions = 0; + if (options.LODLockBorder) + simplifyOptions |= meshopt_SimplifyLockBorder; + if (options.LODTargetErrorAbsolute) + simplifyOptions |= meshopt_SimplifyErrorAbsolute; + if (options.LODPreserveUVs && srcMesh->UVs.HasItems()) + { + // Pack UV channels as attributes for meshopt_simplifyWithAttributes + int32 uvChannelCount = srcMesh->UVs.Count(); + int32 attributeCount = uvChannelCount * 2; // 2 floats (U, V) per channel + Array attributes; + attributes.Resize(srcMeshVertexCount * attributeCount); + Array attributeWeights; + attributeWeights.Resize(attributeCount); + for (int32 ch = 0; ch < uvChannelCount; ch++) + { + for (int32 v = 0; v < srcMeshVertexCount; v++) + { + Float2 uv = srcMesh->UVs[ch][v]; + attributes[v * attributeCount + ch * 2 + 0] = uv.X; + attributes[v * attributeCount + ch * 2 + 1] = uv.Y; + } + attributeWeights[ch * 2 + 0] = options.LODPreserveUVsWeight; + attributeWeights[ch * 2 + 1] = options.LODPreserveUVsWeight; + } + PROFILE_CPU_NAMED("meshopt_simplifyWithAttributes"); + dstMeshIndexCount = (int32)meshopt_simplifyWithAttributes(indices.Get(), srcMesh->Indices.Get(), srcMeshIndexCount, (const float*)srcMesh->Positions.Get(), srcMeshVertexCount, sizeof(Float3), attributes.Get(), sizeof(float) * attributeCount, attributeWeights.Get(), attributeCount, nullptr, dstMeshIndexCountTarget, options.LODTargetError, simplifyOptions, nullptr); + } + else + { + PROFILE_CPU_NAMED("meshopt_simplify"); + dstMeshIndexCount = (int32)meshopt_simplify(indices.Get(), srcMesh->Indices.Get(), srcMeshIndexCount, (const float*)srcMesh->Positions.Get(), srcMeshVertexCount, sizeof(Float3), dstMeshIndexCountTarget, options.LODTargetError, simplifyOptions, nullptr); + } + } + if (dstMeshIndexCount <= 0 || dstMeshIndexCount >= indices.Count()) + continue; // Skip if failed to generate LOD or it doesn't have less vertices than source indices.Resize(dstMeshIndexCount); // Generate simplified vertex buffer remapping table (use only vertices from LOD index buffer) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index ff47e635d..f104bd83f 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -296,20 +296,32 @@ public: API_FIELD(Attributes="EditorOrder(1100), EditorDisplay(\"Level Of Detail\", \"Generate LODs\"), VisibleIf(nameof(ShowGeometry))") bool GenerateLODs = false; // The index of the LOD from the source model data to use as a reference for following LODs generation. - API_FIELD(Attributes="EditorOrder(1110), EditorDisplay(\"Level Of Detail\", \"Base LOD\"), VisibleIf(nameof(ShowGeometry)), Limit(0, 5, 0.065f)") + API_FIELD(Attributes="EditorOrder(1110), EditorDisplay(\"Level Of Detail\", \"Base LOD\"), VisibleIf(nameof(ShowGenerateLODs)), Limit(0, 5, 0.065f)") int32 BaseLOD = 0; // The amount of LODs to include in the model (all remaining ones starting from Base LOD will be generated). - API_FIELD(Attributes="EditorOrder(1120), EditorDisplay(\"Level Of Detail\", \"LOD Count\"), VisibleIf(nameof(ShowGeometry)), Limit(1, 6, 0.065f)") + API_FIELD(Attributes="EditorOrder(1120), EditorDisplay(\"Level Of Detail\", \"LOD Count\"), VisibleIf(nameof(ShowGenerateLODs)), Limit(1, 6, 0.065f)") int32 LODCount = 4; // The target amount of triangles for the generated LOD (based on the higher LOD). Normalized to range 0-1. For instance 0.4 cuts the triangle count to 40%. - API_FIELD(Attributes="EditorOrder(1130), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGeometry)), Limit(0, 1, 0.001f)") + API_FIELD(Attributes="EditorOrder(1130), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGenerateLODs)), Limit(0, 1, 0.001f)") float TriangleReduction = 0.5f; // Whether to do a sloppy mesh optimization. This is faster but does not follow the topology of the original mesh. - API_FIELD(Attributes="EditorOrder(1140), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGeometry))") + API_FIELD(Attributes="EditorOrder(1140), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGenerateLODs))") bool SloppyOptimization = false; // Target error is an approximate measure of the deviation from the original mesh using distance normalized to [0,1] range (e.g. 0.01 means that simplifier will try to maintain the error to be below 1% of the mesh extents). Only used if Sloppy is unchecked. - API_FIELD(Attributes="EditorOrder(1150), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(SloppyOptimization), true), VisibleIf(nameof(ShowGeometry)), Limit(0.01f, 1, 0.001f)") + API_FIELD(Attributes="EditorOrder(1150), EditorDisplay(\"Level Of Detail\", \"LOD Target Error\"), VisibleIf(nameof(SloppyOptimization), true), VisibleIf(nameof(ShowGenerateLODs)), Limit(0.01f, 1, 0.001f)") float LODTargetError = 0.05f; + // If checked, vertices on topological borders (edges without a paired triangle) will not be moved during simplification. Useful for meshes that tile or share edges with other meshes. + API_FIELD(Attributes="EditorOrder(1170), EditorDisplay(\"Level Of Detail\", \"Lock Border\"), VisibleIf(nameof(SloppyOptimization), true), VisibleIf(nameof(ShowGenerateLODs))") + bool LODLockBorder = false; + // If checked, the target error will be treated as absolute rather than relative to the mesh extents. In that mode, error is defined in absolute units which can be universal across similar mesh types no matter their size. + API_FIELD(Attributes="EditorOrder(1160), EditorDisplay(\"Level Of Detail\", \"LOD Target Error Absolute\"), VisibleIf(nameof(SloppyOptimization), true), VisibleIf(nameof(ShowGenerateLODs))") + bool LODTargetErrorAbsolute = false; + // If checked, UV channels will be included in the simplification error metric to preserve UV layout. Essential for trimsheets and atlased textures. + API_FIELD(Attributes="EditorOrder(1180), EditorDisplay(\"Level Of Detail\", \"Preserve UVs\"), VisibleIf(nameof(SloppyOptimization), true), VisibleIf(nameof(ShowGenerateLODs))") + bool LODPreserveUVs = false; + // The weight of UV attributes in the simplification error metric. Higher values preserve UVs more aggressively at the cost of geometric quality. Only used when Preserve UVs is enabled. + API_FIELD(Attributes="EditorOrder(1190), EditorDisplay(\"Level Of Detail\", \"Preserve UVs Weight\"), VisibleIf(nameof(LODPreserveUVs)), VisibleIf(nameof(SloppyOptimization), true), VisibleIf(nameof(ShowGenerateLODs)), Limit(0.001f, 1, 0.001f)") + float LODPreserveUVsWeight = 0.01f; public: // Materials diff --git a/Source/Engine/UI/GUI/Control.cs b/Source/Engine/UI/GUI/Control.cs index f3eae8a24..8ae3b3616 100644 --- a/Source/Engine/UI/GUI/Control.cs +++ b/Source/Engine/UI/GUI/Control.cs @@ -572,12 +572,12 @@ namespace FlaxEngine.GUI /// /// Starts the mouse tracking. Used by the scrollbars, splitters, etc. /// - /// If set to true will use mouse screen offset. + /// If set to true will wrap when it hits a screen border. [NoAnimate] - public void StartMouseCapture(bool useMouseScreenOffset = false) + public void StartMouseCapture(bool screenSpaceMouseWrap = false) { var parent = Root; - parent?.StartTrackingMouse(this, useMouseScreenOffset); + parent?.StartTrackingMouse(this, screenSpaceMouseWrap); } /// diff --git a/Source/Engine/Visject/GraphUtilities.cpp b/Source/Engine/Visject/GraphUtilities.cpp index 36bfe297f..13f43fc1a 100644 --- a/Source/Engine/Visject/GraphUtilities.cpp +++ b/Source/Engine/Visject/GraphUtilities.cpp @@ -256,6 +256,33 @@ void GraphUtilities::ApplySomeMathHere(Variant& v, Variant& a, MathOp1 op) vv.W = (double)op((float)aa.W); break; } + case VariantType::Int2: + { + Int2& vv = *(Int2*)v.AsData; + const Int2& aa = *(const Int2*)a.AsData; + vv.X = (int32)op((float)aa.X); + vv.Y = (int32)op((float)aa.Y); + break; + } + case VariantType::Int3: + { + Int3& vv = *(Int3*)v.AsData; + const Int3& aa = *(const Int3*)a.AsData; + vv.X = (int32)op((float)aa.X); + vv.Y = (int32)op((float)aa.Y); + vv.Z = (int32)op((float)aa.Z); + break; + } + case VariantType::Int4: + { + Int4& vv = *(Int4*)v.AsData; + const Int4& aa = *(const Int4*)a.AsData; + vv.X = (int32)op((float)aa.X); + vv.Y = (int32)op((float)aa.Y); + vv.Z = (int32)op((float)aa.Z); + vv.W = (int32)op((float)aa.W); + break; + } case VariantType::Quaternion: { Quaternion& vv = *(Quaternion*)v.AsData; @@ -381,6 +408,36 @@ void GraphUtilities::ApplySomeMathHere(Variant& v, Variant& a, Variant& b, MathO vv.W = (double)op((float)aa.W, (float)bb.W); break; } + case VariantType::Int2: + { + Int2& vv = *(Int2*)v.AsData; + const Int2& aa = *(const Int2*)a.AsData; + const Int2& bb = *(const Int2*)b.AsData; + vv.X = (int32)op((float)aa.X, (float)bb.X); + vv.Y = (int32)op((float)aa.Y, (float)bb.Y); + break; + } + case VariantType::Int3: + { + Int3& vv = *(Int3*)v.AsData; + const Int3& aa = *(const Int3*)a.AsData; + const Int3& bb = *(const Int3*)b.AsData; + vv.X = (int32)op((float)aa.X, (float)bb.X); + vv.Y = (int32)op((float)aa.Y, (float)bb.Y); + vv.Z = (int32)op((float)aa.Z, (float)bb.Z); + break; + } + case VariantType::Int4: + { + Int4& vv = *(Int4*)v.AsData; + const Int4& aa = *(const Int4*)a.AsData; + const Int4& bb = *(const Int4*)b.AsData; + vv.X = (int32)op((float)aa.X, (float)bb.X); + vv.Y = (int32)op((float)aa.Y, (float)bb.Y); + vv.Z = (int32)op((float)aa.Z, (float)bb.Z); + vv.W = (int32)op((float)aa.W, (float)bb.W); + break; + } case VariantType::Quaternion: { Quaternion& vv = *(Quaternion*)v.AsData; @@ -499,6 +556,39 @@ void GraphUtilities::ApplySomeMathHere(Variant& v, Variant& a, Variant& b, Varia vv.W = (double)op((float)aa.W, (float)bb.W, (float)cc.W); break; } + case VariantType::Int2: + { + Int2& vv = *(Int2*)v.AsData; + const Int3& aa = *(const Int2*)a.AsData; + const Int3& bb = *(const Int2*)b.AsData; + const Int3& cc = *(const Int2*)c.AsData; + vv.X = (int32)op((float)aa.X, (float)bb.X, (float)cc.X); + vv.Y = (int32)op((float)aa.Y, (float)bb.Y, (float)cc.Y); + break; + } + case VariantType::Int3: + { + Int3& vv = *(Int3*)v.AsData; + const Int3& aa = *(const Int3*)a.AsData; + const Int3& bb = *(const Int3*)b.AsData; + const Int3& cc = *(const Int3*)c.AsData; + vv.X = (int32)op((float)aa.X, (float)bb.X, (float)cc.X); + vv.Y = (int32)op((float)aa.Y, (float)bb.Y, (float)cc.Y); + vv.Z = (int32)op((float)aa.Z, (float)bb.Z, (float)cc.Z); + break; + } + case VariantType::Int4: + { + Int4& vv = *(Int4*)v.AsData; + const Int4& aa = *(const Int4*)a.AsData; + const Int4& bb = *(const Int4*)b.AsData; + const Int4& cc = *(const Int4*)c.AsData; + vv.X = (int32)op((float)aa.X, (float)bb.X, (float)cc.X); + vv.Y = (int32)op((float)aa.Y, (float)bb.Y, (float)cc.Y); + vv.Z = (int32)op((float)aa.Z, (float)bb.Z, (float)cc.Z); + vv.W = (int32)op((float)aa.W, (float)bb.W, (float)cc.W); + break; + } case VariantType::Quaternion: { Quaternion& vv = *(Quaternion*)v.AsData; diff --git a/Source/Engine/Visject/ShaderGraph.h b/Source/Engine/Visject/ShaderGraph.h index 5b17604d0..d22260a87 100644 --- a/Source/Engine/Visject/ShaderGraph.h +++ b/Source/Engine/Visject/ShaderGraph.h @@ -167,6 +167,15 @@ public: // Base return Base::onNodeLoaded(n); } + void Clear() override + { + FloatCurves.Clear(); + Float2Curves.Clear(); + Float3Curves.Clear(); + Float4Curves.Clear(); + + Base::Clear(); + } }; /// diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp index ec4f51364..2b18a7d71 100644 --- a/Source/Engine/Visject/VisjectGraph.cpp +++ b/Source/Engine/Visject/VisjectGraph.cpp @@ -766,7 +766,7 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value) structureValue = Variant::Cast(structureValue, typeVariantType); } structureValue.InvertInline(); // Extract any Float3/Int32 into Structure type from inlined format - const ScriptingTypeHandle structureValueTypeHandle = Scripting::FindScriptingType(structureValue.Type.GetTypeName()); + const ScriptingTypeHandle structureValueTypeHandle = structureValue.Type.GetScriptingType(); if (structureValue.Type.Type != VariantType::Structure || typeHandle != structureValueTypeHandle) { OnError(node, box, String::Format(TEXT("Cannot unpack value of type {0} to structure of type {1}"), structureValue.Type, typeName)); @@ -952,7 +952,7 @@ void VisjectExecutor::ProcessGroupTools(Box* box, Node* node, Value& value) #define SAMPLE_CURVE(id, curves, type, graphType) \ case id: \ { \ - const auto& curve = GetCurrentGraph()->curves[node->Data.Curve.CurveIndex]; \ + const auto& curve = GetCurrentGraph()->curves[node->CurveIndex]; \ const float time = (float)tryGetValue(node->GetBox(0), Value::Zero); \ value.Type = VariantType(VariantType::graphType); \ curve.Evaluate(*(type*)value.AsData, time, false); \ diff --git a/Source/Engine/Visject/VisjectGraph.h b/Source/Engine/Visject/VisjectGraph.h index 1f0de1ce0..0eceaf8cb 100644 --- a/Source/Engine/Visject/VisjectGraph.h +++ b/Source/Engine/Visject/VisjectGraph.h @@ -42,42 +42,6 @@ public: template class VisjectGraphNode : public GraphNode { -public: - struct CurveData - { - /// - /// The curve index. - /// - int32 CurveIndex; - }; - - /// - /// Custom cached data per node type. Compact to use as small amount of memory as possible. - /// - struct AdditionalData - { - union - { - CurveData Curve; - - struct - { - void* Method; - BinaryModule* Module; - int32 ParamsCount; - uint32 OutParamsMask; - bool IsStatic; - } InvokeMethod; - - struct - { - void* Field; - BinaryModule* Module; - bool IsStatic; - } GetSetField; - }; - }; - public: VisjectGraphNode() : GraphNode() @@ -85,10 +49,7 @@ public: } public: - /// - /// The custom data (depends on node type). Used to cache data for faster usage at runtime. - /// - AdditionalData Data; + int32 CurveIndex = MAX_uint16; /// /// The asset references. Linked resources such as Animation assets are referenced in graph data as ID. We need to keep valid refs to them at runtime to keep data in memory. @@ -148,7 +109,7 @@ public: #define SETUP_CURVE(id, curves, access) \ case id: \ { \ - n->Data.Curve.CurveIndex = curves.Count(); \ + n->CurveIndex = curves.Count(); \ auto& curve = curves.AddOne(); \ const int32 keyframesCount = n->Values[0].AsInt; \ auto& keyframes = curve.GetKeyframes(); \ @@ -177,9 +138,17 @@ public: } } - // Base return Base::onNodeLoaded(n); } + void Clear() override + { + FloatCurves.Clear(); + Float2Curves.Clear(); + Float3Curves.Clear(); + Float4Curves.Clear(); + + Base::Clear(); + } }; /// diff --git a/Source/Engine/Visject/VisjectMeta.cpp b/Source/Engine/Visject/VisjectMeta.cpp index 00f3ca3b4..ecef4c26c 100644 --- a/Source/Engine/Visject/VisjectMeta.cpp +++ b/Source/Engine/Visject/VisjectMeta.cpp @@ -9,13 +9,15 @@ bool VisjectMeta::Load(ReadStream* stream, bool loadData) { Release(); - int32 entries; + int32 entries = -1; stream->ReadInt32(&entries); + if (entries < 0 || entries > MAX_uint16) + return true; Entries.Resize(entries); for (int32 i = 0; i < entries; i++) { - Entry& e = Entries[i]; + Entry& e = Entries.Get()[i]; stream->ReadInt32(&e.TypeID); DateTime creationTime; diff --git a/Source/Platforms/Web/Binaries/Data/check_browser_version.js b/Source/Platforms/Web/Binaries/check_browser_version.js similarity index 100% rename from Source/Platforms/Web/Binaries/Data/check_browser_version.js rename to Source/Platforms/Web/Binaries/check_browser_version.js diff --git a/Source/Platforms/Web/Binaries/Data/check_jspi.js b/Source/Platforms/Web/Binaries/check_jspi.js similarity index 100% rename from Source/Platforms/Web/Binaries/Data/check_jspi.js rename to Source/Platforms/Web/Binaries/check_jspi.js diff --git a/Source/Platforms/Web/Binaries/Data/shell.html b/Source/Platforms/Web/Binaries/shell.html similarity index 100% rename from Source/Platforms/Web/Binaries/Data/shell.html rename to Source/Platforms/Web/Binaries/shell.html diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 08182d421..021d40dc6 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -202,11 +202,11 @@ namespace Flax.Build.Bindings var fullname = apiType.FullNameManaged; if (apiType.IsEnum) - return $"Variant::Enum(VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length})), {value})"; + return $"Variant::Enum(VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length}), !USE_EDITOR), {value})"; if (apiType.IsStruct && !CppInBuildVariantStructures.Contains(apiType.Name)) { if (apiType.IsInBuild) - return $"Variant::Structure(VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length})), {(typeInfo.IsPtr ? "*" + value : value)})"; + return $"Variant::Structure(VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length}), !USE_EDITOR), {(typeInfo.IsPtr ? "*" + value : value)})"; return $"Variant::Structure(VariantType(VariantType::Structure, {apiType.FullNameNative}::TypeInitializer.GetType()), {(typeInfo.IsPtr ? "*" + value : value)})"; } } @@ -268,7 +268,7 @@ namespace Flax.Build.Bindings { var elementType = FindApiTypeInfo(buildData, typeInfo.GenericArgs[0], caller); var elementName = $"{(elementType != null ? elementType.FullNameManaged : typeInfo.GenericArgs[0].Type)}[]"; - return $"VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length}))"; + return $"VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length}), !USE_EDITOR)"; } if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) return "VariantType(VariantType::Dictionary)"; @@ -279,11 +279,11 @@ namespace Flax.Build.Bindings { var fullname = apiType.FullNameManaged; if (apiType.IsEnum) - return $"VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length}))"; + return $"VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length}), !USE_EDITOR)"; if (apiType.IsStruct) { if (apiType.IsInBuild) - return $"VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length}))"; + return $"VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length}), !USE_EDITOR)"; return $"VariantType(VariantType::Structure, {apiType.FullNameNative}::TypeInitializer.GetType())"; } if (apiType.IsClass) @@ -2702,7 +2702,8 @@ namespace Flax.Build.Bindings contents.Append($"ScriptingTypeInitializer {enumTypeNameInternal}_TypeInitializer((BinaryModule*)GetBinaryModule{moduleInfo.Name}(), "); contents.Append($"StringAnsiView(\"{enumTypeNameManaged}\", {enumTypeNameManaged.Length}), "); contents.Append($"sizeof({enumTypeNameNative}), "); - contents.Append($"{enumTypeNameInternal}Internal::Items);").AppendLine(); + var stringSerialization = enumInfo.Attributes != null && enumInfo.Attributes.Contains("EnumString") ? "true" : "false"; + contents.Append($"{enumTypeNameInternal}Internal::Items, {stringSerialization});").AppendLine(); contents.AppendLine($"template<> {moduleInfo.Name.ToUpperInvariant()}_API ScriptingTypeHandle StaticType<{enumTypeNameNative}>() {{ return {enumTypeNameInternal}_TypeInitializer; }}"); } @@ -3044,7 +3045,7 @@ namespace Flax.Build.Bindings header.Append(" Variant result;").AppendLine(); var apiType = FindApiTypeInfo(buildData, valueType, moduleInfo); var elementName = $"{(apiType != null ? apiType.FullNameManaged : valueType.Type)}[]"; - header.Append($" result.SetType(VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length})));").AppendLine(); + header.Append($" result.SetType(VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length}), !USE_EDITOR));").AppendLine(); header.Append(" auto* array = reinterpret_cast*>(result.AsData);").AppendLine(); header.Append(" array->Resize(length);").AppendLine(); header.Append(" for (int32 i = 0; i < length; i++)").AppendLine(); diff --git a/Source/Tools/Flax.Build/Platforms/Web/WebToolchain.cs b/Source/Tools/Flax.Build/Platforms/Web/WebToolchain.cs index a465ccac0..6551e3625 100644 --- a/Source/Tools/Flax.Build/Platforms/Web/WebToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Web/WebToolchain.cs @@ -367,7 +367,7 @@ namespace Flax.Build.Platforms if (args.All(arg => !arg.Contains("-sASSERTIONS"))) { // minimum_runtime_check.js from Emscripten checks min browser versions only with ASSERTIONS enabled so use custom check - var checkBrowserVersion = File.ReadAllText(Path.Combine(Globals.EngineRoot, "Source/Platforms/Web/Binaries/Data/check_browser_version.js")); + var checkBrowserVersion = File.ReadAllText(Path.Combine(Globals.EngineRoot, "Source/Platforms/Web/Binaries/check_browser_version.js")); checkBrowserVersion = checkBrowserVersion.Replace("TARGET_NOT_SUPPORTED", "0x7fffffff"); checkBrowserVersion = checkBrowserVersion.Replace("MIN_CHROME_VERSION", minChrome.ToString()); checkBrowserVersion = checkBrowserVersion.Replace("MIN_FIREFOX_VERSION", minFirefox.ToString()); @@ -377,11 +377,11 @@ namespace Flax.Build.Platforms args.Add($"--pre-js \"{path}\""); } if (addJSPI) - args.Add($"--pre-js \"{Globals.EngineRoot}/Source/Platforms/Web/Binaries/Data/check_jspi.js\""); + args.Add($"--pre-js \"{Globals.EngineRoot}/Source/Platforms/Web/Binaries/check_jspi.js\""); // Customize output HTML shell if (options.LinkEnv.Output == LinkerOutput.Executable) - args.Add($"--shell-file \"{Globals.EngineRoot}/Source/Platforms/Web/Binaries/Data/shell.html\""); + args.Add($"--shell-file \"{Globals.EngineRoot}/Source/Platforms/Web/Binaries/shell.html\""); } args.Add("-Wl,--start-group"); diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs index 26afcd894..f91be996f 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs @@ -140,6 +140,11 @@ namespace Flax.Build.Platforms /// Windows 11 SDK (10.0.26100.0) 24H2 /// v10_0_26100_0, + + /// + /// Windows 11 SDK (10.0.28000.0) + /// + v10_0_28000_0, } /// @@ -333,6 +338,7 @@ namespace Flax.Build.Platforms case WindowsPlatformSDK.v10_0_22000_0: return new Version(10, 0, 22000, 0); case WindowsPlatformSDK.v10_0_22621_0: return new Version(10, 0, 22621, 0); case WindowsPlatformSDK.v10_0_26100_0: return new Version(10, 0, 26100, 0); + case WindowsPlatformSDK.v10_0_28000_0: return new Version(10, 0, 28000, 0); default: throw new ArgumentOutOfRangeException(nameof(sdk), sdk, null); } } @@ -388,6 +394,7 @@ namespace Flax.Build.Platforms WindowsPlatformSDK.v10_0_22000_0, WindowsPlatformSDK.v10_0_22621_0, WindowsPlatformSDK.v10_0_26100_0, + WindowsPlatformSDK.v10_0_28000_0, }; foreach (var sdk10 in sdk10Roots) { diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs index 750a16aab..5ce13dd18 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs @@ -314,6 +314,7 @@ namespace Flax.Build.Platforms case WindowsPlatformSDK.v10_0_22000_0: case WindowsPlatformSDK.v10_0_22621_0: case WindowsPlatformSDK.v10_0_26100_0: + case WindowsPlatformSDK.v10_0_28000_0: { var sdkVersionName = WindowsPlatformBase.GetSDKVersion(SDK).ToString(); string includeRootDir = Path.Combine(windowsSdkDir, "include", sdkVersionName);