diff --git a/.gitattributes b/.gitattributes index f84404f19..df7d89342 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ # Set the default behavior, in case people don't have core.autocrlf set. -* text=auto +* text=auto eol=lf # Explicitly declare text files you want to always be normalized and converted to native line endings on checkout. *.c text diff=cpp diff --git a/Flax.flaxproj b/Flax.flaxproj index d8e86b1ed..7cebe1714 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -3,7 +3,7 @@ "Version": { "Major": 1, "Minor": 6, - "Build": 6342 + "Build": 6343 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.", diff --git a/Source/Editor/Content/Items/ContentFolder.cs b/Source/Editor/Content/Items/ContentFolder.cs index 86c374fee..57e55d3fe 100644 --- a/Source/Editor/Content/Items/ContentFolder.cs +++ b/Source/Editor/Content/Items/ContentFolder.cs @@ -113,7 +113,15 @@ namespace FlaxEditor.Content public override ContentItemSearchFilter SearchFilter => ContentItemSearchFilter.Other; /// - public override bool CanRename => ParentFolder != null; // Deny rename action for root folders + public override bool CanRename + { + get + { + var hasParentFolder = ParentFolder != null; + var isContentFolder = Node is MainContentTreeNode; + return hasParentFolder && !isContentFolder; + } + } /// public override bool CanDrag => ParentFolder != null; // Deny rename action for root folders diff --git a/Source/Editor/CustomEditorWindow.cs b/Source/Editor/CustomEditorWindow.cs index 2d5293f30..242b4d28d 100644 --- a/Source/Editor/CustomEditorWindow.cs +++ b/Source/Editor/CustomEditorWindow.cs @@ -3,7 +3,6 @@ using FlaxEditor.CustomEditors; using FlaxEditor.Windows; using FlaxEngine.GUI; -using FlaxEngine; using DockState = FlaxEditor.GUI.Docking.DockState; namespace FlaxEditor @@ -86,8 +85,12 @@ namespace FlaxEditor if (!FlaxEngine.Scripting.IsTypeFromGameScripts(type)) return; - Editor.Instance.Windows.AddToRestore(this); + if (!Window.IsHidden) + { + Editor.Instance.Windows.AddToRestore(this); + } Window.Close(); + Window.Dispose(); } /// diff --git a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs index 28c039dc8..5a10b9a52 100644 --- a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs @@ -167,7 +167,7 @@ namespace FlaxEditor.CustomEditors.Dedicated Presenter.Undo?.AddAction(new MultiUndoAction(actions)); // Build ragdoll - SceneGraph.Actors.AnimatedModelNode.BuildRagdoll(animatedModel, options, ragdoll); + AnimatedModelNode.BuildRagdoll(animatedModel, options, ragdoll); } private void OnRebuildBone(Button button) @@ -191,7 +191,7 @@ namespace FlaxEditor.CustomEditors.Dedicated } // Build ragdoll - SceneGraph.Actors.AnimatedModelNode.BuildRagdoll(animatedModel, new AnimatedModelNode.RebuildOptions(), ragdoll, name); + AnimatedModelNode.BuildRagdoll(animatedModel, new AnimatedModelNode.RebuildOptions(), ragdoll, name); } private void OnRemoveBone(Button button) diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs index 64578570c..2e769f431 100644 --- a/Source/Editor/Editor.Build.cs +++ b/Source/Editor/Editor.Build.cs @@ -37,7 +37,8 @@ public class Editor : EditorModule { base.Setup(options); - options.ScriptingAPI.SystemReferences.Add("System.Private.Xml"); + options.ScriptingAPI.SystemReferences.Add("System.Xml"); + options.ScriptingAPI.SystemReferences.Add("System.Xml.ReaderWriter"); options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions"); options.ScriptingAPI.SystemReferences.Add("System.ComponentModel.TypeConverter"); diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp index 8a92d11f4..527bfeb6d 100644 --- a/Source/Editor/Editor.cpp +++ b/Source/Editor/Editor.cpp @@ -184,7 +184,7 @@ bool Editor::CheckProjectUpgrade() " BuildNativeCode = false;\n" " }}\n" "}}\n" - ), codeName), Encoding::Unicode); + ), codeName), Encoding::UTF8); if (useEditorModule) { File::WriteAllText(gameEditorModuleFolder / String::Format(TEXT("{0}Editor.Build.cs"), codeName), String::Format(TEXT( @@ -211,7 +211,7 @@ bool Editor::CheckProjectUpgrade() " BuildNativeCode = false;\n" " }}\n" "}}\n" - ), codeName), Encoding::Unicode); + ), codeName), Encoding::UTF8); } // Generate target files @@ -229,7 +229,7 @@ bool Editor::CheckProjectUpgrade() " Modules.Add(\"{0}\");\n" " }}\n" "}}\n" - ), codeName), Encoding::Unicode); + ), codeName), Encoding::UTF8); const String editorTargetGameEditorModule = useEditorModule ? String::Format(TEXT(" Modules.Add(\"{0}Editor\");\n"), codeName) : String::Empty; File::WriteAllText(sourceFolder / String::Format(TEXT("{0}EditorTarget.Build.cs"), codeName), String::Format(TEXT( "using Flax.Build;\n" @@ -246,7 +246,7 @@ bool Editor::CheckProjectUpgrade() "{1}" " }}\n" "}}\n" - ), codeName, editorTargetGameEditorModule), Encoding::Unicode); + ), codeName, editorTargetGameEditorModule), Encoding::UTF8); // Generate new project file Project->ProjectPath = root / String::Format(TEXT("{0}.flaxproj"), codeName); @@ -454,7 +454,7 @@ int32 Editor::LoadProduct() " // Reference the modules for game\n" " Modules.Add(\"Game\");\n" " }\n" - "}\n"), Encoding::Unicode); + "}\n"), Encoding::UTF8); failed |= File::WriteAllText(projectPath / TEXT("Source/GameEditorTarget.Build.cs"),TEXT( "using Flax.Build;\n" "\n" @@ -468,7 +468,7 @@ int32 Editor::LoadProduct() " // Reference the modules for editor\n" " Modules.Add(\"Game\");\n" " }\n" - "}\n"), Encoding::Unicode); + "}\n"), Encoding::UTF8); failed |= File::WriteAllText(projectPath / TEXT("Source/Game/Game.Build.cs"),TEXT( "using Flax.Build;\n" "using Flax.Build.NativeCpp;\n" @@ -496,7 +496,7 @@ int32 Editor::LoadProduct() " // To add C++ define use: options.PublicDefinitions.Add(\"COMPILE_WITH_FLAX\");\n" " // To learn more see scripting documentation.\n" " }\n" - "}\n"), Encoding::Unicode); + "}\n"), Encoding::UTF8); if (failed) return 12; } diff --git a/Source/Editor/GUI/Popups/TypeSearchPopup.cs b/Source/Editor/GUI/Popups/TypeSearchPopup.cs index 9d9ee2040..73c5baab7 100644 --- a/Source/Editor/GUI/Popups/TypeSearchPopup.cs +++ b/Source/Editor/GUI/Popups/TypeSearchPopup.cs @@ -27,6 +27,17 @@ namespace FlaxEditor.GUI /// public ScriptType Type => _type; + /// + /// Initializes a new instance of the class. + /// + public TypeItemView() + { + _type = ScriptType.Null; + Name = ""; + TooltipText = "Unset value."; + Tag = _type; + } + /// /// Initializes a new instance of the class. /// @@ -83,6 +94,7 @@ namespace FlaxEditor.GUI // TODO: use async thread to search types without UI stall var allTypes = Editor.Instance.CodeEditing.All.Get(); + AddItem(new TypeItemView()); for (int i = 0; i < allTypes.Count; i++) { var type = allTypes[i]; diff --git a/Source/Editor/Gizmo/GridGizmo.cs b/Source/Editor/Gizmo/GridGizmo.cs index a36bb8da1..8b4f49986 100644 --- a/Source/Editor/Gizmo/GridGizmo.cs +++ b/Source/Editor/Gizmo/GridGizmo.cs @@ -12,19 +12,98 @@ namespace FlaxEditor.Gizmo [HideInEditor] public class GridGizmo : GizmoBase { - private bool _enabled = true; + [HideInEditor] + private sealed class Renderer : PostProcessEffect + { + private IntPtr _debugDrawContext; + + public Renderer() + { + Order = -100; + UseSingleTarget = true; + Location = PostProcessEffectLocation.BeforeForwardPass; + } + + ~Renderer() + { + if (_debugDrawContext != IntPtr.Zero) + { + DebugDraw.FreeContext(_debugDrawContext); + _debugDrawContext = IntPtr.Zero; + } + } + + public override void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input, GPUTexture output) + { + Profiler.BeginEventGPU("Editor Grid"); + + if (_debugDrawContext == IntPtr.Zero) + _debugDrawContext = DebugDraw.AllocateContext(); + DebugDraw.SetContext(_debugDrawContext); + DebugDraw.UpdateContext(_debugDrawContext, 1.0f / Mathf.Max(Engine.FramesPerSecond, 1)); + + var viewPos = (Vector3)renderContext.View.Position; + var plane = new Plane(Vector3.Zero, Vector3.UnitY); + var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos); + + float space, size; + if (dst <= 500.0f) + { + space = 50; + size = 8000; + } + else if (dst <= 2000.0f) + { + space = 100; + size = 8000; + } + else + { + space = 1000; + size = 100000; + } + + Color color = Color.Gray * 0.7f; + int count = (int)(size / space); + + Vector3 start = new Vector3(0, 0, size * -0.5f); + Vector3 end = new Vector3(0, 0, size * 0.5f); + + for (int i = 0; i <= count; i++) + { + start.X = end.X = i * space + start.Z; + DebugDraw.DrawLine(start, end, color); + } + + start = new Vector3(size * -0.5f, 0, 0); + end = new Vector3(size * 0.5f, 0, 0); + + for (int i = 0; i <= count; i++) + { + start.Z = end.Z = i * space + start.X; + DebugDraw.DrawLine(start, end, color); + } + + DebugDraw.Draw(ref renderContext, input.View(), null, true); + DebugDraw.SetContext(IntPtr.Zero); + + Profiler.EndEventGPU(); + } + } + + private Renderer _renderer; /// /// Gets or sets a value indicating whether this is enabled. /// public bool Enabled { - get => _enabled; + get => _renderer.Enabled; set { - if (_enabled != value) + if (_renderer.Enabled != value) { - _enabled = value; + _renderer.Enabled = value; EnabledChanged?.Invoke(this); } } @@ -42,55 +121,16 @@ namespace FlaxEditor.Gizmo public GridGizmo(IGizmoOwner owner) : base(owner) { + _renderer = new Renderer(); + owner.RenderTask.AddCustomPostFx(_renderer); } - /// - public override void Draw(ref RenderContext renderContext) + /// + /// Destructor. + /// + ~GridGizmo() { - if (!Enabled) - return; - - var viewPos = Owner.ViewPosition; - var plane = new Plane(Vector3.Zero, Vector3.UnitY); - var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos); - - float space, size; - if (dst <= 500.0f) - { - space = 50; - size = 8000; - } - else if (dst <= 2000.0f) - { - space = 100; - size = 8000; - } - else - { - space = 1000; - size = 100000; - } - - Color color = Color.Gray * 0.7f; - int count = (int)(size / space); - - Vector3 start = new Vector3(0, 0, size * -0.5f); - Vector3 end = new Vector3(0, 0, size * 0.5f); - - for (int i = 0; i <= count; i++) - { - start.X = end.X = i * space + start.Z; - DebugDraw.DrawLine(start, end, color); - } - - start = new Vector3(size * -0.5f, 0, 0); - end = new Vector3(size * 0.5f, 0, 0); - - for (int i = 0; i <= count; i++) - { - start.Z = end.Z = i * space + start.X; - DebugDraw.DrawLine(start, end, color); - } + FlaxEngine.Object.Destroy(ref _renderer); } } } diff --git a/Source/Editor/ProjectInfo.cs b/Source/Editor/ProjectInfo.cs index ed9470c35..b00c4e042 100644 --- a/Source/Editor/ProjectInfo.cs +++ b/Source/Editor/ProjectInfo.cs @@ -20,7 +20,7 @@ namespace FlaxEditor /// The to write to. /// The value. /// The calling serializer. - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value == null) { @@ -44,7 +44,7 @@ namespace FlaxEditor /// The existing property value of the JSON that is being converted. /// The calling serializer. /// The object value. - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) { diff --git a/Source/Editor/ProjectInfo.h b/Source/Editor/ProjectInfo.h index 256269cc9..9f52e414f 100644 --- a/Source/Editor/ProjectInfo.h +++ b/Source/Editor/ProjectInfo.h @@ -112,6 +112,7 @@ public: { Version = ::Version(1, 0); DefaultSceneSpawn = Ray(Vector3::Zero, Vector3::Forward); + DefaultScene = Guid::Empty; } /// diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs index d174b7dff..454c3a5d2 100644 --- a/Source/Editor/Scripting/ScriptType.cs +++ b/Source/Editor/Scripting/ScriptType.cs @@ -832,7 +832,7 @@ namespace FlaxEditor.Scripting get { if (_managed != null) - return _managed.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null) != null; + return _managed.IsValueType || _managed.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null) != null; return _custom?.CanCreateInstance ?? false; } } @@ -893,9 +893,16 @@ namespace FlaxEditor.Scripting { if (_managed != null) { - var ctor = _managed.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); object value = RuntimeHelpers.GetUninitializedObject(_managed); - ctor.Invoke(value, null); + if (!_managed.IsValueType) + { + var ctor = _managed.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); +#if !BUILD_RELEASE + if (ctor == null) + throw new Exception($"Missing empty constructor for type {_managed.FullName}."); +#endif + ctor.Invoke(value, null); + } return value; } return _custom.CreateInstance(); diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp index eaffe5691..3082a531e 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.cpp +++ b/Source/Editor/Scripting/ScriptsBuilder.cpp @@ -613,6 +613,28 @@ bool ScriptsBuilderService::Init() const String targetOutput = Globals::ProjectFolder / TEXT("Binaries") / target / platform / architecture / configuration; Array files; FileSystem::DirectoryGetFiles(files, targetOutput, TEXT("*.HotReload.*"), DirectorySearchOption::TopDirectoryOnly); + + for (const auto& reference : Editor::Project->References) + { + if (reference.Project->Name == TEXT("Flax")) + continue; + + String referenceTarget; + if (reference.Project->EditorTarget.HasChars()) + { + referenceTarget = reference.Project->EditorTarget.Get(); + } + else if (reference.Project->GameTarget.HasChars()) + { + referenceTarget = reference.Project->GameTarget.Get(); + } + if (referenceTarget.IsEmpty()) + continue; + + const String referenceTargetOutput = reference.Project->ProjectFolderPath / TEXT("Binaries") / referenceTarget / platform / architecture / configuration; + FileSystem::DirectoryGetFiles(files, referenceTargetOutput, TEXT("*.HotReload.*"), DirectorySearchOption::TopDirectoryOnly); + } + if (files.HasItems()) LOG(Info, "Removing {0} files from previous Editor run hot-reloads", files.Count()); for (auto& file : files) diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index d980b0c4a..fe801bbaf 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -1125,7 +1125,9 @@ namespace FlaxEditor.Utilities public static string GetAssetNamePathWithExt(string path) { var projectFolder = Globals.ProjectFolder; - if (path.StartsWith(projectFolder)) + if (path == projectFolder) + path = string.Empty; + else if (path.StartsWith(projectFolder)) path = path.Substring(projectFolder.Length + 1); return path; } diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index f56370352..b4fb8f8f1 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -1168,14 +1168,15 @@ namespace FlaxEditor.Viewport { offset = Float2.Zero; } - offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X); - offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y); - _mouseDelta = offset / size; - _mouseDelta.Y *= size.Y / size.X; var mouseDelta = Float2.Zero; if (_useMouseFiltering) { + offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X); + offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y); + _mouseDelta = offset / size; + _mouseDelta.Y *= size.Y / size.X; + // Update delta filtering buffer _deltaFilteringBuffer[_deltaFilteringStep] = _mouseDelta; _deltaFilteringStep++; @@ -1192,6 +1193,8 @@ namespace FlaxEditor.Viewport } else { + _mouseDelta = offset / size; + _mouseDelta.Y *= size.Y / size.X; mouseDelta = _mouseDelta; } diff --git a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs index 2310668b6..9a7ec6800 100644 --- a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs +++ b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs @@ -3,8 +3,6 @@ using System; using FlaxEditor.GUI.ContextMenu; using FlaxEngine; -using FlaxEditor.GUI.Input; -using FlaxEngine.GUI; using Object = FlaxEngine.Object; namespace FlaxEditor.Viewport.Previews @@ -15,11 +13,10 @@ namespace FlaxEditor.Viewport.Previews /// public class AnimatedModelPreview : AssetPreview { - private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton; - private bool _showNodes, _showBounds, _showFloor, _showCurrentLOD, _showNodesNames; private AnimatedModel _previewModel; + private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton; + private bool _showNodes, _showBounds, _showFloor, _showNodesNames; private StaticModel _floorModel; - private ContextMenuButton _showCurrentLODButton; private bool _playAnimation, _playAnimationOnce; private float _playSpeed = 1.0f; @@ -207,26 +204,6 @@ namespace FlaxEditor.Viewport.Previews // Show Floor _showFloorButton = ViewWidgetShowMenu.AddButton("Floor", button => ShowFloor = !ShowFloor); _showFloorButton.IndexInParent = 1; - - // Show Current LOD - _showCurrentLODButton = ViewWidgetShowMenu.AddButton("Current LOD", button => - { - _showCurrentLOD = !_showCurrentLOD; - _showCurrentLODButton.Icon = _showCurrentLOD ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; - }); - _showCurrentLODButton.IndexInParent = 2; - - // Preview LOD - { - var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD"); - previewLOD.CloseMenuOnClick = false; - var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f) - { - Parent = previewLOD - }; - previewLODValue.ValueChanged += () => _previewModel.ForcedLOD = previewLODValue.Value; - ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = _previewModel.ForcedLOD; - } } // Enable shadows @@ -339,44 +316,6 @@ namespace FlaxEditor.Viewport.Previews _previewModel.ResetAnimation(); } - private int ComputeLODIndex(SkinnedModel model) - { - if (PreviewActor.ForcedLOD != -1) - return PreviewActor.ForcedLOD; - - // Based on RenderTools::ComputeModelLOD - CreateProjectionMatrix(out var projectionMatrix); - float screenMultiple = 0.5f * Mathf.Max(projectionMatrix.M11, projectionMatrix.M22); - var sphere = PreviewActor.Sphere; - var viewOrigin = ViewPosition; - var distSqr = Vector3.DistanceSquared(ref sphere.Center, ref viewOrigin); - var screenRadiusSquared = Mathf.Square(screenMultiple * sphere.Radius) / Mathf.Max(1.0f, distSqr); - - // Check if model is being culled - if (Mathf.Square(model.MinScreenSize * 0.5f) > screenRadiusSquared) - return -1; - - // Skip if no need to calculate LOD - if (model.LoadedLODs == 0) - return -1; - var lods = model.LODs; - if (lods.Length == 0) - return -1; - if (lods.Length == 1) - return 0; - - // Iterate backwards and return the first matching LOD - for (int lodIndex = lods.Length - 1; lodIndex >= 0; lodIndex--) - { - if (Mathf.Square(lods[lodIndex].ScreenSize * 0.5f) >= screenRadiusSquared) - { - return lodIndex + PreviewActor.LODBias; - } - } - - return 0; - } - /// protected override void OnDebugDraw(GPUContext context, ref RenderContext renderContext) { @@ -440,45 +379,6 @@ namespace FlaxEditor.Viewport.Previews } } - /// - public override void Draw() - { - base.Draw(); - - var skinnedModel = _previewModel.SkinnedModel; - if (skinnedModel == null || !skinnedModel.IsLoaded) - return; - var lods = skinnedModel.LODs; - if (lods.Length == 0) - { - // Force show skeleton for models without geometry - ShowNodes = true; - return; - } - if (_showCurrentLOD) - { - var lodIndex = ComputeLODIndex(skinnedModel); - string text = string.Format("Current LOD: {0}", lodIndex); - if (lodIndex != -1) - { - lodIndex = Mathf.Clamp(lodIndex + PreviewActor.LODBias, 0, lods.Length - 1); - var lod = lods[lodIndex]; - int triangleCount = 0, vertexCount = 0; - for (int meshIndex = 0; meshIndex < lod.Meshes.Length; meshIndex++) - { - var mesh = lod.Meshes[meshIndex]; - triangleCount += mesh.TriangleCount; - vertexCount += mesh.VertexCount; - } - text += string.Format("\nTriangles: {0:N0}\nVertices: {1:N0}", triangleCount, vertexCount); - } - var font = Style.Current.FontMedium; - var pos = new Float2(10, 50); - Render2D.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black); - Render2D.DrawText(font, text, new Rectangle(pos, Size), Color.White); - } - } - /// public override void Update(float deltaTime) { @@ -498,14 +398,21 @@ namespace FlaxEditor.Viewport.Previews } } + /// + /// Resets the camera to focus on a object. + /// + public void ResetCamera() + { + ViewportCamera.SetArcBallView(_previewModel.Box); + } + /// public override bool OnKeyDown(KeyboardKeys key) { switch (key) { case KeyboardKeys.F: - // Pay respect.. - ViewportCamera.SetArcBallView(_previewModel.Box); + ResetCamera(); return true; case KeyboardKeys.Spacebar: PlayAnimation = !PlayAnimation; @@ -525,7 +432,6 @@ namespace FlaxEditor.Viewport.Previews _showNodesButton = null; _showBoundsButton = null; _showFloorButton = null; - _showCurrentLODButton = null; _showNodesNamesButton = null; base.OnDestroy(); diff --git a/Source/Editor/Viewport/Previews/MaterialPreview.cs b/Source/Editor/Viewport/Previews/MaterialPreview.cs index 632ec199e..5520d6c4c 100644 --- a/Source/Editor/Viewport/Previews/MaterialPreview.cs +++ b/Source/Editor/Viewport/Previews/MaterialPreview.cs @@ -4,6 +4,8 @@ using System; using FlaxEditor.Surface; using FlaxEngine; using FlaxEngine.GUI; +using FlaxEditor.Viewport.Widgets; +using FlaxEditor.GUI.ContextMenu; using Object = FlaxEngine.Object; namespace FlaxEditor.Viewport.Previews @@ -46,6 +48,7 @@ namespace FlaxEditor.Viewport.Previews private int _selectedModelIndex; private Image _guiMaterialControl; private readonly MaterialBase[] _postFxMaterialsCache = new MaterialBase[1]; + private ContextMenu _modelWidgetButtonMenu; /// /// Gets or sets the material asset to preview. It can be or . @@ -95,20 +98,33 @@ namespace FlaxEditor.Viewport.Previews Task.AddCustomActor(_previewModel); // Create context menu for primitive switching - if (useWidgets && ViewWidgetButtonMenu != null) + if (useWidgets) { - ViewWidgetButtonMenu.AddSeparator(); - var modelSelect = ViewWidgetButtonMenu.AddChildMenu("Model").ContextMenu; - - // Fill out all models - for (int i = 0; i < Models.Length; i++) + // Model mode widget + var modelMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); + _modelWidgetButtonMenu = new ContextMenu(); + _modelWidgetButtonMenu.VisibleChanged += control => { - var button = modelSelect.AddButton(Models[i]); - button.Tag = i; - } + if (!control.Visible) + return; + _modelWidgetButtonMenu.ItemsContainer.DisposeChildren(); - // Link the action - modelSelect.ButtonClicked += (button) => SelectedModelIndex = (int)button.Tag; + // Fill out all models + for (int i = 0; i < Models.Length; i++) + { + var index = i; + var button = _modelWidgetButtonMenu.AddButton(Models[index]); + button.ButtonClicked += _ => SelectedModelIndex = index; + button.Checked = SelectedModelIndex == index; + button.Tag = index; + } + }; + new ViewportWidgetButton("Model", SpriteHandle.Invalid, _modelWidgetButtonMenu) + { + TooltipText = "Change material model", + Parent = modelMode, + }; + modelMode.Parent = this; } } diff --git a/Source/Editor/Viewport/Previews/ModelBasePreview.cs b/Source/Editor/Viewport/Previews/ModelBasePreview.cs index ffba23094..e4f1109d4 100644 --- a/Source/Editor/Viewport/Previews/ModelBasePreview.cs +++ b/Source/Editor/Viewport/Previews/ModelBasePreview.cs @@ -1,6 +1,5 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. -using FlaxEditor.GUI.Input; using FlaxEngine; using Object = FlaxEngine.Object; @@ -56,25 +55,14 @@ namespace FlaxEditor.Viewport.Previews // Link actors for rendering Task.AddCustomActor(StaticModel); Task.AddCustomActor(AnimatedModel); + } - if (useWidgets) - { - // Preview LOD - { - var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD"); - previewLOD.CloseMenuOnClick = false; - var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f) - { - Parent = previewLOD - }; - previewLODValue.ValueChanged += () => - { - StaticModel.ForcedLOD = previewLODValue.Value; - AnimatedModel.ForcedLOD = previewLODValue.Value; - }; - ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = StaticModel.ForcedLOD; - } - } + /// + /// Resets the camera to focus on a object. + /// + public void ResetCamera() + { + ViewportCamera.SetArcBallView(StaticModel.Model != null ? StaticModel.Box : AnimatedModel.Box); } private void OnBegin(RenderTask task, GPUContext context) @@ -103,8 +91,7 @@ namespace FlaxEditor.Viewport.Previews switch (key) { case KeyboardKeys.F: - // Pay respect.. - ViewportCamera.SetArcBallView(StaticModel.Model != null ? StaticModel.Box : AnimatedModel.Box); + ResetCamera(); break; } return base.OnKeyDown(key); diff --git a/Source/Editor/Viewport/Previews/ModelPreview.cs b/Source/Editor/Viewport/Previews/ModelPreview.cs index 9193b6d40..a0b6d0ca0 100644 --- a/Source/Editor/Viewport/Previews/ModelPreview.cs +++ b/Source/Editor/Viewport/Previews/ModelPreview.cs @@ -1,10 +1,10 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using FlaxEditor.GUI.ContextMenu; -using FlaxEditor.GUI.Input; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; +using FlaxEditor.Viewport.Widgets; using Object = FlaxEngine.Object; namespace FlaxEditor.Viewport.Previews @@ -16,10 +16,27 @@ namespace FlaxEditor.Viewport.Previews public class ModelPreview : AssetPreview { private ContextMenuButton _showBoundsButton, _showCurrentLODButton, _showNormalsButton, _showTangentsButton, _showBitangentsButton, _showFloorButton; + private ContextMenu _previewLODsWidgetButtonMenu; private StaticModel _previewModel, _floorModel; private bool _showBounds, _showCurrentLOD, _showNormals, _showTangents, _showBitangents, _showFloor; private MeshDataCache _meshDatas; + /// + /// Gets or sets a value that shows LOD statistics + /// + public bool ShowCurrentLOD + { + get => _showCurrentLOD; + set + { + if (_showCurrentLOD == value) + return; + _showCurrentLOD = value; + if (_showCurrentLODButton != null) + _showCurrentLODButton.Checked = value; + } + } + /// /// Gets or sets the model asset to preview. /// @@ -198,17 +215,36 @@ namespace FlaxEditor.Viewport.Previews }); _showCurrentLODButton.IndexInParent = 2; - // Preview LOD + // Preview LODs mode widget + var PreviewLODsMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); + _previewLODsWidgetButtonMenu = new ContextMenu(); + _previewLODsWidgetButtonMenu.VisibleChanged += control => { - var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD"); - previewLOD.CloseMenuOnClick = false; - var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f) + if (!control.Visible) + return; + var model = _previewModel.Model; + if (model && !model.WaitForLoaded()) { - Parent = previewLOD - }; - previewLODValue.ValueChanged += () => _previewModel.ForcedLOD = previewLODValue.Value; - ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = _previewModel.ForcedLOD; - } + _previewLODsWidgetButtonMenu.ItemsContainer.DisposeChildren(); + var lods = model.LODs.Length; + for (int i = -1; i < lods; i++) + { + var index = i; + var button = _previewLODsWidgetButtonMenu.AddButton("LOD " + (index == -1 ? "Auto" : index)); + button.ButtonClicked += _ => _previewModel.ForcedLOD = index; + button.Checked = _previewModel.ForcedLOD == index; + button.Tag = index; + if (lods <= 1) + break; + } + } + }; + new ViewportWidgetButton("Preview LOD", SpriteHandle.Invalid, _previewLODsWidgetButtonMenu) + { + TooltipText = "Preview LOD properties", + Parent = PreviewLODsMode, + }; + PreviewLODsMode.Parent = this; } } @@ -312,7 +348,7 @@ namespace FlaxEditor.Viewport.Previews var distSqr = Vector3.DistanceSquared(ref sphere.Center, ref viewOrigin); var screenRadiusSquared = Mathf.Square(screenMultiple * sphere.Radius) / Mathf.Max(1.0f, distSqr); screenSize = Mathf.Sqrt((float)screenRadiusSquared) * 2.0f; - + // Check if model is being culled if (Mathf.Square(model.MinScreenSize * 0.5f) > screenRadiusSquared) return -1; @@ -346,8 +382,11 @@ namespace FlaxEditor.Viewport.Previews if (_showCurrentLOD) { var asset = Model; - var lodIndex = ComputeLODIndex(asset, out var screenSize); - string text = string.Format("Current LOD: {0}\nScreen Size: {1:F2}", lodIndex, screenSize); + var lodIndex = ComputeLODIndex(asset, out var screenSize); + var auto = _previewModel.ForcedLOD == -1; + string text = auto ? "LOD Automatic" : ""; + text += auto ? string.Format("\nScreen Size: {0:F2}", screenSize) : ""; + text += string.Format("\nCurrent LOD: {0}", lodIndex); if (lodIndex != -1) { var lods = asset.LODs; @@ -369,14 +408,21 @@ namespace FlaxEditor.Viewport.Previews } } + /// + /// Resets the camera to focus on a object. + /// + public void ResetCamera() + { + ViewportCamera.SetArcBallView(_previewModel.Box); + } + /// public override bool OnKeyDown(KeyboardKeys key) { switch (key) { case KeyboardKeys.F: - // Pay respect.. - ViewportCamera.SetArcBallView(_previewModel.Box); + ResetCamera(); break; } return base.OnKeyDown(key); @@ -389,6 +435,7 @@ namespace FlaxEditor.Viewport.Previews Object.Destroy(ref _previewModel); _showBoundsButton = null; _showCurrentLODButton = null; + _previewLODsWidgetButtonMenu = null; _showNormalsButton = null; _showTangentsButton = null; _showBitangentsButton = null; diff --git a/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs b/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs new file mode 100644 index 000000000..08f8d8509 --- /dev/null +++ b/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs @@ -0,0 +1,177 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using FlaxEditor.GUI.ContextMenu; +using FlaxEngine; +using FlaxEngine.GUI; +using FlaxEditor.Viewport.Widgets; + +namespace FlaxEditor.Viewport.Previews +{ + /// + /// Animation asset preview editor viewport. + /// + /// + public class SkinnedModelPreview : AnimatedModelPreview + { + private bool _showCurrentLOD; + private ContextMenuButton _showCurrentLODButton; + private ContextMenu _previewLODsWidgetButtonMenu; + + /// + /// Gets or sets a value that shows LOD statistics + /// + public bool ShowCurrentLOD + { + get => _showCurrentLOD; + set + { + if (_showCurrentLOD == value) + return; + _showCurrentLOD = value; + if (_showCurrentLODButton != null) + _showCurrentLODButton.Checked = value; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// if set to true use widgets. + public SkinnedModelPreview(bool useWidgets) + : base(useWidgets) + { + if (useWidgets) + { + // Show Current LOD + _showCurrentLODButton = ViewWidgetShowMenu.AddButton("Current LOD", button => + { + _showCurrentLOD = !_showCurrentLOD; + _showCurrentLODButton.Icon = _showCurrentLOD ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; + }); + _showCurrentLODButton.IndexInParent = 2; + + // PreviewLODS mode widget + var PreviewLODSMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); + _previewLODsWidgetButtonMenu = new ContextMenu(); + _previewLODsWidgetButtonMenu.VisibleChanged += control => + { + if (!control.Visible) + return; + var skinned = PreviewActor.SkinnedModel; + if (skinned && !skinned.WaitForLoaded()) + { + _previewLODsWidgetButtonMenu.ItemsContainer.DisposeChildren(); + var lods = skinned.LODs.Length; + for (int i = -1; i < lods; i++) + { + var index = i; + var button = _previewLODsWidgetButtonMenu.AddButton("LOD " + (index == -1 ? "Auto" : index)); + button.ButtonClicked += (button) => PreviewActor.ForcedLOD = index; + button.Checked = PreviewActor.ForcedLOD == index; + button.Tag = index; + if (lods <= 1) + break; + } + } + }; + new ViewportWidgetButton("Preview LOD", SpriteHandle.Invalid, _previewLODsWidgetButtonMenu) + { + TooltipText = "Preview LOD properties", + Parent = PreviewLODSMode, + }; + PreviewLODSMode.Parent = this; + } + } + + private int ComputeLODIndex(SkinnedModel model, out float screenSize) + { + screenSize = 1.0f; + if (PreviewActor.ForcedLOD != -1) + return PreviewActor.ForcedLOD; + + // Based on RenderTools::ComputeModelLOD + CreateProjectionMatrix(out var projectionMatrix); + float screenMultiple = 0.5f * Mathf.Max(projectionMatrix.M11, projectionMatrix.M22); + var sphere = PreviewActor.Sphere; + var viewOrigin = ViewPosition; + var distSqr = Vector3.DistanceSquared(ref sphere.Center, ref viewOrigin); + var screenRadiusSquared = Mathf.Square(screenMultiple * sphere.Radius) / Mathf.Max(1.0f, distSqr); + screenSize = Mathf.Sqrt((float)screenRadiusSquared) * 2.0f; + + // Check if model is being culled + if (Mathf.Square(model.MinScreenSize * 0.5f) > screenRadiusSquared) + return -1; + + // Skip if no need to calculate LOD + if (model.LoadedLODs == 0) + return -1; + var lods = model.LODs; + if (lods.Length == 0) + return -1; + if (lods.Length == 1) + return 0; + + // Iterate backwards and return the first matching LOD + for (int lodIndex = lods.Length - 1; lodIndex >= 0; lodIndex--) + { + if (Mathf.Square(lods[lodIndex].ScreenSize * 0.5f) >= screenRadiusSquared) + { + return lodIndex + PreviewActor.LODBias; + } + } + + return 0; + } + + /// + public override void Draw() + { + base.Draw(); + + var skinnedModel = PreviewActor.SkinnedModel; + if (skinnedModel == null || !skinnedModel.IsLoaded) + return; + var lods = skinnedModel.LODs; + if (lods.Length == 0) + { + // Force show skeleton for models without geometry + ShowNodes = true; + return; + } + if (_showCurrentLOD) + { + var lodIndex = ComputeLODIndex(skinnedModel, out var screenSize); + var auto = PreviewActor.ForcedLOD == -1; + string text = auto ? "LOD Automatic" : ""; + text += auto ? string.Format("\nScreen Size: {0:F2}", screenSize) : ""; + text += string.Format("\nCurrent LOD: {0}", lodIndex); + if (lodIndex != -1) + { + lodIndex = Mathf.Clamp(lodIndex + PreviewActor.LODBias, 0, lods.Length - 1); + var lod = lods[lodIndex]; + int triangleCount = 0, vertexCount = 0; + for (int meshIndex = 0; meshIndex < lod.Meshes.Length; meshIndex++) + { + var mesh = lod.Meshes[meshIndex]; + triangleCount += mesh.TriangleCount; + vertexCount += mesh.VertexCount; + } + text += string.Format("\nTriangles: {0:N0}\nVertices: {1:N0}", triangleCount, vertexCount); + } + var font = Style.Current.FontMedium; + var pos = new Float2(10, 50); + Render2D.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black); + Render2D.DrawText(font, text, new Rectangle(pos, Size), Color.White); + } + } + + /// + public override void OnDestroy() + { + _showCurrentLODButton = null; + _previewLODsWidgetButtonMenu = null; + + base.OnDestroy(); + } + } +} diff --git a/Source/Editor/Windows/Assets/CollisionDataWindow.cs b/Source/Editor/Windows/Assets/CollisionDataWindow.cs index 9ce4c8765..cc1daa45b 100644 --- a/Source/Editor/Windows/Assets/CollisionDataWindow.cs +++ b/Source/Editor/Windows/Assets/CollisionDataWindow.cs @@ -182,6 +182,7 @@ namespace FlaxEditor.Windows.Assets { // Toolstrip _toolstrip.AddSeparator(); + _toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole collision"); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/physics/colliders/collision-data.html")).LinkTooltip("See documentation to learn more"); // Split Panel diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index e44882b73..f70f6a3b5 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -779,6 +779,7 @@ namespace FlaxEditor.Windows.Assets private MeshDataCache _meshData; private ModelImportSettings _importSettings = new ModelImportSettings(); private float _backfacesThreshold = 0.6f; + private ToolStripButton _showCurrentLODButton; /// public ModelWindow(Editor editor, AssetItem item) @@ -786,6 +787,9 @@ namespace FlaxEditor.Windows.Assets { // Toolstrip _toolstrip.AddSeparator(); + _showCurrentLODButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Info64, () => _preview.ShowCurrentLOD = !_preview.ShowCurrentLOD).LinkTooltip("Show LOD statistics"); + _toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole model"); + _toolstrip.AddSeparator(); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/models/index.html")).LinkTooltip("See documentation to learn more"); // Model preview @@ -869,6 +873,8 @@ namespace FlaxEditor.Windows.Assets } } + _showCurrentLODButton.Checked = _preview.ShowCurrentLOD; + base.Update(deltaTime); } @@ -946,6 +952,7 @@ namespace FlaxEditor.Windows.Assets base.OnDestroy(); Object.Destroy(ref _highlightActor); + _showCurrentLODButton = null; } } } diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index d18593549..a7ee6e767 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -31,7 +31,7 @@ namespace FlaxEditor.Windows.Assets /// public sealed class SkinnedModelWindow : ModelBaseWindow { - private sealed class Preview : AnimatedModelPreview + private sealed class Preview : SkinnedModelPreview { private readonly SkinnedModelWindow _window; @@ -1105,6 +1105,7 @@ namespace FlaxEditor.Windows.Assets private Preview _preview; private AnimatedModel _highlightActor; private ToolStripButton _showNodesButton; + private ToolStripButton _showCurrentLODButton; private MeshData[][] _meshDatas; private bool _meshDatasInProgress; @@ -1116,7 +1117,9 @@ namespace FlaxEditor.Windows.Assets { // Toolstrip _toolstrip.AddSeparator(); + _showCurrentLODButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Info64, () => _preview.ShowCurrentLOD = !_preview.ShowCurrentLOD).LinkTooltip("Show LOD statistics"); _showNodesButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Bone64, () => _preview.ShowNodes = !_preview.ShowNodes).LinkTooltip("Show animated model nodes debug view"); + _toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole model"); _toolstrip.AddSeparator(); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/skinned-model/index.html")).LinkTooltip("See documentation to learn more"); @@ -1265,6 +1268,7 @@ namespace FlaxEditor.Windows.Assets } } + _showCurrentLODButton.Checked = _preview.ShowCurrentLOD; _showNodesButton.Checked = _preview.ShowNodes; base.Update(deltaTime); @@ -1349,6 +1353,7 @@ namespace FlaxEditor.Windows.Assets Object.Destroy(ref _highlightActor); _preview = null; _showNodesButton = null; + _showCurrentLODButton = null; } } } diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index 3e36d3618..7cf7a7f78 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -304,6 +304,6 @@ void SplashScreen::OnFontLoaded(Asset* asset) // Create fonts const float s = _dpiScale; - _titleFont = font->CreateFont((uint32)(35 * s)); - _subtitleFont = font->CreateFont((uint32)(9 * s)); + _titleFont = font->CreateFont(35 * s); + _subtitleFont = font->CreateFont(9 * s); } diff --git a/Source/Engine/ContentExporters/AssetsExportingManager.h b/Source/Engine/ContentExporters/AssetsExportingManager.h index f9f50075e..7e3b11b6d 100644 --- a/Source/Engine/ContentExporters/AssetsExportingManager.h +++ b/Source/Engine/ContentExporters/AssetsExportingManager.h @@ -10,7 +10,7 @@ /// /// Assets Importing service allows to import or create new assets /// -class AssetsExportingManager +class FLAXENGINE_API AssetsExportingManager { public: /// diff --git a/Source/Engine/ContentExporters/Types.h b/Source/Engine/ContentExporters/Types.h index cfc341f37..a2095934f 100644 --- a/Source/Engine/ContentExporters/Types.h +++ b/Source/Engine/ContentExporters/Types.h @@ -26,7 +26,7 @@ typedef Function ExportAssetFunction; /// /// Exporting asset context structure /// -class ExportAssetContext : public NonCopyable +class FLAXENGINE_API ExportAssetContext : public NonCopyable { public: /// diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.h b/Source/Engine/ContentImporters/AssetsImportingManager.h index 2f8369a41..2930f8979 100644 --- a/Source/Engine/ContentImporters/AssetsImportingManager.h +++ b/Source/Engine/ContentImporters/AssetsImportingManager.h @@ -9,7 +9,7 @@ /// /// Assets Importing service allows to import or create new assets /// -class AssetsImportingManager +class FLAXENGINE_API AssetsImportingManager { public: /// diff --git a/Source/Engine/ContentImporters/Types.h b/Source/Engine/ContentImporters/Types.h index 05d1fc116..4ebf55583 100644 --- a/Source/Engine/ContentImporters/Types.h +++ b/Source/Engine/ContentImporters/Types.h @@ -28,7 +28,7 @@ typedef Function CreateAssetFunction; /// /// Importing/creating asset context structure /// -class CreateAssetContext : public NonCopyable +class FLAXENGINE_API CreateAssetContext : public NonCopyable { private: CreateAssetResult _applyChangesResult; diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h index 698788869..ad6f8ffc6 100644 --- a/Source/Engine/Core/Collections/HashSet.h +++ b/Source/Engine/Core/Collections/HashSet.h @@ -357,8 +357,8 @@ public: { for (Iterator i = Begin(); i.IsNotEnd(); ++i) { - if (i->Value) - ::Delete(i->Value); + if (i->Item) + ::Delete(i->Item); } Clear(); } diff --git a/Source/Engine/Core/Encoding.h b/Source/Engine/Core/Encoding.h index 6ac716ba8..3e74144e2 100644 --- a/Source/Engine/Core/Encoding.h +++ b/Source/Engine/Core/Encoding.h @@ -4,4 +4,4 @@ #include "Enums.h" -DECLARE_ENUM_3(Encoding, ANSI, Unicode, UnicodeBigEndian); +DECLARE_ENUM_4(Encoding, ANSI, Unicode, UnicodeBigEndian, UTF8); diff --git a/Source/Engine/Core/Math/BoundingFrustum.h b/Source/Engine/Core/Math/BoundingFrustum.h index 61c5d122f..287d33c2f 100644 --- a/Source/Engine/Core/Math/BoundingFrustum.h +++ b/Source/Engine/Core/Math/BoundingFrustum.h @@ -12,6 +12,7 @@ /// API_STRUCT(InBuild) struct FLAXENGINE_API BoundingFrustum { + friend CollisionsHelper; private: Matrix _matrix; diff --git a/Source/Engine/Core/Math/CollisionsHelper.cpp b/Source/Engine/Core/Math/CollisionsHelper.cpp index 60ba61f28..8ad5b7673 100644 --- a/Source/Engine/Core/Math/CollisionsHelper.cpp +++ b/Source/Engine/Core/Math/CollisionsHelper.cpp @@ -939,7 +939,6 @@ bool CollisionsHelper::RayIntersectsSphere(const Ray& ray, const BoundingSphere& normal = Vector3::Up; return false; } - const Vector3 point = ray.Position + ray.Direction * distance; normal = Vector3::Normalize(point - sphere.Center); return true; @@ -953,22 +952,17 @@ bool CollisionsHelper::RayIntersectsSphere(const Ray& ray, const BoundingSphere& point = Vector3::Zero; return false; } - point = ray.Position + ray.Direction * distance; return true; } PlaneIntersectionType CollisionsHelper::PlaneIntersectsPoint(const Plane& plane, const Vector3& point) { - Real distance = Vector3::Dot(plane.Normal, point); - distance += plane.D; - + const Real distance = Vector3::Dot(plane.Normal, point) + plane.D; if (distance > Plane::DistanceEpsilon) return PlaneIntersectionType::Front; - if (distance < Plane::DistanceEpsilon) return PlaneIntersectionType::Back; - return PlaneIntersectionType::Intersecting; } @@ -1169,7 +1163,6 @@ ContainmentType CollisionsHelper::SphereContainsPoint(const BoundingSphere& sphe { if (Vector3::DistanceSquared(point, sphere.Center) <= sphere.Radius * sphere.Radius) return ContainmentType::Contains; - return ContainmentType::Disjoint; } @@ -1254,13 +1247,10 @@ ContainmentType CollisionsHelper::SphereContainsBox(const BoundingSphere& sphere ContainmentType CollisionsHelper::SphereContainsSphere(const BoundingSphere& sphere1, const BoundingSphere& sphere2) { const Real distance = Vector3::Distance(sphere1.Center, sphere2.Center); - if (sphere1.Radius + sphere2.Radius < distance) return ContainmentType::Disjoint; - if (sphere1.Radius - sphere2.Radius < distance) return ContainmentType::Intersects; - return ContainmentType::Contains; } @@ -1274,7 +1264,8 @@ ContainmentType CollisionsHelper::FrustumContainsBox(const BoundingFrustum& frus auto result = ContainmentType::Contains; for (int32 i = 0; i < 6; i++) { - Plane plane = frustum.GetPlane(i); + Plane plane = frustum._planes[i]; + Vector3 p = box.Minimum; if (plane.Normal.X >= 0) p.X = box.Maximum.X; @@ -1282,7 +1273,7 @@ ContainmentType CollisionsHelper::FrustumContainsBox(const BoundingFrustum& frus p.Y = box.Maximum.Y; if (plane.Normal.Z >= 0) p.Z = box.Maximum.Z; - if (PlaneIntersectsPoint(plane, p) == PlaneIntersectionType::Back) + if (Vector3::Dot(plane.Normal, p) + plane.D < Plane::DistanceEpsilon) return ContainmentType::Disjoint; p = box.Maximum; @@ -1292,7 +1283,7 @@ ContainmentType CollisionsHelper::FrustumContainsBox(const BoundingFrustum& frus p.Y = box.Minimum.Y; if (plane.Normal.Z >= 0) p.Z = box.Minimum.Z; - if (PlaneIntersectsPoint(plane, p) == PlaneIntersectionType::Back) + if (Vector3::Dot(plane.Normal, p) + plane.D < Plane::DistanceEpsilon) result = ContainmentType::Intersects; } return result; diff --git a/Source/Engine/Core/Memory/Memory.h b/Source/Engine/Core/Memory/Memory.h index 770fd2c22..96acc0746 100644 --- a/Source/Engine/Core/Memory/Memory.h +++ b/Source/Engine/Core/Memory/Memory.h @@ -36,6 +36,32 @@ namespace AllocatorExt return result; } + /// + /// Reallocates block of the memory. + /// + /// + /// A pointer to the memory block to reallocate. + /// The size of the new allocation (in bytes). + /// The memory alignment (in bytes). Must be an integer power of 2. + /// The pointer to the allocated chunk of the memory. The pointer is a multiple of alignment. + inline void* ReallocAligned(void* ptr, uint64 newSize, uint64 alignment) + { + if (newSize == 0) + { + Allocator::Free(ptr); + return nullptr; + } + if (!ptr) + return Allocator::Allocate(newSize, alignment); + void* result = Allocator::Allocate(newSize, alignment); + if (result) + { + Platform::MemoryCopy(result, ptr, newSize); + Allocator::Free(ptr); + } + return result; + } + /// /// Reallocates block of the memory. /// diff --git a/Source/Engine/Core/Types/Span.h b/Source/Engine/Core/Types/Span.h index 678550e85..e7bea5fbd 100644 --- a/Source/Engine/Core/Types/Span.h +++ b/Source/Engine/Core/Types/Span.h @@ -114,3 +114,14 @@ inline Span ToSpan(const T* ptr, int32 length) { return Span(ptr, length); } + +template +inline bool SpanContains(const Span span, const T& value) +{ + for (int32 i = 0; i < span.Length(); i++) + { + if (span.Get()[i] == value) + return true; + } + return false; +} diff --git a/Source/Engine/Core/Types/String.cpp b/Source/Engine/Core/Types/String.cpp index cee60c07c..3de255039 100644 --- a/Source/Engine/Core/Types/String.cpp +++ b/Source/Engine/Core/Types/String.cpp @@ -73,7 +73,7 @@ void String::Set(const char* chars, int32 length) _length = length; } if (chars) - StringUtils::ConvertANSI2UTF16(chars, _data, length); + StringUtils::ConvertANSI2UTF16(chars, _data, length, _length); } void String::SetUTF8(const char* chars, int32 length) @@ -112,7 +112,7 @@ void String::Append(const char* chars, int32 count) _data = (Char*)Platform::Allocate((_length + 1) * sizeof(Char), 16); Platform::MemoryCopy(_data, oldData, oldLength * sizeof(Char)); - StringUtils::ConvertANSI2UTF16(chars, _data + oldLength, count * sizeof(Char)); + StringUtils::ConvertANSI2UTF16(chars, _data + oldLength, count, _length); _data[_length] = 0; Platform::Free(oldData); diff --git a/Source/Engine/Core/Types/StringBuilder.h b/Source/Engine/Core/Types/StringBuilder.h index 39c1b3a84..051554a23 100644 --- a/Source/Engine/Core/Types/StringBuilder.h +++ b/Source/Engine/Core/Types/StringBuilder.h @@ -125,7 +125,8 @@ public: const int32 length = str && *str ? StringUtils::Length(str) : 0; const int32 prevCnt = _data.Count(); _data.AddDefault(length); - StringUtils::ConvertANSI2UTF16(str, _data.Get() + prevCnt, length); + int32 tmp; + StringUtils::ConvertANSI2UTF16(str, _data.Get() + prevCnt, length, tmp); return *this; } diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 33ffde349..8232d415f 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -660,7 +660,8 @@ Variant::Variant(const StringAnsiView& v) const int32 length = v.Length() * sizeof(Char) + 2; AsBlob.Data = Allocator::Allocate(length); AsBlob.Length = length; - StringUtils::ConvertANSI2UTF16(v.Get(), (Char*)AsBlob.Data, v.Length()); + int32 tmp; + StringUtils::ConvertANSI2UTF16(v.Get(), (Char*)AsBlob.Data, v.Length(), tmp); ((Char*)AsBlob.Data)[v.Length()] = 0; } else @@ -2578,7 +2579,8 @@ void Variant::SetString(const StringAnsiView& str) AsBlob.Data = Allocator::Allocate(length); AsBlob.Length = length; } - StringUtils::ConvertANSI2UTF16(str.Get(), (Char*)AsBlob.Data, str.Length()); + int32 tmp; + StringUtils::ConvertANSI2UTF16(str.Get(), (Char*)AsBlob.Data, str.Length(), tmp); ((Char*)AsBlob.Data)[str.Length()] = 0; } diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 7330f4339..e4d53fcb5 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -466,7 +466,7 @@ inline void DrawText3D(const DebugText3D& t, const RenderContext& renderContext, Matrix::Multiply(fw, vp, m); Render2D::Begin(context, target, depthBuffer, viewport, m); const StringView text(t.Text.Get(), t.Text.Count() - 1); - Render2D::DrawText(DebugDrawFont->CreateFont(t.Size), text, t.Color, Vector2::Zero); + Render2D::DrawText(DebugDrawFont->CreateFont((float)t.Size), text, t.Color, Vector2::Zero); Render2D::End(); } @@ -777,7 +777,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe context->BindSR(0, renderContext.Buffers->DepthBuffer); const bool enableDepthWrite = data.EnableDepthTest; - context->SetRenderTarget(depthBuffer ? depthBuffer : *renderContext.Buffers->DepthBuffer, target); + context->SetRenderTarget(depthBuffer ? depthBuffer : (data.EnableDepthTest ? nullptr : renderContext.Buffers->DepthBuffer->View()), target); // Lines if (depthTestLines.VertexCount) @@ -859,12 +859,12 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe for (auto& t : Context->DebugDrawDefault.DefaultText2D) { const StringView text(t.Text.Get(), t.Text.Count() - 1); - Render2D::DrawText(DebugDrawFont->CreateFont(t.Size), text, t.Color, t.Position); + Render2D::DrawText(DebugDrawFont->CreateFont((float)t.Size), text, t.Color, t.Position); } for (auto& t : Context->DebugDrawDefault.OneFrameText2D) { const StringView text(t.Text.Get(), t.Text.Count() - 1); - Render2D::DrawText(DebugDrawFont->CreateFont(t.Size), text, t.Color, t.Position); + Render2D::DrawText(DebugDrawFont->CreateFont((float)t.Size), text, t.Color, t.Position); } Render2D::End(); } diff --git a/Source/Engine/Engine/NativeInterop.Managed.cs b/Source/Engine/Engine/NativeInterop.Managed.cs index 7eda1cdec..797b02d5b 100644 --- a/Source/Engine/Engine/NativeInterop.Managed.cs +++ b/Source/Engine/Engine/NativeInterop.Managed.cs @@ -50,13 +50,13 @@ namespace FlaxEngine.Interop /// The resources must be released by calling FreePooled() instead of Free()-method. public static ManagedArray WrapPooledArray(Array arr, Type arrayType) { - ManagedArray managedArray = ManagedArrayPool.Get(arr.Length * Marshal.SizeOf(arr.GetType().GetElementType())); + ManagedArray managedArray = ManagedArrayPool.Get(arr.Length * NativeInterop.GetTypeSize(arr.GetType().GetElementType())); managedArray.WrapArray(arr, arrayType); return managedArray; } internal static ManagedArray AllocateNewArray(int length, Type arrayType, Type elementType) - => new ManagedArray((IntPtr)NativeInterop.NativeAlloc(length, Marshal.SizeOf(elementType)), length, arrayType, elementType); + => new ManagedArray((IntPtr)NativeInterop.NativeAlloc(length, NativeInterop.GetTypeSize(elementType)), length, arrayType, elementType); internal static ManagedArray AllocateNewArray(IntPtr ptr, int length, Type arrayType, Type elementType) => new ManagedArray(ptr, length, arrayType, elementType); @@ -86,7 +86,7 @@ namespace FlaxEngine.Interop _length = arr.Length; _arrayType = arrayType; _elementType = arr.GetType().GetElementType(); - _elementSize = Marshal.SizeOf(_elementType); + _elementSize = NativeInterop.GetTypeSize(_elementType); } internal void Allocate(int length) where T : unmanaged @@ -117,7 +117,7 @@ namespace FlaxEngine.Interop _length = length; _arrayType = arrayType; _elementType = elementType; - _elementSize = Marshal.SizeOf(elementType); + _elementSize = NativeInterop.GetTypeSize(_elementType); } ~ManagedArray() diff --git a/Source/Engine/Engine/NativeInterop.Marshallers.cs b/Source/Engine/Engine/NativeInterop.Marshallers.cs index a3531d11d..0a23bfcbd 100644 --- a/Source/Engine/Engine/NativeInterop.Marshallers.cs +++ b/Source/Engine/Engine/NativeInterop.Marshallers.cs @@ -350,14 +350,14 @@ namespace FlaxEngine.Interop #endif public static class NativeToManaged { - public static T[]? AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements) + public static T[] AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements) { if (unmanaged is null) return null; return new T[numElements]; } - public static Span GetManagedValuesDestination(T[]? managed) => managed; + public static Span GetManagedValuesDestination(T[] managed) => managed; public static ReadOnlySpan GetUnmanagedValuesSource(TUnmanagedElement* unmanaged, int numElements) { @@ -390,7 +390,7 @@ namespace FlaxEngine.Interop #endif public static class ManagedToNative { - public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[]? managed, out int numElements) + public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[] managed, out int numElements) { if (managed is null) { @@ -402,7 +402,7 @@ namespace FlaxEngine.Interop return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak); } - public static ReadOnlySpan GetManagedValuesSource(T[]? managed) => managed; + public static ReadOnlySpan GetManagedValuesSource(T[] managed) => managed; public static Span GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements) { @@ -431,7 +431,7 @@ namespace FlaxEngine.Interop ManagedArray unmanagedArray; ManagedHandle handle; - public void FromManaged(T[]? managed) + public void FromManaged(T[] managed) { if (managed == null) return; @@ -476,7 +476,7 @@ namespace FlaxEngine.Interop } } - public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[]? managed, out int numElements) + public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[] managed, out int numElements) { if (managed is null) { @@ -489,7 +489,7 @@ namespace FlaxEngine.Interop return (TUnmanagedElement*)handle; } - public static ReadOnlySpan GetManagedValuesSource(T[]? managed) => managed; + public static ReadOnlySpan GetManagedValuesSource(T[] managed) => managed; public static Span GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements) { @@ -499,9 +499,9 @@ namespace FlaxEngine.Interop return unmanagedArray.ToSpan(); } - public static T[]? AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements) => unmanaged is null ? null : new T[numElements]; + public static T[] AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements) => unmanaged is null ? null : new T[numElements]; - public static Span GetManagedValuesDestination(T[]? managed) => managed; + public static Span GetManagedValuesDestination(T[] managed) => managed; public static ReadOnlySpan GetUnmanagedValuesSource(TUnmanagedElement* unmanaged, int numElements) { diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index c079a14e1..d4a8d44f8 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -885,6 +885,7 @@ namespace FlaxEngine.Interop handle.Free(); fieldHandleCacheCollectible.Clear(); #endif + _typeSizeCache.Clear(); foreach (var pair in classAttributesCacheCollectible) pair.Value.Free(); diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index b33e4bcb6..e82cb3858 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -46,6 +46,7 @@ namespace FlaxEngine.Interop #endif private static Dictionary classAttributesCacheCollectible = new(); private static Dictionary assemblyHandles = new(); + private static Dictionary _typeSizeCache = new(); private static Dictionary loadedNativeLibraries = new(); internal static Dictionary nativeLibraryPaths = new(); @@ -58,19 +59,8 @@ namespace FlaxEngine.Interop if (!loadedNativeLibraries.TryGetValue(libraryName, out IntPtr nativeLibrary)) { if (!nativeLibraryPaths.TryGetValue(libraryName, out var nativeLibraryPath)) - { nativeLibraryPath = libraryName; - // Check if any of the loaded assemblies has matching native module filename - foreach (var e in nativeLibraryPaths) - { - if (string.Equals(Path.GetFileNameWithoutExtension(e.Value), libraryName, StringComparison.Ordinal)) - { - nativeLibraryPath = e.Value; - break; - } - } - } nativeLibrary = NativeLibrary.Load(nativeLibraryPath, assembly, dllImportSearchPath); loadedNativeLibraries.Add(libraryName, nativeLibrary); assemblyOwnedNativeLibraries.Add(assembly, libraryName); @@ -107,14 +97,17 @@ namespace FlaxEngine.Interop } #if FLAX_EDITOR - private static Assembly? OnScriptingAssemblyLoadContextResolving(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName) + private static Assembly OnScriptingAssemblyLoadContextResolving(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName) { // FIXME: There should be a better way to resolve the path to EditorTargetPath where the dependencies are stored - string editorTargetPath = Path.GetDirectoryName(nativeLibraryPaths.Keys.First(x => x != "FlaxEngine")); + foreach (string nativeLibraryPath in nativeLibraryPaths.Values) + { + string editorTargetPath = Path.GetDirectoryName(nativeLibraryPath); - var assemblyPath = Path.Combine(editorTargetPath, assemblyName.Name + ".dll"); - if (File.Exists(assemblyPath)) - return assemblyLoadContext.LoadFromAssemblyPath(assemblyPath); + var assemblyPath = Path.Combine(editorTargetPath, assemblyName.Name + ".dll"); + if (File.Exists(assemblyPath)) + return assemblyLoadContext.LoadFromAssemblyPath(assemblyPath); + } return null; } #endif @@ -592,7 +585,7 @@ namespace FlaxEngine.Interop else if (fieldType.IsClass || fieldType.IsPointer) fieldAlignment = IntPtr.Size; else - fieldAlignment = Marshal.SizeOf(fieldType); + fieldAlignment = GetTypeSize(fieldType); } internal static void ToManagedField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) @@ -1096,6 +1089,26 @@ namespace FlaxEngine.Interop return handle; } + internal static int GetTypeSize(Type type) + { + if (!_typeSizeCache.TryGetValue(type, out var size)) + { + try + { + size = Marshal.SizeOf(type); + } + catch + { + // Workaround the issue where structure defined within generic type instance (eg. MyType.MyStruct) fails to get size + // https://github.com/dotnet/runtime/issues/46426 + var obj = Activator.CreateInstance(type); + size = Marshal.SizeOf(obj); + } + _typeSizeCache.Add(type, size); + } + return size; + } + private static class DelegateHelpers { #if USE_AOT diff --git a/Source/Engine/Graphics/Shaders/GPUShader.cpp b/Source/Engine/Graphics/Shaders/GPUShader.cpp index 2666bf36b..3639e01b1 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.cpp +++ b/Source/Engine/Graphics/Shaders/GPUShader.cpp @@ -145,7 +145,7 @@ bool GPUShader::Create(MemoryReadStream& stream) // Create CB #if GPU_ENABLE_RESOURCE_NAMING - String name = ToString() + TEXT(".CB") + i; + String name = String::Format(TEXT("{}.CB{}"), ToString(), i); #else String name; #endif diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index e5c4c5a09..4c1d32b77 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -146,7 +146,7 @@ void AnimatedModel::SetCurrentPose(const Array& nodesTransformation, boo Matrix invWorld; Matrix::Invert(world, invWorld); for (auto& m : GraphInstance.NodesPose) - m = invWorld * m; + m = m * invWorld; } OnAnimationUpdated(); } @@ -774,7 +774,13 @@ void AnimatedModel::OnAnimationUpdated_Sync() // Update synchronous stuff UpdateSockets(); ApplyRootMotion(GraphInstance.RootMotion); - AnimationUpdated(); + if (!_isDuringUpdateEvent) + { + // Prevent stack-overflow when gameplay modifies the pose within the event + _isDuringUpdateEvent = true; + AnimationUpdated(); + _isDuringUpdateEvent = false; + } } void AnimatedModel::OnAnimationUpdated() diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index d9623f77f..18b16c150 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -67,6 +67,7 @@ private: AnimationUpdateMode _actualMode; uint32 _counter; Real _lastMinDstSqr; + bool _isDuringUpdateEvent = false; uint64 _lastUpdateFrame; mutable MeshDeformation* _deformation = nullptr; ScriptingObjectReference _masterPose; diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index ae617b980..ab8a9bbfe 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -740,16 +740,20 @@ Actor* FindActorRecursive(Actor* node, const Tag& tag) return result; } -void FindActorsRecursive(Actor* node, const Tag& tag, Array& result) +void FindActorsRecursive(Actor* node, const Tag& tag, const bool activeOnly, Array& result) { + if (activeOnly && !node->GetIsActive()) + return; if (node->HasTag(tag)) result.Add(node); for (Actor* child : node->Children) - FindActorsRecursive(child, tag, result); + FindActorsRecursive(child, tag, activeOnly, result); } -void FindActorsRecursiveByParentTags(Actor* node, const Array& tags, Array& result) +void FindActorsRecursiveByParentTags(Actor* node, const Array& tags, const bool activeOnly, Array& result) { + if (activeOnly && !node->GetIsActive()) + return; for (Tag tag : tags) { if (node->HasTag(tag)) @@ -759,7 +763,7 @@ void FindActorsRecursiveByParentTags(Actor* node, const Array& tags, Array< } } for (Actor* child : node->Children) - FindActorsRecursiveByParentTags(child, tags, result); + FindActorsRecursiveByParentTags(child, tags, activeOnly, result); } Actor* Level::FindActor(const Tag& tag, Actor* root) @@ -785,24 +789,24 @@ void FindActorRecursive(Actor* node, const Tag& tag, Array& result) FindActorRecursive(child, tag, result); } -Array Level::FindActors(const Tag& tag, Actor* root) +Array Level::FindActors(const Tag& tag, const bool activeOnly, Actor* root) { PROFILE_CPU(); Array result; if (root) { - FindActorsRecursive(root, tag, result); + FindActorsRecursive(root, tag, activeOnly, result); } else { ScopeLock lock(ScenesLock); for (Scene* scene : Scenes) - FindActorsRecursive(scene, tag, result); + FindActorsRecursive(scene, tag, activeOnly, result); } return result; } -Array Level::FindActorsByParentTag(const Tag& parentTag, Actor* root) +Array Level::FindActorsByParentTag(const Tag& parentTag, const bool activeOnly, Actor* root) { PROFILE_CPU(); Array result; @@ -814,19 +818,19 @@ Array Level::FindActorsByParentTag(const Tag& parentTag, Actor* root) } if (subTags.Count() == 1) { - result = FindActors(subTags[0], root); + result = FindActors(subTags[0], activeOnly, root); return result; } if (root) { - FindActorsRecursiveByParentTags(root, subTags, result); + FindActorsRecursiveByParentTags(root, subTags, activeOnly, result); } else { ScopeLock lock(ScenesLock); for (Scene* scene : Scenes) - FindActorsRecursiveByParentTags(scene, subTags, result); + FindActorsRecursiveByParentTags(scene, subTags, activeOnly, result); } return result; diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index e27b81bb9..9a041bda9 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -494,17 +494,19 @@ public: /// Tries to find the actors with the given tag (returns all found). /// /// The tag of the actor to search for. + /// Find only active actors. /// The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes. /// Found actors or empty if none. - API_FUNCTION() static Array FindActors(const Tag& tag, Actor* root = nullptr); + API_FUNCTION() static Array FindActors(const Tag& tag, const bool activeOnly = false, Actor* root = nullptr); /// /// Search actors using a parent parentTag. /// /// The tag to search actors with subtags belonging to this tag + /// Find only active actors. /// The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes. /// Returns all actors that have subtags belonging to the given parent parentTag - API_FUNCTION() static Array FindActorsByParentTag(const Tag& parentTag, Actor* root = nullptr); + API_FUNCTION() static Array FindActorsByParentTag(const Tag& parentTag, const bool activeOnly = false, Actor* root = nullptr); private: // Actor API diff --git a/Source/Engine/Level/Spline.cs b/Source/Engine/Level/Spline.cs index 28a6aff8c..a2c9aecde 100644 --- a/Source/Engine/Level/Spline.cs +++ b/Source/Engine/Level/Spline.cs @@ -23,8 +23,8 @@ namespace FlaxEngine if (_keyframes == null || _keyframes.Length != count) _keyframes = new BezierCurve.Keyframe[count]; #if !BUILD_RELEASE - if (Marshal.SizeOf(typeof(BezierCurve.Keyframe)) != Transform.SizeInBytes * 3 + sizeof(float)) - throw new Exception("Invalid size of BezierCurve keyframe " + Marshal.SizeOf(typeof(BezierCurve.Keyframe)) + " bytes."); + if (System.Runtime.CompilerServices.Unsafe.SizeOf.Keyframe>() != Transform.SizeInBytes * 3 + sizeof(float)) + throw new Exception("Invalid size of BezierCurve keyframe " + System.Runtime.CompilerServices.Unsafe.SizeOf.Keyframe>() + " bytes."); #endif Internal_GetKeyframes(__unmanagedPtr, _keyframes); return _keyframes; diff --git a/Source/Engine/Networking/NetworkReplicationHierarchy.cpp b/Source/Engine/Networking/NetworkReplicationHierarchy.cpp index d86acfc7f..6ec5e52c2 100644 --- a/Source/Engine/Networking/NetworkReplicationHierarchy.cpp +++ b/Source/Engine/Networking/NetworkReplicationHierarchy.cpp @@ -23,7 +23,7 @@ void NetworkReplicationHierarchyUpdateResult::Init() { _clientsHaveLocation = false; _clients.Resize(NetworkManager::Clients.Count()); - _clientsMask = NetworkClientsMask(); + _clientsMask = NetworkManager::Mode == NetworkManagerMode::Client ? NetworkClientsMask::All : NetworkClientsMask(); for (int32 i = 0; i < _clients.Count(); i++) _clientsMask.SetBit(i); _entries.Clear(); @@ -49,7 +49,7 @@ bool NetworkReplicationHierarchyUpdateResult::GetClientLocation(int32 clientInde void NetworkReplicationNode::AddObject(NetworkReplicationHierarchyObject obj) { - if (obj.ReplicationFPS > 0.0f) + if (obj.ReplicationFPS > ZeroTolerance) // > 0 { // Randomize initial replication update to spread rep rates more evenly for large scenes that register all objects within the same frame obj.ReplicationUpdatesLeft = NetworkReplicationNodeObjectCounter++ % Math::Clamp(Math::RoundToInt(NetworkManager::NetworkFPS / obj.ReplicationFPS), 1, 60); @@ -63,6 +63,17 @@ bool NetworkReplicationNode::RemoveObject(ScriptingObject* obj) return !Objects.Remove(obj); } +bool NetworkReplicationNode::GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result) +{ + const int32 index = Objects.Find(obj); + if (index != -1) + { + result = Objects[index]; + return true; + } + return false; +} + bool NetworkReplicationNode::DirtyObject(ScriptingObject* obj) { const int32 index = Objects.Find(obj); @@ -80,7 +91,11 @@ void NetworkReplicationNode::Update(NetworkReplicationHierarchyUpdateResult* res const float networkFPS = NetworkManager::NetworkFPS / result->ReplicationScale; for (NetworkReplicationHierarchyObject& obj : Objects) { - if (obj.ReplicationFPS <= 0.0f) + if (obj.ReplicationFPS < -ZeroTolerance) // < 0 + { + continue; + } + else if (obj.ReplicationFPS < ZeroTolerance) // == 0 { // Always relevant result->AddObject(obj.Object); @@ -152,6 +167,7 @@ void NetworkReplicationGridNode::AddObject(NetworkReplicationHierarchyObject obj cell->MinCullDistance = obj.CullDistance; } cell->Node->AddObject(obj); + _objectToCell[obj.Object] = coord; // Cache minimum culling distance for a whole cell to skip it at once cell->MinCullDistance = Math::Min(cell->MinCullDistance, obj.CullDistance); @@ -159,14 +175,35 @@ void NetworkReplicationGridNode::AddObject(NetworkReplicationHierarchyObject obj bool NetworkReplicationGridNode::RemoveObject(ScriptingObject* obj) { - for (const auto& e : _children) + Int3 coord; + + if (!_objectToCell.TryGet(obj, coord)) { - if (e.Value.Node->RemoveObject(obj)) - { - // TODO: remove empty cells? - // TODO: update MinCullDistance for cell? - return true; - } + return false; + } + + if (_children[coord].Node->RemoveObject(obj)) + { + _objectToCell.Remove(obj); + // TODO: remove empty cells? + // TODO: update MinCullDistance for cell? + return true; + } + return false; +} + +bool NetworkReplicationGridNode::GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result) +{ + Int3 coord; + + if (!_objectToCell.TryGet(obj, coord)) + { + return false; + } + + if (_children[coord].Node->GetObject(obj, result)) + { + return true; } return false; } diff --git a/Source/Engine/Networking/NetworkReplicationHierarchy.h b/Source/Engine/Networking/NetworkReplicationHierarchy.h index 55cd6b7b1..b054de453 100644 --- a/Source/Engine/Networking/NetworkReplicationHierarchy.h +++ b/Source/Engine/Networking/NetworkReplicationHierarchy.h @@ -20,7 +20,7 @@ API_STRUCT(NoDefault, Namespace = "FlaxEngine.Networking") struct FLAXENGINE_API // The object to replicate. API_FIELD() ScriptingObjectReference Object; - // The target amount of the replication updates per second (frequency of the replication). Constrained by NetworkManager::NetworkFPS. Use 0 for 'always relevant' object. + // The target amount of the replication updates per second (frequency of the replication). Constrained by NetworkManager::NetworkFPS. Use 0 for 'always relevant' object and less than 0 (eg. -1) for 'never relevant' objects that would only get synched on client join once. API_FIELD() float ReplicationFPS = 60; // The minimum distance from the player to the object at which it can process replication. For example, players further away won't receive object data. Use 0 if unused. API_FIELD() float CullDistance = 15000; @@ -200,6 +200,14 @@ API_CLASS(Abstract, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API Ne /// True on successful removal, otherwise false. API_FUNCTION() virtual bool RemoveObject(ScriptingObject* obj); + /// + /// Gets object from the hierarchy. + /// + /// The object to get. + /// The hierarchy object to retrieve. + /// True on successful retrieval, otherwise false. + API_FUNCTION() virtual bool GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result); + /// /// Force replicates the object during the next update. Resets any internal tracking state to force the synchronization. /// @@ -238,6 +246,7 @@ private: }; Dictionary _children; + Dictionary _objectToCell; public: /// @@ -247,6 +256,7 @@ public: void AddObject(NetworkReplicationHierarchyObject obj) override; bool RemoveObject(ScriptingObject* obj) override; + bool GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result) override; void Update(NetworkReplicationHierarchyUpdateResult* result) override; }; diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 11bd5f916..ea3e8422a 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -700,9 +700,9 @@ void NetworkReplicator::AddRPC(const ScriptingTypeHandle& typeHandle, const Stri NetworkRpcInfo::RPCsTable[rpcName] = rpcInfo; } -void NetworkReplicator::CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MArray* targetIds) +bool NetworkReplicator::CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MArray* targetIds) { - EndInvokeRPC(obj, type, GetCSharpCachedName(name), argsStream, MUtils::ToSpan(targetIds)); + return EndInvokeRPC(obj, type, GetCSharpCachedName(name), argsStream, MUtils::ToSpan(targetIds)); } StringAnsiView NetworkReplicator::GetCSharpCachedName(const StringAnsiView& name) @@ -1113,12 +1113,12 @@ NetworkStream* NetworkReplicator::BeginInvokeRPC() return CachedWriteStream; } -void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span targetIds) +bool NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span targetIds) { Scripting::ObjectsLookupIdMapping.Set(nullptr); const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name)); if (!info || !obj || NetworkManager::IsOffline()) - return; + return false; ObjectsLock.Lock(); auto& rpc = RpcQueue.AddOne(); rpc.Object = obj; @@ -1135,12 +1135,23 @@ void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHa } #endif ObjectsLock.Unlock(); + + // Check if skip local execution (eg. server rpc called from client or client rpc with specific targets) + const NetworkManagerMode networkMode = NetworkManager::Mode; + if (info->Server && networkMode == NetworkManagerMode::Client) + return true; + if (info->Client && networkMode == NetworkManagerMode::Server) + return true; + if (info->Client && networkMode == NetworkManagerMode::Host && targetIds.IsValid() && !SpanContains(targetIds, NetworkManager::LocalClientId)) + return true; + return false; } void NetworkInternal::NetworkReplicatorClientConnected(NetworkClient* client) { ScopeLock lock(ObjectsLock); NewClients.Add(client); + ASSERT(sizeof(NetworkClientsMask) * 8 >= (uint32)NetworkManager::Clients.Count()); // Ensure that clients mask can hold all of clients } void NetworkInternal::NetworkReplicatorClientDisconnected(NetworkClient* client) @@ -1193,6 +1204,7 @@ void NetworkInternal::NetworkReplicatorClear() Objects.Remove(it); } } + Objects.Clear(); RpcQueue.Clear(); SpawnQueue.Clear(); DespawnQueue.Clear(); diff --git a/Source/Engine/Networking/NetworkReplicator.cs b/Source/Engine/Networking/NetworkReplicator.cs index 1ca251f75..bc437b82e 100644 --- a/Source/Engine/Networking/NetworkReplicator.cs +++ b/Source/Engine/Networking/NetworkReplicator.cs @@ -120,10 +120,11 @@ namespace FlaxEngine.Networking /// The RPC name. /// The RPC serialized arguments stream returned from BeginInvokeRPC. /// Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs. + /// True if RPC cannot be executed locally, false if execute it locally too (checks RPC mode and target client ids). [Unmanaged] - public static void EndInvokeRPC(Object obj, Type type, string name, NetworkStream argsStream, uint[] targetIds = null) + public static bool EndInvokeRPC(Object obj, Type type, string name, NetworkStream argsStream, uint[] targetIds = null) { - Internal_CSharpEndInvokeRPC(FlaxEngine.Object.GetUnmanagedPtr(obj), type, name, FlaxEngine.Object.GetUnmanagedPtr(argsStream), targetIds); + return Internal_CSharpEndInvokeRPC(FlaxEngine.Object.GetUnmanagedPtr(obj), type, name, FlaxEngine.Object.GetUnmanagedPtr(argsStream), targetIds); } /// diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h index 156e7eeea..ecccc52cd 100644 --- a/Source/Engine/Networking/NetworkReplicator.h +++ b/Source/Engine/Networking/NetworkReplicator.h @@ -195,13 +195,14 @@ public: /// The RPC name. /// The RPC serialized arguments stream returned from BeginInvokeRPC. /// Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs. - static void EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span targetIds = Span()); + /// True if RPC cannot be executed locally, false if execute it locally too (checks RPC mode and target client ids). + static bool EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span targetIds = Span()); private: #if !COMPILE_WITHOUT_CSHARP API_FUNCTION(NoProxy) static void AddSerializer(const ScriptingTypeHandle& typeHandle, const Function& serialize, const Function& deserialize); API_FUNCTION(NoProxy) static void AddRPC(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name, const Function& execute, bool isServer, bool isClient, NetworkChannelType channel); - API_FUNCTION(NoProxy) static void CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MArray* targetIds); + API_FUNCTION(NoProxy) static bool CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MArray* targetIds); static StringAnsiView GetCSharpCachedName(const StringAnsiView& name); #endif }; diff --git a/Source/Engine/Networking/NetworkRpc.h b/Source/Engine/Networking/NetworkRpc.h index c0c8a558e..6e8d26752 100644 --- a/Source/Engine/Networking/NetworkRpc.h +++ b/Source/Engine/Networking/NetworkRpc.h @@ -41,7 +41,7 @@ struct FLAXENGINE_API NetworkRpcInfo uint8 Client : 1; uint8 Channel : 4; void (*Execute)(ScriptingObject* obj, NetworkStream* stream, void* tag); - void (*Invoke)(ScriptingObject* obj, void** args); + bool (*Invoke)(ScriptingObject* obj, void** args); void* Tag; /// @@ -83,10 +83,7 @@ FORCE_INLINE void NetworkRpcInitArg(Array>& args, con { \ Array> args; \ NetworkRpcInitArg(args, __VA_ARGS__); \ - rpcInfo.Invoke(this, args.Get()); \ - if (rpcInfo.Server && networkMode == NetworkManagerMode::Client) \ - return; \ - if (rpcInfo.Client && networkMode == NetworkManagerMode::Server) \ + if (rpcInfo.Invoke(this, args.Get())) \ return; \ } \ } diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 18bcbdd54..1d7fa18a1 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -299,17 +299,19 @@ void ParticleEffect::Stop() void ParticleEffect::UpdateBounds() { BoundingBox bounds = BoundingBox::Empty; - if (ParticleSystem && Instance.LastUpdateTime >= 0) + const auto particleSystem = ParticleSystem.Get(); + if (particleSystem && Instance.LastUpdateTime >= 0) { - for (int32 j = 0; j < ParticleSystem->Tracks.Count(); j++) + for (int32 j = 0; j < particleSystem->Tracks.Count(); j++) { - const auto& track = ParticleSystem->Tracks[j]; + const auto& track = particleSystem->Tracks[j]; if (track.Type != ParticleSystem::Track::Types::Emitter || track.Disabled) continue; - auto& emitter = ParticleSystem->Emitters[track.AsEmitter.Index]; - auto& data = Instance.Emitters[track.AsEmitter.Index]; - if (!emitter || emitter->Capacity == 0 || emitter->Graph.Layout.Size == 0) + const int32 emitterIndex = track.AsEmitter.Index; + ParticleEmitter* emitter = particleSystem->Emitters[emitterIndex].Get(); + if (!emitter || emitter->Capacity == 0 || emitter->Graph.Layout.Size == 0 || Instance.Emitters.Count() <= emitterIndex) continue; + auto& data = Instance.Emitters[emitterIndex]; BoundingBox emitterBounds; if (emitter->GraphExecutorCPU.ComputeBounds(emitter, this, data, emitterBounds)) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 2ed59c8f6..f5f729d39 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -230,10 +230,11 @@ void WheeledVehicle::DrawPhysicsDebug(RenderView& view) { const Vector3 currentPos = wheel.Collider->GetPosition(); const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0); + const Quaternion wheelDebugOrientation = GetOrientation() * Quaternion::Euler(-data.State.RotationAngle, data.State.SteerAngle, 0) * Quaternion::Euler(90, 0, 90); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(basePos, wheel.Radius * 0.07f), Color::Blue * 0.3f, 0, true); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, true); DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, true); - DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.8f, 0, true); + DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheelDebugOrientation, wheel.Radius, wheel.Width, Color::Red * 0.8f, 0, true); if (!data.State.IsInAir) { DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.TireContactPoint, 5.0f), Color::Green, 0, true); @@ -260,6 +261,7 @@ void WheeledVehicle::OnDebugDrawSelected() { const Vector3 currentPos = wheel.Collider->GetPosition(); const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0); + const Quaternion wheelDebugOrientation = GetOrientation() * Quaternion::Euler(-data.State.RotationAngle, data.State.SteerAngle, 0) * Quaternion::Euler(90, 0, 90); Transform actorPose = Transform::Identity, shapePose = Transform::Identity; PhysicsBackend::GetRigidActorPose(_actor, actorPose.Translation, actorPose.Orientation); PhysicsBackend::GetShapeLocalPose(wheel.Collider->GetPhysicsShape(), shapePose.Translation, shapePose.Orientation); @@ -267,7 +269,7 @@ void WheeledVehicle::OnDebugDrawSelected() DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, false); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(actorPose.LocalToWorld(shapePose.Translation), wheel.Radius * 0.11f), Color::OrangeRed * 0.8f, 0, false); DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, false); - DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.4f, 0, false); + DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheelDebugOrientation, wheel.Radius, wheel.Width, Color::Red * 0.4f, 0, false); if (!data.State.SuspensionTraceStart.IsZero()) { DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.SuspensionTraceStart, 5.0f), Color::AliceBlue, 0, false); diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 52f6e58e4..57ef4ce17 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -279,6 +279,60 @@ class CharacterControllerFilterPhysX : public PxControllerFilterCallback } }; +class CharacterControllerHitReportPhysX : public PxUserControllerHitReport +{ + void onHit(const PxControllerHit& hit, Collision& c) + { + ASSERT_LOW_LAYER(c.ThisActor && c.OtherActor); + c.Impulse = Vector3::Zero; + c.ThisVelocity = P2C(hit.dir) * hit.length; + c.OtherVelocity = Vector3::Zero; + c.ContactsCount = 1; + ContactPoint& contact = c.Contacts[0]; + contact.Point = P2C(hit.worldPos); + contact.Normal = P2C(hit.worldNormal); + contact.Separation = 0.0f; + + //auto simulationEventCallback = static_cast(hit.controller->getScene()->getSimulationEventCallback()); + //simulationEventCallback->Collisions[SimulationEventCallback::CollidersPair(c.ThisActor, c.OtherActor)] = c; + // TODO: build additional list for hit-only events to properly send enter/exit pairs instead of spamming every frame whether controller executes move + + // Single-hit collision + c.ThisActor->OnCollisionEnter(c); + c.SwapObjects(); + c.ThisActor->OnCollisionEnter(c); + c.SwapObjects(); + c.ThisActor->OnCollisionExit(c); + c.SwapObjects(); + c.ThisActor->OnCollisionExit(c); + } + + void onShapeHit(const PxControllerShapeHit& hit) override + { + Collision c; + PxShape* controllerShape; + hit.controller->getActor()->getShapes(&controllerShape, 1); + c.ThisActor = static_cast(controllerShape->userData); + c.OtherActor = static_cast(hit.shape->userData); + onHit(hit, c); + } + + void onControllerHit(const PxControllersHit& hit) override + { + Collision c; + PxShape* controllerShape; + hit.controller->getActor()->getShapes(&controllerShape, 1); + c.ThisActor = static_cast(controllerShape->userData); + hit.other->getActor()->getShapes(&controllerShape, 1); + c.OtherActor = static_cast(controllerShape->userData); + onHit(hit, c); + } + + void onObstacleHit(const PxControllerObstacleHit& hit) override + { + } +}; + #if WITH_VEHICLE class WheelFilterPhysX : public PxQueryFilterCallback @@ -463,6 +517,7 @@ namespace QueryFilterPhysX QueryFilter; CharacterQueryFilterPhysX CharacterQueryFilter; CharacterControllerFilterPhysX CharacterControllerFilter; + CharacterControllerHitReportPhysX CharacterControllerHitReport; Dictionary> SceneOrigins; CriticalSection FlushLocker; @@ -2811,6 +2866,7 @@ void* PhysicsBackend::CreateController(void* scene, IPhysicsActor* actor, Physic const Vector3 sceneOrigin = SceneOrigins[scenePhysX->Scene]; PxCapsuleControllerDesc desc; desc.userData = actor; + desc.reportCallback = &CharacterControllerHitReport; desc.contactOffset = Math::Max(contactOffset, ZeroTolerance); desc.position = PxExtendedVec3(position.X - sceneOrigin.X, position.Y - sceneOrigin.Y, position.Z - sceneOrigin.Z); desc.slopeLimit = Math::Cos(slopeLimit * DegreesToRadians); diff --git a/Source/Engine/Platform/Base/FileBase.cpp b/Source/Engine/Platform/Base/FileBase.cpp index f45685c7f..eb11292b0 100644 --- a/Source/Engine/Platform/Base/FileBase.cpp +++ b/Source/Engine/Platform/Base/FileBase.cpp @@ -309,6 +309,45 @@ bool FileBase::WriteAllText(const StringView& path, const Char* data, int32 leng return WriteAllBytes(path, tmp.Get(), tmp.Count()); } + case Encoding::UTF8: + { + Array tmp; + tmp.SetCapacity(length); + + for (int32 i = 0; i < length; i++) + { + Char c = data[i]; + if (c < 0x0080) + { + tmp.Add((byte)c); + } + else if (c < 0x0800) + { + tmp.Add(0xC0 | (c >> 6)); + tmp.Add(0x80 | (c & 0x3F)); + } + else if (c < 0xD800 || c >= 0xE000) + { + tmp.Add(0xE0 | (c >> 12)); + tmp.Add(0x80 | ((c >> 6) & 0x3F)); + tmp.Add(0x80 | (c & 0x3F)); + } + else // Surrogate pair + { + ++i; + if (i >= length) + return true; + + uint32 p = 0x10000 + (((c & 0x3FF) << 10) | (data[i] & 0x3FF)); + tmp.Add(0xF0 | (p >> 18)); + tmp.Add(0x80 | ((p >> 12) & 0x3F)); + tmp.Add(0x80 | ((p >> 6) & 0x3F)); + tmp.Add(0x80 | (p & 0x3F)); + } + } + + return WriteAllBytes(path, tmp.Get(), tmp.Count()); + } default: return true; } diff --git a/Source/Engine/Platform/StringUtils.h b/Source/Engine/Platform/StringUtils.h index 04e644d2e..72b3b5f4b 100644 --- a/Source/Engine/Platform/StringUtils.h +++ b/Source/Engine/Platform/StringUtils.h @@ -179,7 +179,7 @@ public: public: // Converts characters from ANSI to UTF-16 - static void ConvertANSI2UTF16(const char* from, Char* to, int32 len); + static void ConvertANSI2UTF16(const char* from, Char* to, int32 fromLength, int32& toLength); // Converts characters from UTF-16 to ANSI static void ConvertUTF162ANSI(const Char* from, char* to, int32 len); diff --git a/Source/Engine/Platform/Unix/UnixStringUtils.cpp b/Source/Engine/Platform/Unix/UnixStringUtils.cpp index 7e5c34b24..daa705769 100644 --- a/Source/Engine/Platform/Unix/UnixStringUtils.cpp +++ b/Source/Engine/Platform/Unix/UnixStringUtils.cpp @@ -311,14 +311,14 @@ static inline uint32 Utf8ToUtf32Codepoint(const char* src, int32 length) } } -void StringUtils::ConvertANSI2UTF16(const char* from, Char* to, int32 len) +void StringUtils::ConvertANSI2UTF16(const char* from, Char* to, int32 fromLength, int32& toLength) { - const char* const u8end = from + len; + const char* const u8end = from + fromLength; const char* u8cur = from; char16_t* u16cur = to; while (u8cur < u8end) { - len = Utf8CodepointLength(*u8cur); + int32 len = Utf8CodepointLength(*u8cur); uint32 codepoint = Utf8ToUtf32Codepoint(u8cur, len); // Convert the UTF32 codepoint to one or more UTF16 codepoints @@ -336,6 +336,7 @@ void StringUtils::ConvertANSI2UTF16(const char* from, Char* to, int32 len) } u8cur += len; } + toLength = (int32)(u16cur - to); } static const char32_t kByteMask = 0x000000BF; diff --git a/Source/Engine/Platform/Win32/Win32StringUtils.cpp b/Source/Engine/Platform/Win32/Win32StringUtils.cpp index d1634d90d..2a9bcf174 100644 --- a/Source/Engine/Platform/Win32/Win32StringUtils.cpp +++ b/Source/Engine/Platform/Win32/Win32StringUtils.cpp @@ -179,10 +179,12 @@ const char* StringUtils::Find(const char* str, const char* toFind) return strstr(str, toFind); } -void StringUtils::ConvertANSI2UTF16(const char* from, Char* to, int32 len) +void StringUtils::ConvertANSI2UTF16(const char* from, Char* to, int32 fromLength, int32& toLength) { - if (len) - mbstowcs(to, from, len); + if (fromLength) + toLength = mbstowcs(to, from, fromLength); + else + toLength = 0; } void StringUtils::ConvertUTF162ANSI(const Char* from, char* to, int32 len) diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 8d02312af..4d43272d1 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -992,7 +992,8 @@ void ReadPipe(HANDLE pipe, Array& rawData, Array& logData, LogType l // Log contents logData.Clear(); logData.Resize(rawData.Count() + 1); - StringUtils::ConvertANSI2UTF16(rawData.Get(), logData.Get(), rawData.Count()); + int32 tmp; + StringUtils::ConvertANSI2UTF16(rawData.Get(), logData.Get(), rawData.Count(), tmp); logData.Last() = '\0'; if (settings.LogOutput) Log::Logger::Write(logType, StringView(logData.Get(), rawData.Count())); diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index f12a9ce1c..90423f5ce 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -7,7 +7,7 @@ #include "Engine/Threading/Threading.h" #include "IncludeFreeType.h" -Font::Font(FontAsset* parentAsset, int32 size) +Font::Font(FontAsset* parentAsset, float size) : ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer)) , _asset(parentAsset) , _size(size) @@ -436,7 +436,7 @@ void Font::FlushFaceSize() const { // Set the character size const FT_Face face = _asset->GetFTFace(); - const FT_Error error = FT_Set_Char_Size(face, 0, ConvertPixelTo26Dot6((float)_size * FontManager::FontScale), DefaultDPI, DefaultDPI); + const FT_Error error = FT_Set_Char_Size(face, 0, ConvertPixelTo26Dot6(_size * FontManager::FontScale), DefaultDPI, DefaultDPI); if (error) { LOG_FT_ERROR(error); diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index d68f497d6..90f723cd8 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -228,7 +228,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Font); private: FontAsset* _asset; - int32 _size; + float _size; int32 _height; int32 _ascender; int32 _descender; @@ -244,7 +244,7 @@ public: /// /// The parent asset. /// The size. - Font(FontAsset* parentAsset, int32 size); + Font(FontAsset* parentAsset, float size); /// /// Finalizes an instance of the class. @@ -264,7 +264,7 @@ public: /// /// Gets font size. /// - API_PROPERTY() FORCE_INLINE int32 GetSize() const + API_PROPERTY() FORCE_INLINE float GetSize() const { return _size; } diff --git a/Source/Engine/Render2D/FontAsset.cpp b/Source/Engine/Render2D/FontAsset.cpp index 5fa07771f..f000eb3c6 100644 --- a/Source/Engine/Render2D/FontAsset.cpp +++ b/Source/Engine/Render2D/FontAsset.cpp @@ -92,7 +92,7 @@ void FontAsset::SetOptions(const FontOptions& value) _options = value; } -Font* FontAsset::CreateFont(int32 size) +Font* FontAsset::CreateFont(float size) { PROFILE_CPU(); diff --git a/Source/Engine/Render2D/FontAsset.h b/Source/Engine/Render2D/FontAsset.h index abc601f45..60dbee01d 100644 --- a/Source/Engine/Render2D/FontAsset.h +++ b/Source/Engine/Render2D/FontAsset.h @@ -139,7 +139,7 @@ public: /// /// The font characters size. /// The created font object. - API_FUNCTION() Font* CreateFont(int32 size); + API_FUNCTION() Font* CreateFont(float size); /// /// Gets the font with bold style. Returns itself or creates a new virtual font asset using this font but with bold option enabled. diff --git a/Source/Engine/Render2D/FontReference.cs b/Source/Engine/Render2D/FontReference.cs index 290e10312..cc32bcd9a 100644 --- a/Source/Engine/Render2D/FontReference.cs +++ b/Source/Engine/Render2D/FontReference.cs @@ -13,7 +13,7 @@ namespace FlaxEngine private FontAsset _font; [NoSerialize] - private int _size; + private float _size; [NoSerialize] private Font _cachedFont; @@ -33,7 +33,7 @@ namespace FlaxEngine /// /// The font. /// The font size. - public FontReference(FontAsset font, int size) + public FontReference(FontAsset font, float size) { _font = font; _size = size; @@ -91,7 +91,7 @@ namespace FlaxEngine /// The size of the font characters. /// [EditorOrder(10), Limit(1, 500, 0.1f), Tooltip("The size of the font characters.")] - public int Size + public float Size { get => _size; set @@ -187,7 +187,7 @@ namespace FlaxEngine unchecked { int hashCode = _font ? _font.GetHashCode() : 0; - hashCode = (hashCode * 397) ^ _size; + hashCode = (hashCode * 397) ^ _size.GetHashCode(); return hashCode; } } diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index f2c5ee376..246c1620f 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -712,7 +712,8 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa // Provide new path of hot-reloaded native library path for managed DllImport if (nativePath.HasChars()) { - RegisterNativeLibrary(assemblyPathAnsi.Get(), StringAnsi(nativePath).Get()); + StringAnsi nativeName = _name.EndsWith(".CSharp") ? StringAnsi(_name.Get(), _name.Length() - 7) : StringAnsi(_name); + RegisterNativeLibrary(nativeName.Get(), StringAnsi(nativePath).Get()); } _hasCachedClasses = false; @@ -845,7 +846,7 @@ bool MClass::IsSubClassOf(const MClass* klass, bool checkInterfaces) const bool MClass::HasInterface(const MClass* klass) const { static void* TypeIsAssignableFrom = GetStaticMethodPointer(TEXT("TypeIsAssignableFrom")); - return klass && CallStaticMethod(TypeIsAssignableFrom, _handle, klass->_handle); + return klass && CallStaticMethod(TypeIsAssignableFrom, klass->_handle, _handle); } bool MClass::IsInstanceOfType(MObject* object) const diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 9bddc333e..e2d709653 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -559,6 +559,16 @@ void Scripting::Release() } _objectsLocker.Unlock(); + // Release assets sourced from game assemblies + const auto flaxModule = GetBinaryModuleFlaxEngine(); + for (auto asset : Content::GetAssets()) + { + if (asset->GetTypeHandle().Module == flaxModule) + continue; + + asset->DeleteObjectNow(); + } + // Unload assemblies (from back to front) { LOG(Info, "Unloading binary modules"); @@ -640,9 +650,9 @@ void Scripting::Reload(bool canTriggerSceneReload) MCore::GC::WaitForPendingFinalizers(); // Destroy objects from game assemblies (eg. not released objects that might crash if persist in memory after reload) + const auto flaxModule = GetBinaryModuleFlaxEngine(); _objectsLocker.Lock(); { - const auto flaxModule = GetBinaryModuleFlaxEngine(); for (auto i = _objectsDictionary.Begin(); i.IsNotEnd(); ++i) { auto obj = i->Value; @@ -657,6 +667,15 @@ void Scripting::Reload(bool canTriggerSceneReload) } _objectsLocker.Unlock(); + // Release assets sourced from game assemblies + for (auto asset : Content::GetAssets()) + { + if (asset->GetTypeHandle().Module == flaxModule) + continue; + + asset->DeleteObjectNow(); + } + // Unload all game modules LOG(Info, "Unloading game binary modules"); auto modules = BinaryModule::GetModules(); diff --git a/Source/Engine/Serialization/ReadStream.h b/Source/Engine/Serialization/ReadStream.h index 9d3215442..be80e3249 100644 --- a/Source/Engine/Serialization/ReadStream.h +++ b/Source/Engine/Serialization/ReadStream.h @@ -6,6 +6,7 @@ #include "Engine/Core/Templates.h" extern FLAXENGINE_API class ScriptingObject* FindObject(const Guid& id, class MClass* type); +extern FLAXENGINE_API class Asset* LoadAsset(const Guid& id, const struct ScriptingTypeHandle& type); /// /// Base class for all data read streams @@ -159,6 +160,34 @@ public: Read(ptr); v = ptr; } + template + FORCE_INLINE void Read(SoftObjectReference& v) + { + uint32 id[4]; + ReadBytes(id, sizeof(id)); + v.Set(*(Guid*)id); + } + template + FORCE_INLINE void Read(AssetReference& v) + { + uint32 id[4]; + ReadBytes(id, sizeof(id)); + v = (T*)::LoadAsset(*(Guid*)id, T::TypeInitializer); + } + template + FORCE_INLINE void Read(WeakAssetReference& v) + { + uint32 id[4]; + ReadBytes(id, sizeof(id)); + v = (T*)::LoadAsset(*(Guid*)id, T::TypeInitializer); + } + template + FORCE_INLINE void Read(SoftAssetReference& v) + { + uint32 id[4]; + ReadBytes(id, sizeof(id)); + v.Set(*(Guid*)id); + } /// /// Read data array diff --git a/Source/Engine/Serialization/Stream.h b/Source/Engine/Serialization/Stream.h index 83f0eaf21..e477eee95 100644 --- a/Source/Engine/Serialization/Stream.h +++ b/Source/Engine/Serialization/Stream.h @@ -17,6 +17,14 @@ class ISerializable; class ScriptingObject; template class ScriptingObjectReference; +template +class SoftObjectReference; +template +class AssetReference; +template +class WeakAssetReference; +template +class SoftAssetReference; /// /// Base class for all data streams (memory streams, file streams etc.) diff --git a/Source/Engine/Serialization/WriteStream.h b/Source/Engine/Serialization/WriteStream.h index 92ac211ca..95bd87df9 100644 --- a/Source/Engine/Serialization/WriteStream.h +++ b/Source/Engine/Serialization/WriteStream.h @@ -176,6 +176,26 @@ public: { Write(v.Get()); } + template + FORCE_INLINE void Write(const SoftObjectReference& v) + { + Write(v.Get()); + } + template + FORCE_INLINE void Write(const AssetReference& v) + { + Write(v.Get()); + } + template + FORCE_INLINE void Write(const WeakAssetReference& v) + { + Write(v.Get()); + } + template + FORCE_INLINE void Write(const SoftAssetReference& v) + { + Write(v.Get()); + } template void Write(const Array& data) diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index d4db794eb..cd60abddf 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -73,14 +73,14 @@ void TextRender::SetColor(const Color& value) } } -int32 TextRender::GetFontSize() const +float TextRender::GetFontSize() const { return _size; } -void TextRender::SetFontSize(int32 value) +void TextRender::SetFontSize(float value) { - value = Math::Clamp(value, 1, 1024); + value = Math::Clamp(value, 1.0f, 1024.0f); if (_size != value) { _size = value; diff --git a/Source/Engine/UI/TextRender.h b/Source/Engine/UI/TextRender.h index d69b5427b..816ad8aa4 100644 --- a/Source/Engine/UI/TextRender.h +++ b/Source/Engine/UI/TextRender.h @@ -38,7 +38,7 @@ private: LocalizedString _text; Color _color; TextLayoutOptions _layoutOptions; - int32 _size; + float _size; int32 _sceneRenderingKey = -1; BoundingBox _localBox; @@ -91,12 +91,12 @@ public: /// Gets the font characters size. /// API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(32), Limit(1, 1000), EditorDisplay(\"Text\")") - int32 GetFontSize() const; + float GetFontSize() const; /// /// Sets the font characters size. /// - API_PROPERTY() void SetFontSize(int32 value); + API_PROPERTY() void SetFontSize(float value); /// /// The draw passes to use for rendering this object. diff --git a/Source/Engine/Utilities/StringConverter.h b/Source/Engine/Utilities/StringConverter.h index 340278c00..e494a8b94 100644 --- a/Source/Engine/Utilities/StringConverter.h +++ b/Source/Engine/Utilities/StringConverter.h @@ -49,7 +49,7 @@ public: { } - StringAsANSI(const Char* text, const int32 length) + StringAsANSI(const Char* text, int32 length) { if (length + 1 < InlinedSize) { @@ -83,7 +83,7 @@ public: { } - StringAsUTF8(const Char* text, const int32 length) + StringAsUTF8(const Char* text, int32 length) { int32 lengthUtf8; if (length + 1 < InlinedSize) @@ -112,17 +112,17 @@ public: { } - StringAsUTF16(const char* text, const int32 length) + StringAsUTF16(const char* text, int32 length) { if (length + 1 < InlinedSize) { - StringUtils::ConvertANSI2UTF16(text, this->_inlined, length); + StringUtils::ConvertANSI2UTF16(text, this->_inlined, length, length); this->_inlined[length] = 0; } else { this->_dynamic = (CharType*)Allocator::Allocate((length + 1) * sizeof(CharType)); - StringUtils::ConvertANSI2UTF16(text, this->_dynamic, length); + StringUtils::ConvertANSI2UTF16(text, this->_dynamic, length, length); this->_dynamic[length] = 0; } } diff --git a/Source/Engine/Utilities/Utils.cs b/Source/Engine/Utilities/Utils.cs index 5ac43ecd9..cb385efcf 100644 --- a/Source/Engine/Utilities/Utils.cs +++ b/Source/Engine/Utilities/Utils.cs @@ -255,7 +255,7 @@ namespace FlaxEngine #else private class ExtractArrayFromListContext { - public static FieldInfo? itemsField; + public static FieldInfo itemsField; } internal static T[] ExtractArrayFromList(List list) { diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 0cb8982a2..d69b8dcbf 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1204,7 +1204,7 @@ namespace Flax.Build.Bindings if (apiType != null) { if (parameterInfo.IsOut) - contents.Append(indent).AppendFormat("{1} {0}Temp;", parameterInfo.Name, new TypeInfo(parameterInfo.Type) { IsRef = false }.GetFullNameNative(buildData, caller)).AppendLine(); + contents.Append(indent).AppendFormat("{1} {0}Temp;", parameterInfo.Name, parameterInfo.Type.GetFullNameNative(buildData, caller, false)).AppendLine(); else contents.Append(indent).AppendFormat("auto {0}Temp = {1};", parameterInfo.Name, param).AppendLine(); if (parameterInfo.Type.IsPtr && !parameterInfo.Type.IsRef) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index b09b0c137..c05ab4e73 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -1417,6 +1417,9 @@ namespace Flax.Build.Bindings context.Tokenizer.SkipUntil(TokenType.Comma, out desc.Lang); desc.Code = context.Tokenizer.ExpectToken(TokenType.String).Value.Replace("\\\"", "\""); desc.Code = desc.Code.Substring(1, desc.Code.Length - 2); + desc.Code = desc.Code.Replace("\\\n", "\n"); + desc.Code = desc.Code.Replace("\\\r\n", "\n"); + desc.Code = desc.Code.Replace("\t", " "); context.Tokenizer.ExpectToken(TokenType.RightParent); return desc; } diff --git a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs index 7b2326527..df47c85e4 100644 --- a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs @@ -144,7 +144,7 @@ namespace Flax.Build.Bindings GenericArgs = BindingsGenerator.Read(reader, GenericArgs); } - public string GetFullNameNative(Builder.BuildData buildData, ApiTypeInfo caller) + public string GetFullNameNative(Builder.BuildData buildData, ApiTypeInfo caller, bool canRef = true, bool canConst = true) { var type = BindingsGenerator.FindApiTypeInfo(buildData, this, caller); if (type == null) @@ -155,7 +155,7 @@ namespace Flax.Build.Bindings return type.FullNameNative; var sb = new StringBuilder(64); - if (IsConst) + if (IsConst && canConst) sb.Append("const "); sb.Append(type.FullNameNative); if (GenericArgs != null) @@ -171,7 +171,7 @@ namespace Flax.Build.Bindings } if (IsPtr) sb.Append('*'); - if (IsRef) + if (IsRef && canRef) sb.Append('&'); return sb.ToString(); } diff --git a/Source/Tools/Flax.Build/Build/Builder.Projects.cs b/Source/Tools/Flax.Build/Build/Builder.Projects.cs index 2860148de..4fbabe986 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Projects.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Projects.cs @@ -330,7 +330,7 @@ namespace Flax.Build var referenceBuildOptions = GetBuildOptions(referenceTarget, configurationData.TargetBuildOptions.Platform, configurationData.TargetBuildOptions.Toolchain, configurationData.Architecture, configurationData.Configuration, reference.Project.ProjectFolderPath); referenceBuildOptions.Flags |= BuildFlags.GenerateProject; var referenceModules = CollectModules(rules, referenceBuildOptions.Platform, referenceTarget, referenceBuildOptions, referenceBuildOptions.Toolchain, referenceBuildOptions.Architecture, referenceBuildOptions.Configuration); - var referenceBinaryModules = GetBinaryModules(projectInfo, referenceTarget, referenceModules); + var referenceBinaryModules = referenceModules.Keys.GroupBy(x => x.BinaryModuleName).ToArray(); foreach (var binaryModule in referenceBinaryModules) { project.Defines.Add(binaryModule.Key.ToUpperInvariant() + "_API="); diff --git a/Source/Tools/Flax.Build/Build/Builder.cs b/Source/Tools/Flax.Build/Build/Builder.cs index 0765a94ab..aa0f7c879 100644 --- a/Source/Tools/Flax.Build/Build/Builder.cs +++ b/Source/Tools/Flax.Build/Build/Builder.cs @@ -143,6 +143,16 @@ namespace Flax.Build { Log.Info("Removing: " + targetBuildOptions.IntermediateFolder); CleanDirectory(intermediateFolder); + intermediateFolder.Create(); + } + + // Delete all output files + var outputFolder = new DirectoryInfo(targetBuildOptions.OutputFolder); + if (outputFolder.Exists) + { + Log.Info("Removing: " + targetBuildOptions.OutputFolder); + CleanDirectory(outputFolder); + outputFolder.Create(); } } } diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs index 921a1e43f..13433f92c 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs @@ -4,14 +4,16 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using Flax.Build.Graph; +using Flax.Build.NativeCpp; using Flax.Deploy; namespace Flax.Build { static partial class Builder { - public static event Action> BuildDotNetAssembly; + public static event Action> BuildDotNetAssembly; private static void BuildTargetDotNet(RulesAssembly rules, TaskGraph graph, Target target, Platform platform, TargetConfiguration configuration) { @@ -150,7 +152,7 @@ namespace Flax.Build } } - private static void BuildDotNet(TaskGraph graph, BuildData buildData, NativeCpp.BuildOptions buildOptions, string name, List sourceFiles, HashSet fileReferences = null, IGrouping binaryModule = null) + private static void BuildDotNet(TaskGraph graph, BuildData buildData, BuildOptions buildOptions, string name, List sourceFiles, HashSet fileReferences = null, IGrouping binaryModule = null) { // Setup build options var buildPlatform = Platform.BuildTargetPlatform; @@ -163,6 +165,8 @@ namespace Flax.Build if (!dotnetSdk.IsValid) throw new Exception("Cannot compile C# without .NET SDK"); string dotnetPath = "dotnet", referenceAnalyzers; + string[] runtimeVersionNameParts = dotnetSdk.RuntimeVersionName.Split('.'); + string runtimeVersionShort = runtimeVersionNameParts[0] + '.' + runtimeVersionNameParts[1]; #else string monoRoot, monoPath; #endif @@ -173,7 +177,7 @@ namespace Flax.Build #if USE_NETCORE dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet.exe"); cscPath = Path.Combine(dotnetSdk.RootPath, @$"sdk\{dotnetSdk.VersionName}\Roslyn\bincore\csc.dll"); - referenceAssemblies = Path.Combine(dotnetSdk.RootPath, @$"shared\Microsoft.NETCore.App\{dotnetSdk.RuntimeVersionName}\"); + referenceAssemblies = Path.Combine(dotnetSdk.RootPath, @$"packs\Microsoft.NETCore.App.Ref\{dotnetSdk.RuntimeVersionName}\ref\net{runtimeVersionShort}\"); referenceAnalyzers = Path.Combine(dotnetSdk.RootPath, @$"packs\Microsoft.NETCore.App.Ref\{dotnetSdk.RuntimeVersionName}\analyzers\dotnet\cs\"); #else monoRoot = Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Editor", "Windows", "Mono"); @@ -189,7 +193,7 @@ namespace Flax.Build { #if USE_NETCORE cscPath = Path.Combine(dotnetSdk.RootPath, $"sdk/{dotnetSdk.VersionName}/Roslyn/bincore/csc.dll"); - referenceAssemblies = Path.Combine(dotnetSdk.RootPath, $"shared/Microsoft.NETCore.App/{dotnetSdk.RuntimeVersionName}/"); + referenceAssemblies = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/ref/net{runtimeVersionShort}/"); referenceAnalyzers = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/analyzers/dotnet/cs/"); #else monoRoot = Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Editor", "Linux", "Mono"); @@ -203,7 +207,7 @@ namespace Flax.Build { #if USE_NETCORE cscPath = Path.Combine(dotnetSdk.RootPath, $"sdk/{dotnetSdk.VersionName}/Roslyn/bincore/csc.dll"); - referenceAssemblies = Path.Combine(dotnetSdk.RootPath, $"shared/Microsoft.NETCore.App/{dotnetSdk.RuntimeVersionName}/"); + referenceAssemblies = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/ref/net{runtimeVersionShort}/"); referenceAnalyzers = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/analyzers/dotnet/cs/"); #else monoRoot = Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Editor", "Mac", "Mono"); @@ -242,7 +246,9 @@ namespace Flax.Build args.Add("/filealign:512"); #if USE_NETCORE args.Add("/langversion:11.0"); - args.Add("-nowarn:8632"); // Nullable + args.Add(string.Format("/nullable:{0}", buildOptions.ScriptingAPI.CSharpNullableReferences.ToString().ToLowerInvariant())); + if (buildOptions.ScriptingAPI.CSharpNullableReferences == CSharpNullableReferences.Disable) + args.Add("-nowarn:8632"); // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. #else args.Add("/langversion:7.3"); #endif @@ -253,6 +259,7 @@ namespace Flax.Build args.Add(buildData.Configuration == TargetConfiguration.Release ? "/optimize+" : "/optimize-"); #else args.Add(buildData.Configuration == TargetConfiguration.Debug ? "/optimize-" : "/optimize+"); + args.Add(string.Format("/reference:\"{0}mscorlib.dll\"", referenceAssemblies)); #endif args.Add(string.Format("/out:\"{0}\"", outputFile)); args.Add(string.Format("/doc:\"{0}\"", outputDocFile)); @@ -260,7 +267,6 @@ namespace Flax.Build args.Add("/define:" + string.Join(";", buildOptions.ScriptingAPI.Defines)); if (buildData.Configuration == TargetConfiguration.Debug) args.Add("/define:DEBUG"); - args.Add(string.Format("/reference:\"{0}mscorlib.dll\"", referenceAssemblies)); foreach (var reference in buildOptions.ScriptingAPI.SystemReferences) args.Add(string.Format("/reference:\"{0}{1}.dll\"", referenceAssemblies, reference)); foreach (var reference in fileReferences) @@ -272,6 +278,13 @@ namespace Flax.Build foreach (var sourceFile in sourceFiles) args.Add("\"" + sourceFile + "\""); +#if USE_NETCORE + // Inject some assembly metadata (similar to msbuild in Visual Studio) + var assemblyAttributesPath = Path.Combine(buildOptions.IntermediateFolder, name + ".AssemblyAttributes.cs"); + File.WriteAllText(assemblyAttributesPath, $"[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(\".NETCoreApp,Version=v{runtimeVersionShort}\", FrameworkDisplayName = \".NET {runtimeVersionShort}\")]\n", Encoding.UTF8); + args.Add("\"" + assemblyAttributesPath + "\""); +#endif + // Generate response file with source files paths and compilation arguments string responseFile = Path.Combine(buildOptions.IntermediateFolder, name + ".response"); Utilities.WriteFileIfChanged(responseFile, string.Join(Environment.NewLine, args)); diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs index 8a5a55873..ef365d3b7 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs @@ -23,6 +23,32 @@ namespace Flax.Build.NativeCpp GenerateProject = 1, } + /// + /// The nullable context type used with reference types (C#). + /// + public enum CSharpNullableReferences + { + /// + /// The code is nullable oblivious, nullable warnings and language analysis features are disabled. + /// + Disable, + + /// + /// The compiler enables all null reference analysis and all language features. + /// + Enable, + + /// + /// The compiler performs all null analysis and emits warnings when code might dereference null. + /// + Warnings, + + /// + /// The compiler doesn't perform null analysis or emit warnings when code might dereference null. + /// + Annotations, + } + /// /// The native C++ module build settings container. /// @@ -188,6 +214,15 @@ namespace Flax.Build.NativeCpp /// public bool IgnoreMissingDocumentationWarnings; + /// + /// The nullable context used in C# project. + /// + public CSharpNullableReferences CSharpNullableReferences = CSharpNullableReferences.Disable; + + public ScriptingAPIOptions() + { + } + /// /// Adds the other options into this. /// @@ -209,6 +244,8 @@ namespace Flax.Build.NativeCpp Defines = new HashSet(), SystemReferences = new HashSet { + "mscorlib", + "netstandard", "Microsoft.CSharp", "System", @@ -223,25 +260,32 @@ namespace Flax.Build.NativeCpp //"System.ComponentModel.TypeConverter", "System.Console", "System.Core", + "System.Diagnostics.StackTrace", "System.Globalization", "System.IO", "System.IO.Compression", "System.IO.FileSystem.Watcher", "System.Linq", "System.Linq.Expressions", + "System.Memory", + "System.Net", "System.Net.Http", "System.Net.Primitives", "System.ObjectModel", - "System.Private.CoreLib", - "System.Private.Uri", - //"System.Private.Xml", + "System.ValueTuple", - "System.Reflection", "System.Runtime", + "System.Runtime.Extensions", + "System.Runtime.Handles", + "System.Runtime.Intrinsics", + "System.Runtime.Numerics", + "System.Runtime.Loader", "System.Runtime.CompilerServices.Unsafe", "System.Runtime.InteropServices", "System.Runtime.InteropServices.RuntimeInformation", - "System.Runtime.Serialization.Formatters", // BinaryFormatter + "System.Runtime.Serialization", + "System.Runtime.Serialization.Formatters", + "System.Security.Cryptography", "System.Security.Cryptography.Algorithms", "System.Security.Cryptography.Primitives", @@ -249,8 +293,11 @@ namespace Flax.Build.NativeCpp "System.Threading.Tasks.Parallel", //"System.Xml", + "System.Threading", + "System.Threading.Thread", + + "System.Reflection", //"System.Reflection.Metadata", - "netstandard", }, SystemAnalyzers = new HashSet { diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 21464ab55..d646a81cd 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -241,7 +241,7 @@ namespace Flax.Build.Plugins // Deserialize arguments argNames += arg.Name; - contents.AppendLine($" {arg.Type.Type} {arg.Name};"); + contents.AppendLine($" {arg.Type.GetFullNameNative(buildData, typeInfo, false, false)} {arg.Name};"); contents.AppendLine($" stream->Read({arg.Name});"); } @@ -254,7 +254,7 @@ namespace Flax.Build.Plugins // Generated method thunk to invoke RPC to network { - contents.Append(" static void ").Append(functionInfo.Name).AppendLine("_Invoke(ScriptingObject* obj, void** args)"); + contents.Append(" static bool ").Append(functionInfo.Name).AppendLine("_Invoke(ScriptingObject* obj, void** args)"); contents.AppendLine(" {"); contents.AppendLine(" NetworkStream* stream = NetworkReplicator::BeginInvokeRPC();"); contents.AppendLine(" Span targetIds;"); @@ -270,11 +270,11 @@ namespace Flax.Build.Plugins } // Serialize arguments - contents.AppendLine($" stream->Write(*({arg.Type.Type}*)args[{i}]);"); + contents.AppendLine($" stream->Write(*(const {arg.Type.GetFullNameNative(buildData, typeInfo, false, false)}*)args[{i}]);"); } // Invoke RPC - contents.AppendLine($" NetworkReplicator::EndInvokeRPC(obj, {typeInfo.NativeName}::TypeInitializer, StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length}), stream, targetIds);"); + contents.AppendLine($" return NetworkReplicator::EndInvokeRPC(obj, {typeInfo.NativeName}::TypeInitializer, StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length}), stream, targetIds);"); contents.AppendLine(" }"); } contents.AppendLine(); @@ -780,7 +780,7 @@ namespace Flax.Build.Plugins private static void GenerateCallINetworkSerializable(ref DotnetContext context, TypeDefinition type, string name, MethodDefinition method) { var m = new MethodDefinition(name, MethodAttributes.Public | MethodAttributes.HideBySig, context.VoidType); - m.Parameters.Add(new ParameterDefinition("stream", ParameterAttributes.None, context.NetworkStreamType)); + m.Parameters.Add(new ParameterDefinition("stream", ParameterAttributes.None, type.Module.ImportReference(context.NetworkStreamType))); ILProcessor il = m.Body.GetILProcessor(); il.Emit(OpCodes.Nop); il.Emit(OpCodes.Ldarg_0); @@ -1304,7 +1304,10 @@ namespace Flax.Build.Plugins il.Emit(jmp4); valueContext.Load(ref il); il.Emit(OpCodes.Ldloc, varStart + 1); // idx - il.Emit(OpCodes.Ldelem_Ref); + if (elementType.IsValueType) + il.Emit(OpCodes.Ldelem_Any, elementType); + else + il.Emit(OpCodes.Ldelem_Ref); il.Emit(OpCodes.Stloc, varStart + 2); // // Serialize item value @@ -1753,10 +1756,10 @@ namespace Flax.Build.Plugins if (jumpBodyEnd == null) throw new Exception("Missing IL Return op code in method " + method.Name); il.Emit(OpCodes.Ldloc, varsStart + 0); - il.Emit(OpCodes.Brfalse_S, jumpIf2Start); + il.Emit(OpCodes.Brfalse, jumpIf2Start); il.Emit(OpCodes.Ldloc, varsStart + 2); il.Emit(OpCodes.Ldc_I4_2); - il.Emit(OpCodes.Beq_S, jumpIfBodyStart); + il.Emit(OpCodes.Beq, jumpIfBodyStart); // || il.Emit(jumpIf2Start); il.Emit(OpCodes.Ldloc, varsStart + 1); @@ -1812,35 +1815,12 @@ namespace Flax.Build.Plugins else il.Emit(OpCodes.Ldnull); var endInvokeRPC = networkReplicatorType.Resolve().GetMethod("EndInvokeRPC", 5); + if (endInvokeRPC.ReturnType.FullName != boolType.FullName) + throw new Exception("Invalid EndInvokeRPC return type. Remove any 'Binaries' folders to force project recompile."); il.Emit(OpCodes.Call, module.ImportReference(endInvokeRPC)); - // if (server && networkMode == NetworkManagerMode.Client) return; - if (methodRPC.IsServer) - { - il.Emit(OpCodes.Nop); - il.Emit(OpCodes.Ldloc, varsStart + 2); - il.Emit(OpCodes.Ldc_I4_2); - var tmp = ilp.Create(OpCodes.Nop); - il.Emit(OpCodes.Beq_S, tmp); - il.Emit(OpCodes.Br, jumpBodyStart); - il.Emit(tmp); - //il.Emit(OpCodes.Ret); - il.Emit(OpCodes.Br, jumpBodyEnd); - } - - // if (client && networkMode == NetworkManagerMode.Server) return; - if (methodRPC.IsClient) - { - il.Emit(OpCodes.Nop); - il.Emit(OpCodes.Ldloc, varsStart + 2); - il.Emit(OpCodes.Ldc_I4_1); - var tmp = ilp.Create(OpCodes.Nop); - il.Emit(OpCodes.Beq_S, tmp); - il.Emit(OpCodes.Br, jumpBodyStart); - il.Emit(tmp); - //il.Emit(OpCodes.Ret); - il.Emit(OpCodes.Br, jumpBodyEnd); - } + // if (EndInvokeRPC) return + il.Emit(OpCodes.Brtrue, jumpBodyEnd); // Continue to original method body il.Emit(jumpBodyStart); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs index ed8222fb7..efce04776 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs @@ -88,7 +88,7 @@ namespace Flax.Build.Projects.VisualStudio csProjectFileContent.AppendLine(" net7.0"); csProjectFileContent.AppendLine(" disable"); - csProjectFileContent.AppendLine(" annotations"); + csProjectFileContent.AppendLine(string.Format(" {0}", baseConfiguration.TargetBuildOptions.ScriptingAPI.CSharpNullableReferences.ToString().ToLowerInvariant())); csProjectFileContent.AppendLine(" false"); csProjectFileContent.AppendLine(" false"); csProjectFileContent.AppendLine(" false"); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index fb5c90872..1955a57ba 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -547,6 +547,7 @@ namespace Flax.Build.Projects.VisualStudio if (project.Type == TargetType.DotNetCore) { var path = Path.Combine(Path.GetDirectoryName(project.Path), "Properties/launchSettings.json"); + path = Utilities.NormalizePath(path); if (profiles.ContainsKey(path)) profile.AppendLine(","); profile.AppendLine($" \"{project.BaseName}\": {{"); @@ -568,6 +569,7 @@ namespace Flax.Build.Projects.VisualStudio var folder = Path.GetDirectoryName(e.Key); if (!Directory.Exists(folder)) Directory.CreateDirectory(folder); + profile.Clear(); profile.AppendLine("{"); profile.AppendLine(" \"profiles\": {"); profile.AppendLine(e.Value); diff --git a/Source/Tools/Flax.Build/Utilities/MonoCecil.cs b/Source/Tools/Flax.Build/Utilities/MonoCecil.cs index 0301e33d2..188327931 100644 --- a/Source/Tools/Flax.Build/Utilities/MonoCecil.cs +++ b/Source/Tools/Flax.Build/Utilities/MonoCecil.cs @@ -249,7 +249,7 @@ namespace Flax.Build public static void GetType(this ModuleDefinition module, string fullName, out TypeReference type) { - if (!module.TryGetTypeReference(fullName, out type)) + //if (!module.TryGetTypeReference(fullName, out type)) // TODO: this seams to return 'FlaxEngine.Networking.NetworkManagerMode' as a Class instead of Enum { // Do manual search foreach (var a in module.AssemblyReferences)