From be204fd2f5a730724569b044f8266e8de86c85ee Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 19 Apr 2023 14:05:51 -0500 Subject: [PATCH 01/37] Ensure prefab actors static flags match parent flags or keeps it's own. --- Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index 2e9aab3b4..5c87cbff6 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -625,7 +625,16 @@ namespace FlaxEditor.SceneGraph.GUI { var item = _dragAssets.Objects[i]; var actor = item.OnEditorDrop(this); - actor.StaticFlags = spawnParent.StaticFlags; + if (spawnParent.GetType() != typeof(Scene)) + { + // Set all Actors static flags to match parents + List childActors = new List(); + GetActorsTree(childActors, actor); + foreach (var child in childActors) + { + child.StaticFlags = spawnParent.StaticFlags; + } + } actor.Name = item.ShortName; actor.Transform = spawnParent.Transform; Editor.Instance.SceneEditing.Spawn(actor, spawnParent, false); @@ -670,6 +679,16 @@ namespace FlaxEditor.SceneGraph.GUI return result; } + + private void GetActorsTree(List list, Actor a) + { + list.Add(a); + int cnt = a.ChildrenCount; + for (int i = 0; i < cnt; i++) + { + GetActorsTree(list, a.GetChild(i)); + } + } private bool ValidateDragActor(ActorNode actorNode) { From 76c6696eadf11c1ce4f92d2a09ed8889d26c1f0e Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 21 Apr 2023 08:36:56 -0500 Subject: [PATCH 02/37] Add saving and re-opening all active scenes between editor sessions. --- Source/Editor/Editor.cs | 53 +++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 4d2cf4346..bf51dd323 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -323,8 +323,16 @@ namespace FlaxEditor } case GeneralOptions.StartupSceneModes.LastOpened: { - if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName) && Guid.TryParse(lastSceneIdName, out var lastSceneId)) - Internal_LoadAsset(ref lastSceneId); + if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName)) + { + var lastSceneList = JsonSerializer.Deserialize>(lastSceneIdName); + + foreach (var scene in lastSceneList) + { + var lastScene = scene; + Internal_LoadAsset(ref lastScene); + } + } break; } } @@ -438,18 +446,25 @@ namespace FlaxEditor } case GeneralOptions.StartupSceneModes.LastOpened: { - if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName) && Guid.TryParse(lastSceneIdName, out var lastSceneId)) + if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName)) { - var lastScene = ContentDatabase.Find(lastSceneId); - if (lastScene is SceneItem) - { - Editor.Log("Loading last opened scene"); - Scene.OpenScene(lastSceneId); + var lastSceneList = JsonSerializer.Deserialize>(lastSceneIdName); - // Restore view - if (ProjectCache.TryGetCustomData(ProjectDataLastSceneSpawn, out var lastSceneSpawnName)) - Windows.EditWin.Viewport.ViewRay = JsonSerializer.Deserialize(lastSceneSpawnName); + foreach (var sceneId in lastSceneList) + { + var lastScene = ContentDatabase.Find(sceneId); + if (!(lastScene is SceneItem)) + continue; + + Editor.Log($"Loading last opened scene: {lastScene.ShortName}"); + if (sceneId == lastSceneList[0]) + Scene.OpenScene(sceneId); + else + Level.LoadSceneAsync(sceneId); } + // Restore view + if (ProjectCache.TryGetCustomData(ProjectDataLastSceneSpawn, out var lastSceneSpawnName)) + Windows.EditWin.Viewport.ViewRay = JsonSerializer.Deserialize(lastSceneSpawnName); } break; } @@ -664,9 +679,21 @@ namespace FlaxEditor // Cache last opened scene { - var lastSceneId = Level.ScenesCount > 0 ? Level.Scenes[0].ID : Guid.Empty; + List lastSceneIds = new List(); + if (Level.ScenesCount > 0) + { + foreach (var scene in Level.Scenes) + { + lastSceneIds.Add(scene.ID); + } + } + else + { + lastSceneIds.Add(Guid.Empty); + } + //var lastSceneId = Level.ScenesCount > 0 ? Level.Scenes[0].ID : Guid.Empty; var lastSceneSpawn = Windows.EditWin.Viewport.ViewRay; - ProjectCache.SetCustomData(ProjectDataLastScene, lastSceneId.ToString()); + ProjectCache.SetCustomData(ProjectDataLastScene, JsonSerializer.Serialize(lastSceneIds)); ProjectCache.SetCustomData(ProjectDataLastSceneSpawn, JsonSerializer.Serialize(lastSceneSpawn)); } From c93b41a88d839aac31876c791f23dafe281dc983 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 21 Apr 2023 10:34:40 -0500 Subject: [PATCH 03/37] Small Cleanup --- Source/Editor/Editor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index bf51dd323..1f4725ef7 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -691,7 +691,6 @@ namespace FlaxEditor { lastSceneIds.Add(Guid.Empty); } - //var lastSceneId = Level.ScenesCount > 0 ? Level.Scenes[0].ID : Guid.Empty; var lastSceneSpawn = Windows.EditWin.Viewport.ViewRay; ProjectCache.SetCustomData(ProjectDataLastScene, JsonSerializer.Serialize(lastSceneIds)); ProjectCache.SetCustomData(ProjectDataLastSceneSpawn, JsonSerializer.Serialize(lastSceneSpawn)); From 918140bc6dbc19c7b881ea100eb88018339b983f Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 22 Apr 2023 12:04:57 -0500 Subject: [PATCH 04/37] Change List to Guid[] when deserializing. --- Source/Editor/Editor.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 1f4725ef7..11e16f1ef 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -325,8 +325,7 @@ namespace FlaxEditor { if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName)) { - var lastSceneList = JsonSerializer.Deserialize>(lastSceneIdName); - + var lastSceneList = JsonSerializer.Deserialize(lastSceneIdName); foreach (var scene in lastSceneList) { var lastScene = scene; @@ -448,8 +447,7 @@ namespace FlaxEditor { if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName)) { - var lastSceneList = JsonSerializer.Deserialize>(lastSceneIdName); - + var lastSceneList = JsonSerializer.Deserialize(lastSceneIdName); foreach (var sceneId in lastSceneList) { var lastScene = ContentDatabase.Find(sceneId); @@ -677,7 +675,7 @@ namespace FlaxEditor // Start exit StateMachine.GoToState(); - // Cache last opened scene + // Cache last opened scenes { List lastSceneIds = new List(); if (Level.ScenesCount > 0) From 107bea9c2e323d15948beeb759683a388f8f6eac Mon Sep 17 00:00:00 2001 From: Ruan Lucas <79365912+RuanLucasGD@users.noreply.github.com> Date: Sat, 22 Apr 2023 13:38:54 -0400 Subject: [PATCH 05/37] implement: add "GetSubTags" to "Tags" --- Source/Engine/Level/Tags.cpp | 18 ++++++++++++++++++ Source/Engine/Level/Tags.h | 7 +++++++ 2 files changed, 25 insertions(+) diff --git a/Source/Engine/Level/Tags.cpp b/Source/Engine/Level/Tags.cpp index b9b3b1f79..b16928630 100644 --- a/Source/Engine/Level/Tags.cpp +++ b/Source/Engine/Level/Tags.cpp @@ -55,6 +55,24 @@ Tag Tags::Get(const StringView& tagName) return tag; } +Array Tags::GetSubTags(Tag parentTag) +{ + Array subTags = Array(); + + auto _parentTagName = parentTag.ToString(); + + for (int i = 0; i < Tags::List.Count(); i++) + { + auto& subTagName = Tags::List[i]; + if (subTagName.Contains(_parentTagName) && subTagName != _parentTagName) + { + subTags.Add(Tags::Get(subTagName)); + } + } + + return subTags; +} + bool Tags::HasTag(const Array& list, const Tag& tag) { if (tag.Index == 0) diff --git a/Source/Engine/Level/Tags.h b/Source/Engine/Level/Tags.h index 03b09a1fc..04ba76a23 100644 --- a/Source/Engine/Level/Tags.h +++ b/Source/Engine/Level/Tags.h @@ -92,6 +92,13 @@ API_CLASS(Static) class FLAXENGINE_API Tags /// The tag. API_FUNCTION() static Tag Get(const StringView& tagName); + /// + /// Get all subtags of the specific Tag + /// + /// + /// + API_FUNCTION() static Array GetSubTags(Tag tag); + public: /// /// Checks if the list of tags contains a given tag (including parent tags check). For example, HasTag({"A.B"}, "A") returns true, for exact check use HasTagExact. From 85cfc73bbfdbc736769ef006f7168985b341efc6 Mon Sep 17 00:00:00 2001 From: Ruan Lucas <79365912+RuanLucasGD@users.noreply.github.com> Date: Sat, 22 Apr 2023 14:51:40 -0400 Subject: [PATCH 06/37] clean code --- Source/Engine/Level/Tags.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Engine/Level/Tags.cpp b/Source/Engine/Level/Tags.cpp index b16928630..dd4bc331a 100644 --- a/Source/Engine/Level/Tags.cpp +++ b/Source/Engine/Level/Tags.cpp @@ -57,8 +57,7 @@ Tag Tags::Get(const StringView& tagName) Array Tags::GetSubTags(Tag parentTag) { - Array subTags = Array(); - + Array subTags; auto _parentTagName = parentTag.ToString(); for (int i = 0; i < Tags::List.Count(); i++) From 2c9541f6a0c085c0ef2659e2c3df23577e768ab4 Mon Sep 17 00:00:00 2001 From: Wiktor Kocielski Date: Mon, 24 Apr 2023 02:03:11 +0300 Subject: [PATCH 07/37] ViewportIconsRenderer::AddActorWithTexture addition --- .../Utilities/ViewportIconsRenderer.cpp | 72 +++++++++++++++++-- .../Editor/Utilities/ViewportIconsRenderer.h | 7 ++ 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/Source/Editor/Utilities/ViewportIconsRenderer.cpp b/Source/Editor/Utilities/ViewportIconsRenderer.cpp index 9b7f4979b..a5ed0356f 100644 --- a/Source/Editor/Utilities/ViewportIconsRenderer.cpp +++ b/Source/Editor/Utilities/ViewportIconsRenderer.cpp @@ -47,6 +47,8 @@ AssetReference CustomTextureMaterial; ModelInstanceEntries InstanceBuffers[static_cast(IconTypes::MAX)]; Dictionary ActorTypeToIconType; Dictionary> ActorTypeToTexture; +Dictionary, AssetReference> ActorToTexture; +Dictionary, AssetReference> TextureToMaterial; class ViewportIconsRendererService : public EngineService { @@ -103,10 +105,18 @@ void ViewportIconsRenderer::AddActor(Actor* actor) actor->GetSceneRendering()->AddViewportIcon(actor); } +void ViewportIconsRenderer::AddActorWithTexture(Actor* actor, Texture* iconTexture) +{ + CHECK(actor && actor->GetScene() && iconTexture); + ActorToTexture[actor] = iconTexture; + actor->GetSceneRendering()->AddViewportIcon(actor); +} + void ViewportIconsRenderer::RemoveActor(Actor* actor) { CHECK(actor && actor->GetScene()); actor->GetSceneRendering()->RemoveViewportIcon(actor); + ActorToTexture.Remove(actor); } void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene* scene, Mesh::DrawInfo& draw) @@ -128,7 +138,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene ScriptingTypeHandle typeHandle = icon->GetTypeHandle(); draw.Buffer = nullptr; - if (ActorTypeToTexture.TryGet(typeHandle, texture)) + if (ActorToTexture.TryGet(icon, texture) || ActorTypeToTexture.TryGet(typeHandle, texture)) { // Use custom texture draw.Buffer = &InstanceBuffers[static_cast(IconTypes::CustomTexture)]; @@ -137,9 +147,20 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene // Lazy-init (use in-built icon material with custom texture) draw.Buffer->Setup(1); draw.Buffer->At(0).ReceiveDecals = false; - draw.Buffer->At(0).Material = InstanceBuffers[0][0].Material->CreateVirtualInstance(); + draw.Buffer->At(0).ShadowsMode = ShadowsCastingMode::None; } - draw.Buffer->At(0).Material->SetParameterValue(TEXT("Image"), Variant(texture)); + + AssetReference material; + + if (!TextureToMaterial.TryGet(texture, material)) + { + // Create custom material per custom texture + TextureToMaterial[texture] = InstanceBuffers[0][0].Material->CreateVirtualInstance(); + TextureToMaterial[texture]->SetParameterValue(TEXT("Image"), Variant(texture)); + material = TextureToMaterial[texture]; + } + + draw.Buffer->At(0).Material = material; } else if (ActorTypeToIconType.TryGet(typeHandle, iconType)) { @@ -170,6 +191,8 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor Matrix m1, m2, world; BoundingSphere sphere(actor->GetPosition() - renderContext.View.Origin, ICON_RADIUS); IconTypes iconType; + AssetReference texture; + if (frustum.Intersects(sphere) && ActorTypeToIconType.TryGet(actor->GetTypeHandle(), iconType)) { // Create world matrix @@ -182,7 +205,39 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor // Draw icon GeometryDrawStateData drawState; draw.DrawState = &drawState; - draw.Buffer = &InstanceBuffers[static_cast(iconType)]; + + // Support custom icons through types, but not onces that were added through actors, + // since they cant register while in prefab view anyway + if (ActorTypeToTexture.TryGet(actor->GetTypeHandle(), texture)) + { + // Use custom texture + draw.Buffer = &InstanceBuffers[static_cast(IconTypes::CustomTexture)]; + if (draw.Buffer->Count() == 0) + { + // Lazy-init (use in-built icon material with custom texture) + draw.Buffer->Setup(1); + draw.Buffer->At(0).ReceiveDecals = false; + draw.Buffer->At(0).ShadowsMode = ShadowsCastingMode::None; + } + + AssetReference material; + + if (!TextureToMaterial.TryGet(texture, material)) + { + // Create custom material per custom texture + TextureToMaterial[texture] = InstanceBuffers[0][0].Material->CreateVirtualInstance(); + TextureToMaterial[texture]->SetParameterValue(TEXT("Image"), Variant(texture)); + material = TextureToMaterial[texture]; + } + + draw.Buffer->At(0).Material = material; + } + else + { + // Use predefined material + draw.Buffer = &InstanceBuffers[static_cast(iconType)]; + } + draw.World = &world; draw.Bounds = sphere; QuadModel->Draw(renderContext, draw); @@ -196,9 +251,10 @@ bool ViewportIconsRendererService::Init() { QuadModel = Content::LoadAsyncInternal(TEXT("Engine/Models/Quad")); #define INIT(type, path) \ - InstanceBuffers[static_cast(IconTypes::type)].Setup(1); \ - InstanceBuffers[static_cast(IconTypes::type)][0].ReceiveDecals = false; \ - InstanceBuffers[static_cast(IconTypes::type)][0].Material = Content::LoadAsyncInternal(TEXT(path)) + InstanceBuffers[static_cast(IconTypes::type)].Setup(1); \ + InstanceBuffers[static_cast(IconTypes::type)][0].ReceiveDecals = false; \ + InstanceBuffers[static_cast(IconTypes::type)][0].ShadowsMode = ShadowsCastingMode::None; \ + InstanceBuffers[static_cast(IconTypes::type)][0].Material = Content::LoadAsyncInternal(TEXT(path)) INIT(PointLight, "Editor/Icons/PointLight"); INIT(DirectionalLight, "Editor/Icons/DirectionalLight"); INIT(EnvironmentProbe, "Editor/Icons/EnvironmentProbe"); @@ -236,4 +292,6 @@ void ViewportIconsRendererService::Dispose() for (int32 i = 0; i < ARRAY_COUNT(InstanceBuffers); i++) InstanceBuffers[i].Release(); ActorTypeToIconType.Clear(); + ActorToTexture.Clear(); + TextureToMaterial.Clear(); } diff --git a/Source/Editor/Utilities/ViewportIconsRenderer.h b/Source/Editor/Utilities/ViewportIconsRenderer.h index 509252795..4062c2642 100644 --- a/Source/Editor/Utilities/ViewportIconsRenderer.h +++ b/Source/Editor/Utilities/ViewportIconsRenderer.h @@ -37,6 +37,13 @@ public: /// The actor to register for icon drawing. API_FUNCTION() static void AddActor(Actor* actor); + /// + /// Adds actor to the viewport icon rendering. + /// + /// The actor to register for icon drawing. + /// The icon texture to draw. + API_FUNCTION() static void AddActorWithTexture(Actor* actor, Texture* iconTexture); + /// /// Removes actor from the viewport icon rendering. /// From 5178caeda6f418b4b5c99386434f368c8d9892ed Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 24 Apr 2023 17:27:43 -0500 Subject: [PATCH 08/37] Add inputs to Random Range visject float nodes --- Source/Editor/Surface/Archetypes/Particles.cs | 44 ++++--------------- 1 file changed, 8 insertions(+), 36 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Particles.cs b/Source/Editor/Surface/Archetypes/Particles.cs index 97be9a20d..dd861d4af 100644 --- a/Source/Editor/Surface/Archetypes/Particles.cs +++ b/Source/Editor/Surface/Archetypes/Particles.cs @@ -730,12 +730,8 @@ namespace FlaxEditor.Surface.Archetypes Elements = new[] { NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0), - - NodeElementArchetype.Factory.Text(0, 0, "Min", 30.0f, 18.0f), - NodeElementArchetype.Factory.Float(30, 0, 0), - - NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY, "Max", 30.0f, 18.0f), - NodeElementArchetype.Factory.Float(30, Surface.Constants.LayoutOffsetY, 1), + NodeElementArchetype.Factory.Input(0, "Min", true, typeof(float), 1, 0), + NodeElementArchetype.Factory.Input(1, "Max", true, typeof(float), 2, 1), } }, new NodeArchetype @@ -753,14 +749,8 @@ namespace FlaxEditor.Surface.Archetypes Elements = new[] { NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float2), 0), - - NodeElementArchetype.Factory.Text(0, 0, "Min", 30.0f, 18.0f), - NodeElementArchetype.Factory.Vector_X(30, 0, 0), - NodeElementArchetype.Factory.Vector_Y(83, 0, 0), - - NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY, "Max", 30.0f, 18.0f), - NodeElementArchetype.Factory.Vector_X(30, Surface.Constants.LayoutOffsetY, 1), - NodeElementArchetype.Factory.Vector_Y(83, Surface.Constants.LayoutOffsetY, 1), + NodeElementArchetype.Factory.Input(0, "Min", true, typeof(Float2), 1, 0), + NodeElementArchetype.Factory.Input(1, "Max", true, typeof(Float2), 2, 1), } }, new NodeArchetype @@ -778,16 +768,8 @@ namespace FlaxEditor.Surface.Archetypes Elements = new[] { NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 0), - - NodeElementArchetype.Factory.Text(0, 0, "Min", 30.0f, 18.0f), - NodeElementArchetype.Factory.Vector_X(30, 0, 0), - NodeElementArchetype.Factory.Vector_Y(83, 0, 0), - NodeElementArchetype.Factory.Vector_Z(136, 0, 0), - - NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY, "Max", 30.0f, 18.0f), - NodeElementArchetype.Factory.Vector_X(30, Surface.Constants.LayoutOffsetY, 1), - NodeElementArchetype.Factory.Vector_Y(83, Surface.Constants.LayoutOffsetY, 1), - NodeElementArchetype.Factory.Vector_Z(136, Surface.Constants.LayoutOffsetY, 1), + NodeElementArchetype.Factory.Input(0, "Min", true, typeof(Float3), 1, 0), + NodeElementArchetype.Factory.Input(1, "Max", true, typeof(Float3), 2, 1), } }, new NodeArchetype @@ -805,18 +787,8 @@ namespace FlaxEditor.Surface.Archetypes Elements = new[] { NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float4), 0), - - NodeElementArchetype.Factory.Text(0, 0, "Min", 30.0f, 18.0f), - NodeElementArchetype.Factory.Vector_X(30, 0, 0), - NodeElementArchetype.Factory.Vector_Y(83, 0, 0), - NodeElementArchetype.Factory.Vector_Z(136, 0, 0), - NodeElementArchetype.Factory.Vector_W(189, 0, 0), - - NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY, "Max", 30.0f, 18.0f), - NodeElementArchetype.Factory.Vector_X(30, Surface.Constants.LayoutOffsetY, 1), - NodeElementArchetype.Factory.Vector_Y(83, Surface.Constants.LayoutOffsetY, 1), - NodeElementArchetype.Factory.Vector_Z(136, Surface.Constants.LayoutOffsetY, 1), - NodeElementArchetype.Factory.Vector_W(189, Surface.Constants.LayoutOffsetY, 1), + NodeElementArchetype.Factory.Input(0, "Min", true, typeof(Float4), 1, 0), + NodeElementArchetype.Factory.Input(1, "Max", true, typeof(Float4), 2, 1), } }, From c920b75142d99a3cccbbec622d0c35de72a76041 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 24 Apr 2023 17:47:19 -0500 Subject: [PATCH 09/37] Fix collision assets creation in non asset folders. --- Source/Editor/Content/Proxy/CollisionDataProxy.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Content/Proxy/CollisionDataProxy.cs b/Source/Editor/Content/Proxy/CollisionDataProxy.cs index 55e8c6327..e4a2381f0 100644 --- a/Source/Editor/Content/Proxy/CollisionDataProxy.cs +++ b/Source/Editor/Content/Proxy/CollisionDataProxy.cs @@ -141,7 +141,13 @@ namespace FlaxEditor.Content }); }; var initialName = (modelItem?.ShortName ?? Path.GetFileNameWithoutExtension(model.Path)) + " Collision"; - Editor.Instance.Windows.ContentWin.NewItem(this, null, create, initialName, withRenaming); + + // If folder can not have assets then move to folder with the model and create asset + var contentWin = Editor.Instance.Windows.ContentWin; + if (!contentWin.CurrentViewFolder.CanHaveAssets) + contentWin.Navigate(modelItem?.ParentFolder?.Node); + + contentWin.NewItem(this, null, create, initialName, withRenaming); } } } From 89d34650c0e0af29e0a27289838662ed272586b0 Mon Sep 17 00:00:00 2001 From: Wiktor Kocielski Date: Wed, 26 Apr 2023 00:28:51 +0300 Subject: [PATCH 10/37] Fix LayersMatrixEditor --- Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs b/Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs index 494663149..fa104b893 100644 --- a/Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs @@ -45,7 +45,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var upperRightCell = new VerticalPanel { ClipChildren = false, - Pivot = new Float2(0.0f, 0.0f), + Pivot = new Float2(0.00001f, 0.0f), Offset = new Float2(-labelsWidth, 0), Rotation = -90, Spacing = 0, From 6fd636b421856c04c7c2a1d025d946aec2e29baa Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 28 Apr 2023 09:33:24 -0500 Subject: [PATCH 11/37] Change to serialize list as array --- Source/Editor/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 11e16f1ef..498c8cbd9 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -690,7 +690,7 @@ namespace FlaxEditor lastSceneIds.Add(Guid.Empty); } var lastSceneSpawn = Windows.EditWin.Viewport.ViewRay; - ProjectCache.SetCustomData(ProjectDataLastScene, JsonSerializer.Serialize(lastSceneIds)); + ProjectCache.SetCustomData(ProjectDataLastScene, JsonSerializer.Serialize(lastSceneIds.ToArray())); ProjectCache.SetCustomData(ProjectDataLastSceneSpawn, JsonSerializer.Serialize(lastSceneSpawn)); } From 315df12fbe5b9b7e2908dbe1d3aeff37ad18430b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 May 2023 14:34:32 +0200 Subject: [PATCH 12/37] Favor passing `Tag` as value since it's just `uint32` under the hood --- Source/Engine/Level/Tags.cpp | 4 ++-- Source/Engine/Level/Tags.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Level/Tags.cpp b/Source/Engine/Level/Tags.cpp index b9b3b1f79..7f287022f 100644 --- a/Source/Engine/Level/Tags.cpp +++ b/Source/Engine/Level/Tags.cpp @@ -55,7 +55,7 @@ Tag Tags::Get(const StringView& tagName) return tag; } -bool Tags::HasTag(const Array& list, const Tag& tag) +bool Tags::HasTag(const Array& list, const Tag tag) { if (tag.Index == 0) return false; @@ -69,7 +69,7 @@ bool Tags::HasTag(const Array& list, const Tag& tag) return false; } -bool Tags::HasTagExact(const Array& list, const Tag& tag) +bool Tags::HasTagExact(const Array& list, const Tag tag) { if (tag.Index == 0) return false; diff --git a/Source/Engine/Level/Tags.h b/Source/Engine/Level/Tags.h index 03b09a1fc..b15b5087f 100644 --- a/Source/Engine/Level/Tags.h +++ b/Source/Engine/Level/Tags.h @@ -99,7 +99,7 @@ public: /// The tags list to use. /// The tag to check. /// True if given tag is contained by the list of tags. Returns false for empty list. - static bool HasTag(const Array& list, const Tag& tag); + static bool HasTag(const Array& list, const Tag tag); /// /// Checks if the list of tags contains a given tag (exact match). For example, HasTag({"A.B"}, "A") returns false, for parents check use HasTag. @@ -107,7 +107,7 @@ public: /// The tags list to use. /// The tag to check. /// True if given tag is contained by the list of tags. Returns false for empty list. - static bool HasTagExact(const Array& list, const Tag& tag); + static bool HasTagExact(const Array& list, const Tag tag); /// /// Checks if the list of tags contains any of the given tags (including parent tags check). For example, HasAny({"A.B", "C"}, {"A"}) returns true, for exact check use HasAnyExact. From 37e8fa7b767916c764567a877c4af2803d4d5cb2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 May 2023 14:54:42 +0200 Subject: [PATCH 13/37] Fix creating localization table on blank project #1060 --- .../CustomEditors/Dedicated/LocalizationSettingsEditor.cs | 5 ++++- Source/Editor/Windows/Assets/JsonAssetWindow.cs | 4 ++++ Source/Engine/Content/Content.cpp | 5 ----- Source/Engine/Content/JsonAsset.cs | 5 +++++ Source/Engine/Localization/LocalizedStringTable.cpp | 1 + 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs b/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs index e784048af..071810ffa 100644 --- a/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs @@ -145,7 +145,10 @@ namespace FlaxEditor.CustomEditors.Dedicated else { // No localization so initialize with empty table - var path = Path.Combine(Path.Combine(Path.GetDirectoryName(GameSettings.Load().Localization.Path), "Localization", culture.Name + ".json")); + var folder = Path.Combine(Path.GetDirectoryName(GameSettings.Load().Localization.Path), "Localization"); + if (!Directory.Exists(folder)) + Directory.CreateDirectory(folder); + var path = Path.Combine(Path.Combine(folder, culture.Name + ".json")); var table = FlaxEngine.Content.CreateVirtualAsset(); table.Locale = culture.Name; if (!table.Save(path)) diff --git a/Source/Editor/Windows/Assets/JsonAssetWindow.cs b/Source/Editor/Windows/Assets/JsonAssetWindow.cs index ecc68581c..e6606f35f 100644 --- a/Source/Editor/Windows/Assets/JsonAssetWindow.cs +++ b/Source/Editor/Windows/Assets/JsonAssetWindow.cs @@ -126,6 +126,10 @@ namespace FlaxEditor.Windows.Assets _presenter.NoSelectionText = "Failed to load asset. See log for more. " + ex.Message.Replace('\n', ' '); } } + else if (string.IsNullOrEmpty(dataTypeName)) + { + _presenter.NoSelectionText = "Empty data type."; + } else { _presenter.NoSelectionText = string.Format("Missing type '{0}'.", dataTypeName); diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index a30fa7b78..e58aa458d 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -1002,17 +1002,12 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& } #if ASSETS_LOADING_EXTRA_VERIFICATION - - // Ensure we have valid asset info - ASSERT(assetInfo.TypeName.HasChars() && assetInfo.Path.HasChars()); - // Check if file exists if (!FileSystem::FileExists(assetInfo.Path)) { LOG(Error, "Cannot find file '{0}'", assetInfo.Path); return nullptr; } - #endif // Find asset factory based in its type diff --git a/Source/Engine/Content/JsonAsset.cs b/Source/Engine/Content/JsonAsset.cs index 3278a3377..afd83bafd 100644 --- a/Source/Engine/Content/JsonAsset.cs +++ b/Source/Engine/Content/JsonAsset.cs @@ -35,6 +35,11 @@ namespace FlaxEngine return null; var dataTypeName = DataTypeName; + if (string.IsNullOrEmpty(dataTypeName)) + { + Debug.LogError(string.Format("Missing typename of data in Json asset '{0}'.", Path), this); + return null; + } var assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { diff --git a/Source/Engine/Localization/LocalizedStringTable.cpp b/Source/Engine/Localization/LocalizedStringTable.cpp index 74c9b6774..e14328f89 100644 --- a/Source/Engine/Localization/LocalizedStringTable.cpp +++ b/Source/Engine/Localization/LocalizedStringTable.cpp @@ -15,6 +15,7 @@ REGISTER_JSON_ASSET(LocalizedStringTable, "FlaxEngine.LocalizedStringTable", tru LocalizedStringTable::LocalizedStringTable(const SpawnParams& params, const AssetInfo* info) : JsonAssetBase(params, info) { + DataTypeName = TypeName; } void LocalizedStringTable::AddString(const StringView& id, const StringView& value) From 285a824858670f9d52dc92327819a1b010169183 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 May 2023 15:08:53 +0200 Subject: [PATCH 14/37] Fix crash when removing particle emitter from system that has old parameter override #1044 --- Source/Engine/Particles/ParticleSystem.cpp | 31 +++++++--------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/Source/Engine/Particles/ParticleSystem.cpp b/Source/Engine/Particles/ParticleSystem.cpp index b04dfe0c0..6f63101b2 100644 --- a/Source/Engine/Particles/ParticleSystem.cpp +++ b/Source/Engine/Particles/ParticleSystem.cpp @@ -212,6 +212,12 @@ Asset::LoadResult ParticleSystem::load() int32 version; stream.ReadInt32(&version); +#if USE_EDITOR + // Skip unused parameters +#define SKIP_UNUSED_PARAM_OVERRIDE() if (key.First < 0 || key.First >= Emitters.Count() || Emitters[key.First] == nullptr || Emitters[key.First]->Graph.GetParameter(key.Second) == nullptr) continue +#else +#define SKIP_UNUSED_PARAM_OVERRIDE() +#endif switch (version) { case 1: @@ -281,13 +287,7 @@ Asset::LoadResult ParticleSystem::load() stream.ReadInt32(&key.First); stream.Read(key.Second); stream.ReadCommonValue(&value); - -#if USE_EDITOR - // Skip unused parameters - if (key.First < 0 || key.First >= Emitters.Count() || Emitters[key.First]->Graph.GetParameter(key.Second) == nullptr) - continue; -#endif - + SKIP_UNUSED_PARAM_OVERRIDE(); EmittersParametersOverrides.Add(key, Variant(value)); } } @@ -361,13 +361,7 @@ Asset::LoadResult ParticleSystem::load() stream.ReadInt32(&key.First); stream.Read(key.Second); stream.ReadCommonValue(&value); - -#if USE_EDITOR - // Skip unused parameters - if (key.First < 0 || key.First >= Emitters.Count() || Emitters[key.First]->Graph.GetParameter(key.Second) == nullptr) - continue; -#endif - + SKIP_UNUSED_PARAM_OVERRIDE(); EmittersParametersOverrides[key] = Variant(value); } } @@ -440,13 +434,7 @@ Asset::LoadResult ParticleSystem::load() stream.ReadInt32(&key.First); stream.Read(key.Second); stream.ReadVariant(&value); - -#if USE_EDITOR - // Skip unused parameters - if (key.First < 0 || key.First >= Emitters.Count() || Emitters[key.First]->Graph.GetParameter(key.Second) == nullptr) - continue; -#endif - + SKIP_UNUSED_PARAM_OVERRIDE(); EmittersParametersOverrides[key] = value; } } @@ -457,6 +445,7 @@ Asset::LoadResult ParticleSystem::load() LOG(Warning, "Unknown timeline version {0}.", version); return LoadResult::InvalidData; } +#undef SKIP_UNUSED_PARAM_OVERRIDE #if !BUILD_RELEASE _debugName = StringUtils::GetFileNameWithoutExtension(GetPath()); From 86044fcc05afddbfd487e925c2560e1d383e66d6 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 5 May 2023 08:17:07 -0500 Subject: [PATCH 15/37] Change logic for Random Rang particle nodes --- .../ParticleEmitterGraph.GPU.Particles.cpp | 24 +++++++++---------- Source/Engine/Visject/VisjectGraph.cpp | 16 ++++++------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp index 597c7695c..9b59ba5b9 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp @@ -388,33 +388,33 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va // Random Float Range case 213: { - auto& a = node->Values[0].AsFloat; - auto& b = node->Values[1].AsFloat; - value = writeLocal(VariantType::Float, String::Format(TEXT("lerp({0}, {1}, RAND)"), a, b), node); + auto a = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat(); + auto b = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat(); + value = writeLocal(VariantType::Float, String::Format(TEXT("lerp({0}, {1}, RAND)"), a.Value, b.Value), node); break; } // Random Vector2 Range case 214: { - auto& a = node->Values[0].AsFloat2(); - auto& b = node->Values[1].AsFloat2(); - value = writeLocal(VariantType::Float2, String::Format(TEXT("float2(lerp({0}, {1}, RAND), lerp({2}, {3}, RAND))"), a.X, b.X, a.Y, b.Y), node); + auto a = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat2(); + auto b = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat2(); + value = writeLocal(VariantType::Float2, String::Format(TEXT("float2(lerp({0}.x, {1}.x, RAND), lerp({0}.y, {1}.y, RAND))"), a.Value, b.Value), node); break; } // Random Vector3 Range case 215: { - auto& a = node->Values[0].AsFloat3(); - auto& b = node->Values[1].AsFloat3(); - value = writeLocal(VariantType::Float3, String::Format(TEXT("float3(lerp({0}, {1}, RAND), lerp({2}, {3}, RAND), lerp({4}, {5}, RAND))"), a.X, b.X, a.Y, b.Y, a.Z, b.Z), node); + auto a = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat3(); + auto b = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat3(); + value = writeLocal(VariantType::Float3, String::Format(TEXT("float3(lerp({0}.x, {1}.x, RAND), lerp({0}.y, {1}.y, RAND), lerp({0}.z, {1}.z, RAND))"), a.Value, b.Value), node); break; } // Random Vector4 Range case 216: { - auto& a = node->Values[0].AsFloat4(); - auto& b = node->Values[1].AsFloat4(); - value = writeLocal(VariantType::Float4, String::Format(TEXT("float4(lerp({0}, {1}, RAND), lerp({2}, {3}, RAND), lerp({4}, {5}, RAND), lerp({6}, {7}, RAND))"), a.X, b.X, a.Y, b.Y, a.Z, b.Z, a.W, b.W), node); + auto a = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat4(); + auto b = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat4(); + value = writeLocal(VariantType::Float4, String::Format(TEXT("float4(lerp({0}.x, {1}.x, RAND), lerp({0}.y, {1}.y, RAND), lerp({0}.z, {1}.z, RAND), lerp({0}.w, {1}.w, RAND))"), a.Value, b.Value), node); break; } // Particle Emitter Function diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp index 4e067e61c..5559cd713 100644 --- a/Source/Engine/Visject/VisjectGraph.cpp +++ b/Source/Engine/Visject/VisjectGraph.cpp @@ -1273,16 +1273,16 @@ void VisjectExecutor::ProcessGroupParticles(Box* box, Node* node, Value& value) // Random Float Range case 213: { - auto& a = node->Values[0].AsFloat; - auto& b = node->Values[1].AsFloat; + auto a = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat; + auto b = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat; value = Math::Lerp(a, b, RAND); break; } // Random Vector2 Range case 214: { - auto a = (Float2)node->Values[0]; - auto b = (Float2)node->Values[1]; + auto a = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat2(); + auto b = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat2(); value = Float2( Math::Lerp(a.X, b.X, RAND), Math::Lerp(a.Y, b.Y, RAND) @@ -1292,8 +1292,8 @@ void VisjectExecutor::ProcessGroupParticles(Box* box, Node* node, Value& value) // Random Vector3 Range case 215: { - auto a = (Float3)node->Values[0]; - auto b = (Float3)node->Values[1]; + auto a = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat3(); + auto b = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat3(); value = Float3( Math::Lerp(a.X, b.X, RAND), Math::Lerp(a.Y, b.Y, RAND), @@ -1304,8 +1304,8 @@ void VisjectExecutor::ProcessGroupParticles(Box* box, Node* node, Value& value) // Random Vector4 Range case 216: { - auto a = (Float4)node->Values[0]; - auto b = (Float4)node->Values[1]; + auto a = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat4(); + auto b = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat4(); value = Float4( Math::Lerp(a.X, b.X, RAND), Math::Lerp(a.Y, b.Y, RAND), From 42a9eaf72e833c280921f1ba9e815a850e3656ec Mon Sep 17 00:00:00 2001 From: Wiktor Kocielski Date: Fri, 5 May 2023 16:29:12 +0300 Subject: [PATCH 16/37] Make DebugDraw render lines with unlit PS --- Source/Engine/Debug/DebugDraw.cpp | 6 ++++-- Source/Shaders/DebugDraw.shader | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index a1dfa0b61..d64494125 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -632,9 +632,10 @@ void DebugDrawService::Update() desc.VS = shader->GetVS("VS"); // Default - desc.PS = shader->GetPS("PS"); + desc.PS = shader->GetPS("PSUnlit"); desc.PrimitiveTopologyType = PrimitiveTopologyType::Line; failed |= DebugDrawPsLinesDefault.Create(desc); + desc.PS = shader->GetPS("PS"); desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle; failed |= DebugDrawPsTrianglesDefault.Create(desc); desc.Wireframe = true; @@ -642,9 +643,10 @@ void DebugDrawService::Update() // Depth Test desc.Wireframe = false; - desc.PS = shader->GetPS("PS_DepthTest"); + desc.PS = shader->GetPS("PS_DepthTestUnlit"); desc.PrimitiveTopologyType = PrimitiveTopologyType::Line; failed |= DebugDrawPsLinesDepthTest.Create(desc); + desc.PS = shader->GetPS("PS_DepthTest"); desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle; failed |= DebugDrawPsTrianglesDepthTest.Create(desc); desc.Wireframe = true; diff --git a/Source/Shaders/DebugDraw.shader b/Source/Shaders/DebugDraw.shader index 34d1b9108..8782abe4b 100644 --- a/Source/Shaders/DebugDraw.shader +++ b/Source/Shaders/DebugDraw.shader @@ -54,9 +54,22 @@ float4 PS(VS2PS input) : SV_Target return PerformFakeLighting(input.Position, input.Color); } +META_PS(true, FEATURE_LEVEL_ES2) +float4 PSUnlit(VS2PS input) : SV_Target +{ + return input.Color; +} + META_PS(true, FEATURE_LEVEL_ES2) float4 PS_DepthTest(VS2PS input) : SV_Target { PerformDepthTest(input.Position); return PerformFakeLighting(input.Position, input.Color); } + +META_PS(true, FEATURE_LEVEL_ES2) +float4 PS_DepthTestUnlit(VS2PS input) : SV_Target +{ + PerformDepthTest(input.Position); + return input.Color; +} From 95d798f49e8f94eeb3eca257d8e35c9532132531 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 5 May 2023 08:30:17 -0500 Subject: [PATCH 17/37] Standardize to changing the folder for any proxy that can not be created in the current view. --- Source/Editor/Content/Proxy/CollisionDataProxy.cs | 8 +------- Source/Editor/Windows/ContentWindow.cs | 5 +++++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Source/Editor/Content/Proxy/CollisionDataProxy.cs b/Source/Editor/Content/Proxy/CollisionDataProxy.cs index e4a2381f0..55e8c6327 100644 --- a/Source/Editor/Content/Proxy/CollisionDataProxy.cs +++ b/Source/Editor/Content/Proxy/CollisionDataProxy.cs @@ -141,13 +141,7 @@ namespace FlaxEditor.Content }); }; var initialName = (modelItem?.ShortName ?? Path.GetFileNameWithoutExtension(model.Path)) + " Collision"; - - // If folder can not have assets then move to folder with the model and create asset - var contentWin = Editor.Instance.Windows.ContentWin; - if (!contentWin.CurrentViewFolder.CanHaveAssets) - contentWin.Navigate(modelItem?.ParentFolder?.Node); - - contentWin.NewItem(this, null, create, initialName, withRenaming); + Editor.Instance.Windows.ContentWin.NewItem(this, null, create, initialName, withRenaming); } } } diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 43fd6b648..6a59cb218 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -656,6 +656,11 @@ namespace FlaxEditor.Windows throw new ArgumentNullException(nameof(proxy)); string name = initialName ?? proxy.NewItemName; + + // If the proxy can not be created in the current folder, then navigate to the content folder + if (!proxy.CanCreate(CurrentViewFolder)) + Navigate(Editor.Instance.ContentDatabase.Game.Content); + ContentFolder parentFolder = CurrentViewFolder; string parentFolderPath = parentFolder.Path; From 404340b02eaec549441f2d6858a904f1114fe778 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 May 2023 15:58:18 +0200 Subject: [PATCH 18/37] Improve #1025 --- Source/Editor/Editor.cs | 136 ++++++++++++++++++++-------------------- 1 file changed, 69 insertions(+), 67 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 498c8cbd9..2bf2a6922 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -307,6 +307,7 @@ namespace FlaxEditor _areModulesInited = true; // Preload initial scene asset + try { var startupSceneMode = Options.Options.General.StartupSceneMode; if (startupSceneMode == GeneralOptions.StartupSceneModes.LastOpened && !ProjectCache.HasCustomData(ProjectDataLastScene)) @@ -325,8 +326,8 @@ namespace FlaxEditor { if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName)) { - var lastSceneList = JsonSerializer.Deserialize(lastSceneIdName); - foreach (var scene in lastSceneList) + var lastScenes = JsonSerializer.Deserialize(lastSceneIdName); + foreach (var scene in lastScenes) { var lastScene = scene; Internal_LoadAsset(ref lastScene); @@ -336,6 +337,10 @@ namespace FlaxEditor } } } + catch (Exception ex) + { + // Ignore errors + } InitializationStart?.Invoke(); @@ -408,64 +413,69 @@ namespace FlaxEditor } // Load scene - - // scene cmd line argument - var scene = ContentDatabase.Find(_startupSceneCmdLine); - if (scene is SceneItem) + try { - Editor.Log("Loading scene specified in command line"); - Scene.OpenScene(_startupSceneCmdLine); - return; - } - - // if no scene cmd line argument is provided - var startupSceneMode = Options.Options.General.StartupSceneMode; - if (startupSceneMode == GeneralOptions.StartupSceneModes.LastOpened && !ProjectCache.HasCustomData(ProjectDataLastScene)) - { - // Fallback to default project scene if nothing saved in the cache - startupSceneMode = GeneralOptions.StartupSceneModes.ProjectDefault; - } - switch (startupSceneMode) - { - case GeneralOptions.StartupSceneModes.ProjectDefault: - { - if (string.IsNullOrEmpty(GameProject.DefaultScene)) - break; - JsonSerializer.ParseID(GameProject.DefaultScene, out var defaultSceneId); - var defaultScene = ContentDatabase.Find(defaultSceneId); - if (defaultScene is SceneItem) + // Scene cmd line argument + var scene = ContentDatabase.Find(_startupSceneCmdLine); + if (scene is SceneItem) { - Editor.Log("Loading default project scene"); - Scene.OpenScene(defaultSceneId); - - // Use spawn point - Windows.EditWin.Viewport.ViewRay = GameProject.DefaultSceneSpawn; + Editor.Log("Loading scene specified in command line"); + Scene.OpenScene(_startupSceneCmdLine); + return; } - break; - } - case GeneralOptions.StartupSceneModes.LastOpened: - { - if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName)) + var startupSceneMode = Options.Options.General.StartupSceneMode; + if (startupSceneMode == GeneralOptions.StartupSceneModes.LastOpened && !ProjectCache.HasCustomData(ProjectDataLastScene)) { - var lastSceneList = JsonSerializer.Deserialize(lastSceneIdName); - foreach (var sceneId in lastSceneList) + // Fallback to default project scene if nothing saved in the cache + startupSceneMode = GeneralOptions.StartupSceneModes.ProjectDefault; + } + switch (startupSceneMode) + { + case GeneralOptions.StartupSceneModes.ProjectDefault: + { + if (string.IsNullOrEmpty(GameProject.DefaultScene)) + break; + JsonSerializer.ParseID(GameProject.DefaultScene, out var defaultSceneId); + var defaultScene = ContentDatabase.Find(defaultSceneId); + if (defaultScene is SceneItem) { - var lastScene = ContentDatabase.Find(sceneId); - if (!(lastScene is SceneItem)) - continue; - - Editor.Log($"Loading last opened scene: {lastScene.ShortName}"); - if (sceneId == lastSceneList[0]) - Scene.OpenScene(sceneId); - else - Level.LoadSceneAsync(sceneId); + Editor.Log("Loading default project scene"); + Scene.OpenScene(defaultSceneId); + + // Use spawn point + Windows.EditWin.Viewport.ViewRay = GameProject.DefaultSceneSpawn; } - // Restore view - if (ProjectCache.TryGetCustomData(ProjectDataLastSceneSpawn, out var lastSceneSpawnName)) - Windows.EditWin.Viewport.ViewRay = JsonSerializer.Deserialize(lastSceneSpawnName); + break; + } + case GeneralOptions.StartupSceneModes.LastOpened: + { + if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName)) + { + var lastScenes = JsonSerializer.Deserialize(lastSceneIdName); + foreach (var sceneId in lastScenes) + { + var lastScene = ContentDatabase.Find(sceneId); + if (!(lastScene is SceneItem)) + continue; + + Editor.Log($"Loading last opened scene: {lastScene.ShortName}"); + if (sceneId == lastScenes[0]) + Scene.OpenScene(sceneId); + else + Level.LoadSceneAsync(sceneId); + } + + // Restore view + if (ProjectCache.TryGetCustomData(ProjectDataLastSceneSpawn, out var lastSceneSpawnName)) + Windows.EditWin.Viewport.ViewRay = JsonSerializer.Deserialize(lastSceneSpawnName); + } + break; + } } - break; } + catch (Exception ex) + { + // Ignore errors } } @@ -594,8 +604,8 @@ namespace FlaxEditor UI.UpdateStatusBar(); } - if (UI?.StatusBar?.Text != null && !UI.StatusBar.Text.Contains("Auto") && - _saveNowButton != null && _cancelSaveButton != null && + if (UI?.StatusBar?.Text != null && !UI.StatusBar.Text.Contains("Auto") && + _saveNowButton != null && _cancelSaveButton != null && (_saveNowButton.Visible || _cancelSaveButton.Visible)) { _saveNowButton.Visible = false; @@ -677,20 +687,12 @@ namespace FlaxEditor // Cache last opened scenes { - List lastSceneIds = new List(); - if (Level.ScenesCount > 0) - { - foreach (var scene in Level.Scenes) - { - lastSceneIds.Add(scene.ID); - } - } - else - { - lastSceneIds.Add(Guid.Empty); - } + var lastScenes = Level.Scenes; + var lastSceneIds = new Guid[lastScenes.Length]; + for (int i = 0; i < lastScenes.Length; i++) + lastSceneIds[i] = lastScenes[i].ID; var lastSceneSpawn = Windows.EditWin.Viewport.ViewRay; - ProjectCache.SetCustomData(ProjectDataLastScene, JsonSerializer.Serialize(lastSceneIds.ToArray())); + ProjectCache.SetCustomData(ProjectDataLastScene, JsonSerializer.Serialize(lastSceneIds)); ProjectCache.SetCustomData(ProjectDataLastSceneSpawn, JsonSerializer.Serialize(lastSceneSpawn)); } From 5d2a3482c770abb9ee522d6bd39dce1a62f77cfc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 May 2023 16:10:09 +0200 Subject: [PATCH 19/37] Reduce compilation problems by moving `FindObject` fwd decl to be more commonly included #1040 --- Source/Engine/Scripting/ScriptingObject.h | 2 ++ Source/Engine/Serialization/ReadStream.h | 2 -- Source/Engine/Serialization/Serialization.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Scripting/ScriptingObject.h b/Source/Engine/Scripting/ScriptingObject.h index 5682f2409..183e23297 100644 --- a/Source/Engine/Scripting/ScriptingObject.h +++ b/Source/Engine/Scripting/ScriptingObject.h @@ -267,3 +267,5 @@ API_CLASS(InBuild) class FLAXENGINE_API PersistentScriptingObject : public Scrip public: PersistentScriptingObject(const SpawnParams& params); }; + +extern FLAXENGINE_API class ScriptingObject* FindObject(const Guid& id, class MClass* type); diff --git a/Source/Engine/Serialization/ReadStream.h b/Source/Engine/Serialization/ReadStream.h index 69e86fa4b..37f8b8433 100644 --- a/Source/Engine/Serialization/ReadStream.h +++ b/Source/Engine/Serialization/ReadStream.h @@ -5,8 +5,6 @@ #include "Stream.h" #include "Engine/Core/Templates.h" -extern FLAXENGINE_API class ScriptingObject* FindObject(const Guid& id, class MClass* type); - /// /// Base class for all data read streams /// diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h index 7543f8241..182ac71cf 100644 --- a/Source/Engine/Serialization/Serialization.h +++ b/Source/Engine/Serialization/Serialization.h @@ -456,7 +456,7 @@ namespace Serialization Guid id; Deserialize(stream, id, modifier); modifier->IdsMapping.TryGet(id, id); - v = (T*)FindObject(id, T::GetStaticClass()); + v = (T*)::FindObject(id, T::GetStaticClass()); } // Scripting Object Reference From 68151592a46491143fe0d239f31c0277ba2adf3f Mon Sep 17 00:00:00 2001 From: Wiktor Kocielski Date: Sat, 22 Apr 2023 20:58:26 +0300 Subject: [PATCH 20/37] Make Level class to unload scenes in reversed order --- Source/Engine/Level/Level.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 4d81886a8..f869c058f 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -903,12 +903,11 @@ bool LevelImpl::unloadScene(Scene* scene) bool LevelImpl::unloadScenes() { auto scenes = Level::Scenes; - for (int32 i = 0; i < scenes.Count(); i++) + for (int32 i = scenes.Count() - 1; i >= 0; i--) { if (unloadScene(scenes[i])) return true; } - return false; } From 12a2a69ad7987711df0e80ef8569244ad559bbe1 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 5 May 2023 09:18:44 -0500 Subject: [PATCH 21/37] Change to `TryGetBox` --- .../GPU/ParticleEmitterGraph.GPU.Particles.cpp | 16 ++++++++-------- Source/Engine/Visject/VisjectGraph.cpp | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp index 9b59ba5b9..905d1a11c 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp @@ -388,32 +388,32 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va // Random Float Range case 213: { - auto a = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat(); - auto b = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat(); + auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat(); + auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat(); value = writeLocal(VariantType::Float, String::Format(TEXT("lerp({0}, {1}, RAND)"), a.Value, b.Value), node); break; } // Random Vector2 Range case 214: { - auto a = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat2(); - auto b = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat2(); + auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat2(); + auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat2(); value = writeLocal(VariantType::Float2, String::Format(TEXT("float2(lerp({0}.x, {1}.x, RAND), lerp({0}.y, {1}.y, RAND))"), a.Value, b.Value), node); break; } // Random Vector3 Range case 215: { - auto a = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat3(); - auto b = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat3(); + auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat3(); + auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat3(); value = writeLocal(VariantType::Float3, String::Format(TEXT("float3(lerp({0}.x, {1}.x, RAND), lerp({0}.y, {1}.y, RAND), lerp({0}.z, {1}.z, RAND))"), a.Value, b.Value), node); break; } // Random Vector4 Range case 216: { - auto a = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat4(); - auto b = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat4(); + auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat4(); + auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat4(); value = writeLocal(VariantType::Float4, String::Format(TEXT("float4(lerp({0}.x, {1}.x, RAND), lerp({0}.y, {1}.y, RAND), lerp({0}.z, {1}.z, RAND), lerp({0}.w, {1}.w, RAND))"), a.Value, b.Value), node); break; } diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp index 5559cd713..4aac619af 100644 --- a/Source/Engine/Visject/VisjectGraph.cpp +++ b/Source/Engine/Visject/VisjectGraph.cpp @@ -1273,16 +1273,16 @@ void VisjectExecutor::ProcessGroupParticles(Box* box, Node* node, Value& value) // Random Float Range case 213: { - auto a = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat; - auto b = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat; + auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat; + auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat; value = Math::Lerp(a, b, RAND); break; } // Random Vector2 Range case 214: { - auto a = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat2(); - auto b = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat2(); + auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat2(); + auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat2(); value = Float2( Math::Lerp(a.X, b.X, RAND), Math::Lerp(a.Y, b.Y, RAND) @@ -1292,8 +1292,8 @@ void VisjectExecutor::ProcessGroupParticles(Box* box, Node* node, Value& value) // Random Vector3 Range case 215: { - auto a = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat3(); - auto b = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat3(); + auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat3(); + auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat3(); value = Float3( Math::Lerp(a.X, b.X, RAND), Math::Lerp(a.Y, b.Y, RAND), @@ -1304,8 +1304,8 @@ void VisjectExecutor::ProcessGroupParticles(Box* box, Node* node, Value& value) // Random Vector4 Range case 216: { - auto a = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat4(); - auto b = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat4(); + auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat4(); + auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat4(); value = Float4( Math::Lerp(a.X, b.X, RAND), Math::Lerp(a.Y, b.Y, RAND), From dee445c32c9c8a203fa3dcca4a1f8c87ece5d102 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 May 2023 16:27:57 +0200 Subject: [PATCH 22/37] Fix compilation warnings --- Source/Editor/Editor.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 2bf2a6922..67d89786d 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -9,7 +9,6 @@ using FlaxEditor.Content; using FlaxEditor.Content.Import; using FlaxEditor.Content.Settings; using FlaxEditor.Content.Thumbnails; -using FlaxEditor.GUI; using FlaxEditor.Modules; using FlaxEditor.Modules.SourceCodeEditing; using FlaxEditor.Options; @@ -337,7 +336,7 @@ namespace FlaxEditor } } } - catch (Exception ex) + catch (Exception) { // Ignore errors } @@ -473,7 +472,7 @@ namespace FlaxEditor } } } - catch (Exception ex) + catch (Exception) { // Ignore errors } From 2affebd3753c5d7fa7c68c8d5d6e36ea7e4cbbb8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 6 May 2023 14:57:44 +0200 Subject: [PATCH 23/37] Fix C# serialization of scene objects when property throws and exception #88 --- Source/Engine/Serialization/JsonConverters.cs | 2 +- Source/Engine/Serialization/JsonSerializer.cs | 127 ++++++++++++------ 2 files changed, 85 insertions(+), 44 deletions(-) diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs index 3bb0f8a89..07388a09b 100644 --- a/Source/Engine/Serialization/JsonConverters.cs +++ b/Source/Engine/Serialization/JsonConverters.cs @@ -37,7 +37,7 @@ namespace FlaxEngine.Json { // Skip serialization as reference id for the root object serialization (eg. Script) var cache = JsonSerializer.Current.Value; - if (cache != null && cache.IsDuringSerialization && cache.SerializerWriter.SerializeStackSize == 0) + if (cache != null && cache.IsWriting && cache.SerializerWriter.SerializeStackSize == 0) { return false; } diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index 8dad82d64..d988b307c 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -26,7 +26,8 @@ namespace FlaxEngine.Json public JsonSerializerInternalWriter SerializerWriter; public UnmanagedMemoryStream MemoryStream; public StreamReader Reader; - public bool IsDuringSerialization; + public bool IsWriting; + public bool IsReading; public unsafe SerializerCache(JsonSerializerSettings settings) { @@ -50,6 +51,49 @@ namespace FlaxEngine.Json DateFormatString = JsonSerializer.DateFormatString, }; } + + public void ReadBegin() + { + if (IsReading) + { + // TODO: Reset reading state (eg if previous deserialization got exception) + } + IsWriting = false; + IsReading = true; + } + + public void ReadEnd() + { + IsReading = false; + } + + public void WriteBegin() + { + if (IsWriting) + { + // Reset writing state (eg if previous serialization got exception) + JsonWriter = new JsonTextWriter(StringWriter) + { + IndentChar = '\t', + Indentation = 1, + Formatting = JsonSerializer.Formatting, + DateFormatHandling = JsonSerializer.DateFormatHandling, + DateTimeZoneHandling = JsonSerializer.DateTimeZoneHandling, + FloatFormatHandling = JsonSerializer.FloatFormatHandling, + StringEscapeHandling = JsonSerializer.StringEscapeHandling, + Culture = JsonSerializer.Culture, + DateFormatString = JsonSerializer.DateFormatString, + }; + } + StringBuilder.Clear(); + IsWriting = true; + IsReading = false; + } + + public void WriteEnd() + { + IsWriting = false; + } } internal static JsonSerializerSettings Settings = CreateDefaultSettings(false); @@ -122,9 +166,9 @@ namespace FlaxEngine.Json var cache = isManagedOnly ? CacheManagedOnly.Value : Cache.Value; Current.Value = cache; - cache.StringBuilder.Clear(); - cache.IsDuringSerialization = true; + cache.WriteBegin(); cache.SerializerWriter.Serialize(cache.JsonWriter, obj, type); + cache.WriteEnd(); return cache.StringBuilder.ToString(); } @@ -141,9 +185,9 @@ namespace FlaxEngine.Json var cache = isManagedOnly ? CacheManagedOnly.Value : Cache.Value; Current.Value = cache; - cache.StringBuilder.Clear(); - cache.IsDuringSerialization = true; + cache.WriteBegin(); cache.SerializerWriter.Serialize(cache.JsonWriter, obj, type); + cache.WriteEnd(); return cache.StringBuilder.ToString(); } @@ -161,9 +205,9 @@ namespace FlaxEngine.Json var cache = isManagedOnly ? CacheManagedOnly.Value : Cache.Value; Current.Value = cache; - cache.StringBuilder.Clear(); - cache.IsDuringSerialization = true; + cache.WriteBegin(); cache.SerializerWriter.SerializeDiff(cache.JsonWriter, obj, type, other); + cache.WriteEnd(); return cache.StringBuilder.ToString(); } @@ -176,21 +220,20 @@ namespace FlaxEngine.Json public static void Deserialize(object input, string json) { var cache = Cache.Value; - cache.IsDuringSerialization = false; - Current.Value = cache; - + cache.ReadBegin(); using (JsonReader reader = new JsonTextReader(new StringReader(json))) { cache.JsonSerializer.Populate(reader, input); - - if (!cache.JsonSerializer.CheckAdditionalContent) - return; - while (reader.Read()) + if (cache.JsonSerializer.CheckAdditionalContent) { - if (reader.TokenType != JsonToken.Comment) - throw new Exception("Additional text found in JSON string after finishing deserializing object."); + while (reader.Read()) + { + if (reader.TokenType != JsonToken.Comment) + throw new Exception("Additional text found in JSON string after finishing deserializing object."); + } } } + cache.ReadEnd(); } /// @@ -214,22 +257,20 @@ namespace FlaxEngine.Json { object result; var cache = Cache.Value; - cache.IsDuringSerialization = false; - Current.Value = cache; - + cache.ReadBegin(); using (JsonReader reader = new JsonTextReader(new StringReader(json))) { result = cache.JsonSerializer.Deserialize(reader, objectType); - - if (!cache.JsonSerializer.CheckAdditionalContent) - return result; - while (reader.Read()) + if (cache.JsonSerializer.CheckAdditionalContent) { - if (reader.TokenType != JsonToken.Comment) - throw new Exception("Additional text found in JSON string after finishing deserializing object."); + while (reader.Read()) + { + if (reader.TokenType != JsonToken.Comment) + throw new Exception("Additional text found in JSON string after finishing deserializing object."); + } } } - + cache.ReadEnd(); return result; } @@ -242,22 +283,21 @@ namespace FlaxEngine.Json { object result; var cache = Cache.Value; - cache.IsDuringSerialization = false; - Current.Value = cache; - + cache.ReadBegin(); using (JsonReader reader = new JsonTextReader(new StringReader(json))) { result = cache.JsonSerializer.Deserialize(reader); - if (!cache.JsonSerializer.CheckAdditionalContent) - return result; - while (reader.Read()) + if (cache.JsonSerializer.CheckAdditionalContent) { - if (reader.TokenType != JsonToken.Comment) - throw new Exception("Additional text found in JSON string after finishing deserializing object."); + while (reader.Read()) + { + if (reader.TokenType != JsonToken.Comment) + throw new Exception("Additional text found in JSON string after finishing deserializing object."); + } } } - + cache.ReadEnd(); return result; } @@ -270,8 +310,7 @@ namespace FlaxEngine.Json public static unsafe void Deserialize(object input, byte* jsonBuffer, int jsonLength) { var cache = Cache.Value; - cache.IsDuringSerialization = false; - Current.Value = cache; + cache.ReadBegin(); /*// Debug json string reading cache.MemoryStream.Initialize(jsonBuffer, jsonLength); @@ -291,14 +330,16 @@ namespace FlaxEngine.Json { cache.JsonSerializer.Populate(jsonReader, input); } - - if (!cache.JsonSerializer.CheckAdditionalContent) - return; - while (jsonReader.Read()) + if (cache.JsonSerializer.CheckAdditionalContent) { - if (jsonReader.TokenType != JsonToken.Comment) - throw new Exception("Additional text found in JSON string after finishing deserializing object."); + while (jsonReader.Read()) + { + if (jsonReader.TokenType != JsonToken.Comment) + throw new Exception("Additional text found in JSON string after finishing deserializing object."); + } } + + cache.ReadEnd(); } /// From 10bab59acc9c5850abae116e7ad0904b36cc07f6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 6 May 2023 14:58:10 +0200 Subject: [PATCH 24/37] Fix scene asset runtime contents when saving scene file in Editor --- Source/Engine/Level/Level.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index f869c058f..4fa37c80d 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -1209,6 +1209,15 @@ bool LevelImpl::saveScene(Scene* scene, const String& path) LOG(Info, "Scene saved! Time {0} ms", Math::CeilToInt((float)(DateTime::NowUTC() - startTime).GetTotalMilliseconds())); +#if USE_EDITOR + // Reload asset at the target location if is loaded + Asset* asset = Content::GetAsset(sceneId); + if (!asset) + asset = Content::GetAsset(path); + if (asset) + asset->Reload(); +#endif + // Fire event CallSceneEvent(SceneEventType::OnSceneSaved, scene, sceneId); From db14c8a0a6fba0605babcc080761b3d821b8ee0a Mon Sep 17 00:00:00 2001 From: Ruan Lucas <79365912+RuanLucasGD@users.noreply.github.com> Date: Sat, 6 May 2023 10:46:26 -0400 Subject: [PATCH 25/37] refactor "GetSubTags" --- Source/Engine/Level/Tags.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Level/Tags.cpp b/Source/Engine/Level/Tags.cpp index dd4bc331a..efdd522e7 100644 --- a/Source/Engine/Level/Tags.cpp +++ b/Source/Engine/Level/Tags.cpp @@ -58,14 +58,15 @@ Tag Tags::Get(const StringView& tagName) Array Tags::GetSubTags(Tag parentTag) { Array subTags; - auto _parentTagName = parentTag.ToString(); + const String& parentTagName = parentTag.ToString(); for (int i = 0; i < Tags::List.Count(); i++) { - auto& subTagName = Tags::List[i]; - if (subTagName.Contains(_parentTagName) && subTagName != _parentTagName) + const Tag tag = Tag(i + 1); + const String& tagName = Tags::List[i]; + if (tagName.StartsWith(parentTagName) && parentTag.Index != tag) { - subTags.Add(Tags::Get(subTagName)); + subTags.Add(tag); } } From 64515404e97e70994e04c3f5d7a7d982f57d41b2 Mon Sep 17 00:00:00 2001 From: Ruan Lucas <79365912+RuanLucasGD@users.noreply.github.com> Date: Sat, 6 May 2023 10:49:07 -0400 Subject: [PATCH 26/37] refactor again --- Source/Engine/Level/Tags.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Level/Tags.cpp b/Source/Engine/Level/Tags.cpp index efdd522e7..63cfd8442 100644 --- a/Source/Engine/Level/Tags.cpp +++ b/Source/Engine/Level/Tags.cpp @@ -64,7 +64,7 @@ Array Tags::GetSubTags(Tag parentTag) { const Tag tag = Tag(i + 1); const String& tagName = Tags::List[i]; - if (tagName.StartsWith(parentTagName) && parentTag.Index != tag) + if (tagName.StartsWith(parentTagName) && parentTag.Index != tag.Index) { subTags.Add(tag); } From c47907c0c305944b64fdc7120eadd54a0e261d10 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 6 May 2023 17:36:17 +0200 Subject: [PATCH 27/37] Fix C# serialization of reference to self (eg. script sub-object referencing owning script) #55 --- Source/Engine/Serialization/JsonSerializer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index d988b307c..617768a83 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -33,6 +33,7 @@ namespace FlaxEngine.Json { JsonSerializer = Newtonsoft.Json.JsonSerializer.CreateDefault(settings); JsonSerializer.Formatting = Formatting.Indented; + JsonSerializer.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; StringBuilder = new StringBuilder(256); StringWriter = new StringWriter(StringBuilder, CultureInfo.InvariantCulture); SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer); From efab20e3359c716c08d6d44ced9bcf1cc6b4b3d5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 6 May 2023 23:00:50 +0200 Subject: [PATCH 28/37] Fix compilation regression from 5d2a3482c770abb9ee522d6bd39dce1a62f77cfc --- Source/Engine/Serialization/ReadStream.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Serialization/ReadStream.h b/Source/Engine/Serialization/ReadStream.h index 37f8b8433..69e86fa4b 100644 --- a/Source/Engine/Serialization/ReadStream.h +++ b/Source/Engine/Serialization/ReadStream.h @@ -5,6 +5,8 @@ #include "Stream.h" #include "Engine/Core/Templates.h" +extern FLAXENGINE_API class ScriptingObject* FindObject(const Guid& id, class MClass* type); + /// /// Base class for all data read streams /// From e8ec5794d20840a38947cd29094b0c1ee148c275 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 6 May 2023 23:02:35 +0200 Subject: [PATCH 29/37] Simplify code #1034 --- Source/Engine/Level/Tags.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Level/Tags.cpp b/Source/Engine/Level/Tags.cpp index 602aea445..f540cb112 100644 --- a/Source/Engine/Level/Tags.cpp +++ b/Source/Engine/Level/Tags.cpp @@ -59,17 +59,13 @@ Array Tags::GetSubTags(Tag parentTag) { Array subTags; const String& parentTagName = parentTag.ToString(); - - for (int i = 0; i < Tags::List.Count(); i++) + for (int32 i = 0; i < List.Count(); i++) { - const Tag tag = Tag(i + 1); - const String& tagName = Tags::List[i]; + const Tag tag(i + 1); + const String& tagName = List[i]; if (tagName.StartsWith(parentTagName) && parentTag.Index != tag.Index) - { subTags.Add(tag); - } } - return subTags; } From 4131e15f0eb9131cf0c2a76f26531728dceff24c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 7 May 2023 10:46:13 +0200 Subject: [PATCH 30/37] Fix deprecation warnings --- Source/Engine/Networking/NetworkConfig.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Engine/Networking/NetworkConfig.h b/Source/Engine/Networking/NetworkConfig.h index d875c49b4..0be511210 100644 --- a/Source/Engine/Networking/NetworkConfig.h +++ b/Source/Engine/Networking/NetworkConfig.h @@ -88,5 +88,8 @@ API_STRUCT(Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkConfi { NetworkDriverType = NetworkDriverType::ENet; } + NetworkConfig(const NetworkConfig& other) = default; + NetworkConfig(NetworkConfig&& other) = default; + NetworkConfig& operator=(const NetworkConfig& other) = default; PRAGMA_ENABLE_DEPRECATION_WARNINGS }; From 713cf0d4b2134974c67035d67d5100642e8d8a9a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 7 May 2023 11:01:30 +0200 Subject: [PATCH 31/37] Improve #1077 to use a single pixel shader with permutations --- Content/Shaders/DebugDraw.flax | 4 +-- Source/Engine/Debug/DebugDraw.cpp | 8 ++--- Source/Shaders/DebugDraw.shader | 50 ++++++++++--------------------- 3 files changed, 22 insertions(+), 40 deletions(-) diff --git a/Content/Shaders/DebugDraw.flax b/Content/Shaders/DebugDraw.flax index 024978b04..d1dff466f 100644 --- a/Content/Shaders/DebugDraw.flax +++ b/Content/Shaders/DebugDraw.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:104aeb07ffbb68a6c37d8817565f842a2bcabcfd7980ad1ec45a43c81e1b7a02 -size 2115 +oid sha256:6ec2ded31f6042306c23a273c2cf6c5f54d046834d6c72b7c087d2c2bf0ea645 +size 2060 diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index d64494125..125ffd520 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -632,10 +632,10 @@ void DebugDrawService::Update() desc.VS = shader->GetVS("VS"); // Default - desc.PS = shader->GetPS("PSUnlit"); + desc.PS = shader->GetPS("PS", 0); desc.PrimitiveTopologyType = PrimitiveTopologyType::Line; failed |= DebugDrawPsLinesDefault.Create(desc); - desc.PS = shader->GetPS("PS"); + desc.PS = shader->GetPS("PS", 1); desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle; failed |= DebugDrawPsTrianglesDefault.Create(desc); desc.Wireframe = true; @@ -643,10 +643,10 @@ void DebugDrawService::Update() // Depth Test desc.Wireframe = false; - desc.PS = shader->GetPS("PS_DepthTestUnlit"); + desc.PS = shader->GetPS("PS", 2); desc.PrimitiveTopologyType = PrimitiveTopologyType::Line; failed |= DebugDrawPsLinesDepthTest.Create(desc); - desc.PS = shader->GetPS("PS_DepthTest"); + desc.PS = shader->GetPS("PS", 3); desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle; failed |= DebugDrawPsTrianglesDepthTest.Create(desc); desc.Wireframe = true; diff --git a/Source/Shaders/DebugDraw.shader b/Source/Shaders/DebugDraw.shader index 8782abe4b..018129302 100644 --- a/Source/Shaders/DebugDraw.shader +++ b/Source/Shaders/DebugDraw.shader @@ -27,49 +27,31 @@ VS2PS VS(float3 Position : POSITION, float4 Color : COLOR) return output; } -void PerformDepthTest(float4 svPosition) +META_PS(true, FEATURE_LEVEL_ES2) +META_PERMUTATION_2(USE_DEPTH_TEST=0,USE_FAKE_LIGHTING=0) +META_PERMUTATION_2(USE_DEPTH_TEST=0,USE_FAKE_LIGHTING=1) +META_PERMUTATION_2(USE_DEPTH_TEST=1,USE_FAKE_LIGHTING=0) +META_PERMUTATION_2(USE_DEPTH_TEST=1,USE_FAKE_LIGHTING=1) +float4 PS(VS2PS input) : SV_Target { +#if USE_DEPTH_TEST // Depth test manually if compositing editor primitives FLATTEN if (EnableDepthTest) { - float sceneDepthDeviceZ = SceneDepthTexture.Load(int3(svPosition.xy, 0)).r; - float interpolatedDeviceZ = svPosition.z; + float sceneDepthDeviceZ = SceneDepthTexture.Load(int3(input.Position.xy, 0)).r; + float interpolatedDeviceZ = input.Position.z; clip(sceneDepthDeviceZ - interpolatedDeviceZ); } -} +#endif -float4 PerformFakeLighting(float4 svPosition, float4 c) -{ + float4 color = input.Color; +#if USE_FAKE_LIGHTING // Reconstruct view normal and calculate faked lighting - float depth = svPosition.z * 10000; + float depth = input.Position.z * 10000; float3 n = normalize(float3(-ddx(depth), -ddy(depth), 1.0f)); - c.rgb *= saturate(abs(dot(n, float3(0, 1, 0))) + 0.5f); - return c; -} + color.rgb *= saturate(abs(dot(n, float3(0, 1, 0))) + 0.5f); +#endif -META_PS(true, FEATURE_LEVEL_ES2) -float4 PS(VS2PS input) : SV_Target -{ - return PerformFakeLighting(input.Position, input.Color); -} - -META_PS(true, FEATURE_LEVEL_ES2) -float4 PSUnlit(VS2PS input) : SV_Target -{ - return input.Color; -} - -META_PS(true, FEATURE_LEVEL_ES2) -float4 PS_DepthTest(VS2PS input) : SV_Target -{ - PerformDepthTest(input.Position); - return PerformFakeLighting(input.Position, input.Color); -} - -META_PS(true, FEATURE_LEVEL_ES2) -float4 PS_DepthTestUnlit(VS2PS input) : SV_Target -{ - PerformDepthTest(input.Position); - return input.Color; + return color; } From a99f792979647ff596f18f68db2c10b92cb754e3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 7 May 2023 13:58:28 +0200 Subject: [PATCH 32/37] Disable unit test on Linux for now --- .github/workflows/tests.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index dc258824f..6cbe46bfe 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,12 +25,10 @@ jobs: ./GenerateProjectFiles.sh -vs2019 ./Development/Scripts/Linux/CallBuildTool.sh -build -log -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget ./Development/Scripts/Linux/CallBuildTool.sh -build -log -arch=x64 -platform=Linux -configuration=Debug -buildtargets=FlaxEditor -BuildBindingsOnly - ./Development/Scripts/Linux/CallBuildTool.sh -build -log -arch=x64 -platform=Linux -configuration=Debug -buildtargets="FlaxEngine.Tests" ./Development/Scripts/Linux/CallBuildTool.sh -build -log -arch=x64 -platform=Linux -configuration=Debug -buildtargets="Flax.Build.Tests" - name: Test run: | Binaries/Editor/Linux/Development/FlaxTests - mono Source/Platforms/DotNet/NUnit/nunit3-console.exe Binaries/Tools/FlaxEngine.Tests.dll --framework=mono-4.0 mono Source/Platforms/DotNet/NUnit/nunit3-console.exe Binaries/Tools/Flax.Build.Tests.dll --framework=mono-4.0 - name: Test UseLargeWorlds run: | From dd7dc2882890255d94f70cdbc963a140d84dc04c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 7 May 2023 18:09:54 +0200 Subject: [PATCH 33/37] Add automated unit test for nested prefab changes sync after base prefab apply #1015 --- Source/Engine/Tests/TestPrefabs.cpp | 85 +++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/Source/Engine/Tests/TestPrefabs.cpp b/Source/Engine/Tests/TestPrefabs.cpp index 38cdd033d..5656b6119 100644 --- a/Source/Engine/Tests/TestPrefabs.cpp +++ b/Source/Engine/Tests/TestPrefabs.cpp @@ -245,4 +245,89 @@ TEST_CASE("Prefabs") Content::DeleteAsset(prefabA); Content::DeleteAsset(prefabB); } + SECTION("Test Syncing Changes In Nested Prefab Instance") + { + // https://github.com/FlaxEngine/FlaxEngine/issues/1015 + + // Create TestActor prefab with just root object + AssetReference testActorPrefab = Content::CreateVirtualAsset(); + REQUIRE(testActorPrefab); + Guid id; + Guid::Parse("7691e981482f2a486e10cfae149e07d3", id); + testActorPrefab->ChangeID(id); + auto testActorPrefabInit = testActorPrefab->Init(Prefab::TypeName, + "[" + "{" + "\"ID\": \"5d73990240497afc0c6d36814cc6ebbe\"," + "\"TypeName\": \"FlaxEngine.EmptyActor\"," + "\"Name\": \"TestActor\"" + "}" + "]"); + REQUIRE(!testActorPrefabInit); + + // Create NestedActor prefab that inherits from TestActor prefab + AssetReference nestedActorPrefab = Content::CreateVirtualAsset(); + REQUIRE(nestedActorPrefab); + Guid::Parse("1d521df4465ad849e274748c6d14b703", id); + nestedActorPrefab->ChangeID(id); + auto nestedActorPrefabInit = nestedActorPrefab->Init(Prefab::TypeName, + "[" + "{" + "\"ID\": \"75c1587b4caeea27241ba7af00dafd45\"," + "\"PrefabID\": \"7691e981482f2a486e10cfae149e07d3\"," + "\"PrefabObjectID\": \"5d73990240497afc0c6d36814cc6ebbe\"," + "\"Name\": \"NestedActor\"" + "}" + "]"); + REQUIRE(!nestedActorPrefabInit); + + // Spawn test instances of both prefabs + ScriptingObjectReference testActor = PrefabManager::SpawnPrefab(testActorPrefab); + ScriptingObjectReference nestedActor = PrefabManager::SpawnPrefab(nestedActorPrefab); + + // Verify initial scenario + REQUIRE(testActor); + CHECK(testActor->GetName() == TEXT("TestActor")); + CHECK(testActor->GetStaticFlags() == StaticFlags::FullyStatic); + REQUIRE(nestedActor); + CHECK(nestedActor->GetName() == TEXT("NestedActor")); + CHECK(nestedActor->GetStaticFlags() == StaticFlags::FullyStatic); + + // Modify TestActor instance to have Static Flags property changed + testActor->SetStaticFlags(StaticFlags::None); + + // Apply prefab changes + auto applyResult = PrefabManager::ApplyAll(testActor); + REQUIRE(!applyResult); + + // Verify if instances were properly updated + REQUIRE(testActor); + CHECK(testActor->GetName() == TEXT("TestActor")); + CHECK(testActor->GetStaticFlags() == StaticFlags::None); + REQUIRE(nestedActor); + CHECK(nestedActor->GetName() == TEXT("NestedActor")); + CHECK(nestedActor->GetStaticFlags() == StaticFlags::None); + + // Cleanup + nestedActor->DeleteObject(); + testActor->DeleteObject(); + + // Spawn another test instances instances of both prefabs + testActor = PrefabManager::SpawnPrefab(testActorPrefab); + nestedActor = PrefabManager::SpawnPrefab(nestedActorPrefab); + + // Verify if instances were properly updated + REQUIRE(testActor); + CHECK(testActor->GetName() == TEXT("TestActor")); + CHECK(testActor->GetStaticFlags() == StaticFlags::None); + REQUIRE(nestedActor); + CHECK(nestedActor->GetName() == TEXT("NestedActor")); + CHECK(nestedActor->GetStaticFlags() == StaticFlags::None); + + // Cleanup + nestedActor->DeleteObject(); + testActor->DeleteObject(); + Content::DeleteAsset(nestedActorPrefab); + Content::DeleteAsset(testActorPrefab); + } } From 0c3d1e3ec6e2eb9f64b47f88f7f06ec82d59453b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 7 May 2023 18:12:06 +0200 Subject: [PATCH 34/37] Fix existing nested prefabs sync applying when updating base prefab changes #1015 --- Source/Engine/Level/Prefabs/Prefab.Apply.cpp | 89 ++++++++++---------- Source/Engine/Level/Prefabs/Prefab.h | 8 +- Source/Engine/Level/SceneQuery.cpp | 15 ---- 3 files changed, 48 insertions(+), 64 deletions(-) diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp index 908fa88c0..a5abf2221 100644 --- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp @@ -131,6 +131,8 @@ public: Dictionary PrefabInstanceIdToDataIndex; public: + typedef Array PrefabInstancesData; + /// /// Collects all the valid prefab instances to update on prefab data synchronization. /// @@ -138,15 +140,15 @@ public: /// The prefab asset identifier. /// The default instance (prefab internal, can be null). /// The target actor (optional actor to skip for counting, can be null). - static void CollectPrefabInstances(Array& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor); + static void CollectPrefabInstances(PrefabInstancesData& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor); /// /// Serializes all the prefab instances local changes to restore on prefab data synchronization. /// /// The prefab instances data. /// The temporary json buffer (cleared before and after usage). - /// The scene objects collection cache (cleared before and after usage). - static void SerializePrefabInstances(Array& prefabInstancesData, rapidjson_flax::StringBuffer& tmpBuffer, SceneObjectsListCacheType& sceneObjects); + /// Source prefab. + static void SerializePrefabInstances(PrefabInstancesData& prefabInstancesData, rapidjson_flax::StringBuffer& tmpBuffer, const Prefab* prefab); /// /// Synchronizes the prefab instances by applying changes from the diff data and restoring the local changes captured by SerializePrefabInstances. @@ -158,7 +160,7 @@ public: /// The hash table that maps the prefab object id to json data for the given prefab object. /// The collection of the new prefab objects ids added to prefab during changes synchronization or modifications apply. /// True if failed, otherwise false. - static bool SynchronizePrefabInstances(Array& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, const IdToDataLookupType& prefabObjectIdToDiffData, const Array& newPrefabObjectIds); + static bool SynchronizePrefabInstances(PrefabInstancesData& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, const IdToDataLookupType& prefabObjectIdToDiffData, const Array& newPrefabObjectIds); /// /// Synchronizes the prefab instances by applying changes from the diff data and restoring the local changes captured by SerializePrefabInstances. @@ -171,10 +173,10 @@ public: /// Collection with ids of the objects (actors and scripts) from the prefab before changes apply. Used to find new objects or old objects and use this information during changes sync (eg. generate ids for the new objects to prevent ids collisions). /// Collection with ids of the objects (actors and scripts) from the prefab after changes apply. Used to find new objects or old objects and use this information during changes sync (eg. generate ids for the new objects to prevent ids collisions). /// True if failed, otherwise false. - static bool SynchronizePrefabInstances(Array& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, rapidjson_flax::StringBuffer& tmpBuffer, const Array& oldObjectsIds, const Array& newObjectIds); + static bool SynchronizePrefabInstances(PrefabInstancesData& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, rapidjson_flax::StringBuffer& tmpBuffer, const Array& oldObjectsIds, const Array& newObjectIds); }; -void PrefabInstanceData::CollectPrefabInstances(Array& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor) +void PrefabInstanceData::CollectPrefabInstances(PrefabInstancesData& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor) { ScopeLock lock(PrefabManager::PrefabsReferencesLocker); if (PrefabManager::PrefabsReferences.ContainsKey(prefabId)) @@ -207,11 +209,12 @@ void PrefabInstanceData::CollectPrefabInstances(Array& prefa } } -void PrefabInstanceData::SerializePrefabInstances(Array& prefabInstancesData, rapidjson_flax::StringBuffer& tmpBuffer, SceneObjectsListCacheType& sceneObjects) +void PrefabInstanceData::SerializePrefabInstances(PrefabInstancesData& prefabInstancesData, rapidjson_flax::StringBuffer& tmpBuffer, const Prefab* prefab) { if (prefabInstancesData.IsEmpty()) return; - + CollectionPoolCache::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get(); + sceneObjects->EnsureCapacity(prefab->ObjectsCount * 4); for (int32 dataIndex = 0; dataIndex < prefabInstancesData.Count(); dataIndex++) { auto& instance = prefabInstancesData[dataIndex]; @@ -253,12 +256,10 @@ void PrefabInstanceData::SerializePrefabInstances(Array& pre instance.PrefabInstanceIdToDataIndex.Add(obj->GetSceneObjectId(), i); } } - - sceneObjects->Clear(); tmpBuffer.Clear(); } -bool PrefabInstanceData::SynchronizePrefabInstances(Array& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, const IdToDataLookupType& prefabObjectIdToDiffData, const Array& newPrefabObjectIds) +bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, const IdToDataLookupType& prefabObjectIdToDiffData, const Array& newPrefabObjectIds) { for (int32 instanceIndex = 0; instanceIndex < prefabInstancesData.Count(); instanceIndex++) { @@ -492,7 +493,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(Array& p return false; } -bool PrefabInstanceData::SynchronizePrefabInstances(Array& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, rapidjson_flax::StringBuffer& tmpBuffer, const Array& oldObjectsIds, const Array& newObjectIds) +bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, rapidjson_flax::StringBuffer& tmpBuffer, const Array& oldObjectsIds, const Array& newObjectIds) { if (prefabInstancesData.IsEmpty()) return false; @@ -588,8 +589,6 @@ bool FindCyclicReferences(Actor* actor, const Guid& prefabRootId) bool Prefab::ApplyAll(Actor* targetActor) { - // TODO: use more cached dictionaries and other collections containers to prevent memory allocations during apply (optimize for apply 10 times per second the same prefab on many changes in editor) - PROFILE_CPU(); const auto startTime = DateTime::NowUTC(); @@ -695,22 +694,40 @@ bool Prefab::ApplyAll(Actor* targetActor) ObjectsRemovalService::Flush(); + // Collect existing prefab instances (this and nested ones) to cache 'before' state used later to restore it + PrefabInstancesData thisPrefabInstancesData; + Array allPrefabsInstancesData; + { + PROFILE_CPU_NAMED("Prefab.CachePrefabInstancesData"); + + rapidjson_flax::StringBuffer dataBuffer; + PrefabInstanceData::CollectPrefabInstances(thisPrefabInstancesData, GetID(), _defaultInstance, targetActor); + PrefabInstanceData::SerializePrefabInstances(thisPrefabInstancesData, dataBuffer, this); + + allPrefabsInstancesData.Resize(allPrefabs.Count()); + for (int32 i = 0; i < allPrefabs.Count(); i++) + { + Prefab* prefab = allPrefabs[i]; + PrefabInstanceData::CollectPrefabInstances(allPrefabsInstancesData[i], prefab->GetID(), prefab->GetDefaultInstance(), prefab->GetDefaultInstance()); + PrefabInstanceData::SerializePrefabInstances(allPrefabsInstancesData[i], dataBuffer, prefab); + } + } + // Use internal call to improve shared collections memory sharing - if (ApplyAllInternal(targetActor, true)) + if (ApplyAllInternal(targetActor, true, thisPrefabInstancesData)) return true; - SyncNestedPrefabs(allPrefabs); + SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData); const auto endTime = DateTime::NowUTC(); LOG(Info, "Prefab updated! {0} ms", (int32)(endTime - startTime).GetTotalMilliseconds()); return false; } -bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab) +bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab, PrefabInstancesData& prefabInstancesData) { PROFILE_CPU_NAMED("Prefab.Apply"); ScopeLock lock(Locker); - const auto prefabId = GetID(); // Gather all scene objects in target instance (reused later) @@ -719,9 +736,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr SceneQuery::GetAllSerializableSceneObjects(targetActor, *targetObjects.Value); if (PrefabManager::FilterPrefabInstancesToSave(prefabId, *targetObjects.Value)) return true; - LOG(Info, "Applying prefab changes from actor {0} (total objects count: {2}) to {1}...", targetActor->ToString(), ToString(), targetObjects->Count()); - Array oldObjectsIds = ObjectsIds; // Serialize to json data @@ -809,17 +824,8 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr // TODO: what if user applied prefab with references to the other objects from scene? clear them or what? JsonTools::ChangeIds(diffDataDocument, objectInstanceIdToPrefabObjectId); } - dataBuffer.Clear(); - - // Fetch all existing prefab instances - Array prefabInstancesData; - PrefabInstanceData::CollectPrefabInstances(prefabInstancesData, prefabId, _defaultInstance, targetActor); - - // Serialize all prefab instances data (temporary storage) CollectionPoolCache::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get(); - sceneObjects->EnsureCapacity(ObjectsCount * 4); - PrefabInstanceData::SerializePrefabInstances(prefabInstancesData, dataBuffer, sceneObjects); // Destroy default instance and some cache data in Prefab DeleteDefaultInstance(); @@ -1204,18 +1210,7 @@ bool Prefab::UpdateInternal(const Array& defaultInstanceObjects, r return false; } -bool Prefab::SyncChanges(const NestedPrefabsList& allPrefabs) -{ - // Use internal call to improve shared collections memory sharing - if (SyncChangesInternal()) - return true; - - SyncNestedPrefabs(allPrefabs); - - return false; -} - -bool Prefab::SyncChangesInternal() +bool Prefab::SyncChangesInternal(PrefabInstancesData& prefabInstancesData) { PROFILE_CPU_NAMED("Prefab.SyncChanges"); @@ -1252,10 +1247,10 @@ bool Prefab::SyncChangesInternal() AutoActorCleanup cleanupDefaultInstance(targetActor); // Apply changes - return ApplyAllInternal(targetActor, false); + return ApplyAllInternal(targetActor, false, prefabInstancesData); } -void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs) +void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array& allPrefabsInstancesData) const { PROFILE_CPU(); LOG(Info, "Updating referencing prefabs"); @@ -1274,9 +1269,13 @@ void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs) continue; } - if (nestedPrefab->NestedPrefabs.Contains(GetID())) + const int32 nestedPrefabIndex = nestedPrefab->NestedPrefabs.Find(GetID()); + if (nestedPrefabIndex != -1) { - nestedPrefab->SyncChanges(allPrefabs); + if (nestedPrefab->SyncChangesInternal(allPrefabsInstancesData[nestedPrefabIndex])) + continue; + + nestedPrefab->SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData); ObjectsRemovalService::Flush(); } diff --git a/Source/Engine/Level/Prefabs/Prefab.h b/Source/Engine/Level/Prefabs/Prefab.h index 2f105e9ce..ed82473c9 100644 --- a/Source/Engine/Level/Prefabs/Prefab.h +++ b/Source/Engine/Level/Prefabs/Prefab.h @@ -82,12 +82,12 @@ public: private: #if USE_EDITOR + typedef Array PrefabInstancesData; typedef Array> NestedPrefabsList; - bool ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab); + bool ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab, PrefabInstancesData& prefabInstancesData); bool UpdateInternal(const Array& defaultInstanceObjects, rapidjson_flax::StringBuffer& tmpBuffer); - bool SyncChanges(const NestedPrefabsList& allPrefabs); - bool SyncChangesInternal(); - void SyncNestedPrefabs(const NestedPrefabsList& allPrefabs); + bool SyncChangesInternal(PrefabInstancesData& prefabInstancesData); + void SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array& allPrefabsInstancesData) const; #endif void DeleteDefaultInstance(); diff --git a/Source/Engine/Level/SceneQuery.cpp b/Source/Engine/Level/SceneQuery.cpp index b990daaa8..7bee0e510 100644 --- a/Source/Engine/Level/SceneQuery.cpp +++ b/Source/Engine/Level/SceneQuery.cpp @@ -7,17 +7,14 @@ Actor* SceneQuery::RaycastScene(const Ray& ray) { PROFILE_CPU(); - #if SCENE_QUERIES_WITH_LOCK ScopeLock lock(Level::ScenesLock); #endif - Actor* target; Actor* minTarget = nullptr; Real distance; Vector3 normal; Real minDistance = MAX_Real; - for (int32 i = 0; i < Level::Scenes.Count(); i++) { target = Level::Scenes[i]->Intersects(ray, distance, normal); @@ -30,7 +27,6 @@ Actor* SceneQuery::RaycastScene(const Ray& ray) } } } - return minTarget; } @@ -38,16 +34,13 @@ bool GetAllSceneObjectsQuery(Actor* actor, Array& objects) { objects.Add(actor); objects.Add(reinterpret_cast(actor->Scripts.Get()), actor->Scripts.Count()); - return true; } void SceneQuery::GetAllSceneObjects(Actor* root, Array& objects) { ASSERT(root); - PROFILE_CPU(); - Function&)> func(GetAllSceneObjectsQuery); root->TreeExecuteChildren&>(func, objects); } @@ -56,19 +49,15 @@ bool GetAllSerializableSceneObjectsQuery(Actor* actor, Array& obje { if (EnumHasAnyFlags(actor->HideFlags, HideFlags::DontSave)) return false; - objects.Add(actor); objects.Add(reinterpret_cast(actor->Scripts.Get()), actor->Scripts.Count()); - return true; } void SceneQuery::GetAllSerializableSceneObjects(Actor* root, Array& objects) { ASSERT(root); - PROFILE_CPU(); - Function&)> func(GetAllSerializableSceneObjectsQuery); root->TreeExecute&>(func, objects); } @@ -82,9 +71,7 @@ bool GetAllActorsQuery(Actor* actor, Array& actors) void SceneQuery::GetAllActors(Actor* root, Array& actors) { PROFILE_CPU(); - ASSERT(root); - Function&)> func(GetAllActorsQuery); root->TreeExecuteChildren&>(func, actors); } @@ -92,11 +79,9 @@ void SceneQuery::GetAllActors(Actor* root, Array& actors) void SceneQuery::GetAllActors(Array& actors) { PROFILE_CPU(); - #if SCENE_QUERIES_WITH_LOCK ScopeLock lock(Level::ScenesLock); #endif - for (int32 i = 0; i < Level::Scenes.Count(); i++) GetAllActors(Level::Scenes[i], actors); } From 159fb8f5eec7e1b4d1c65b4aeb02271a44d01c82 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 7 May 2023 18:22:58 +0200 Subject: [PATCH 35/37] Fix folders delete regression issue from dbd5c713445a11f8a377eefffa3467fb451af204 #1084 --- Source/Editor/Windows/ContentWindow.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 42e4c47ef..c9c1a8d21 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -488,7 +488,10 @@ namespace FlaxEditor.Windows /// The item to delete. public void Delete(ContentItem item) { - Delete(Editor.Instance.Windows.ContentWin.View.Selection); + var items = View.Selection; + if (items.Count == 0) + items = new List() { item }; + Delete(items); } /// From 531d0051696b01c3952e0b3a2cb61e2e5bcf5919 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 7 May 2023 19:39:57 +0200 Subject: [PATCH 36/37] Fix accessing object ownership info locally before object gets fully spawned #1066 --- .../Engine/Networking/NetworkReplicator.cpp | 40 ++++++++++++++++++- Source/Engine/Networking/NetworkReplicator.h | 10 ++--- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index d20f31b0b..b66f8d4cd 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -861,7 +861,7 @@ void NetworkReplicator::DespawnObject(ScriptingObject* obj) DeleteNetworkObject(obj); } -uint32 NetworkReplicator::GetObjectOwnerClientId(ScriptingObject* obj) +uint32 NetworkReplicator::GetObjectOwnerClientId(const ScriptingObject* obj) { uint32 id = NetworkManager::ServerClientId; if (obj) @@ -870,11 +870,23 @@ uint32 NetworkReplicator::GetObjectOwnerClientId(ScriptingObject* obj) const auto it = Objects.Find(obj->GetID()); if (it != Objects.End()) id = it->Item.OwnerClientId; + else + { + for (const SpawnItem& item : SpawnQueue) + { + if (item.Object == obj) + { + if (item.HasOwnership) + id = item.OwnerClientId; + break; + } + } + } } return id; } -NetworkObjectRole NetworkReplicator::GetObjectRole(ScriptingObject* obj) +NetworkObjectRole NetworkReplicator::GetObjectRole(const ScriptingObject* obj) { NetworkObjectRole role = NetworkObjectRole::None; if (obj) @@ -883,6 +895,18 @@ NetworkObjectRole NetworkReplicator::GetObjectRole(ScriptingObject* obj) const auto it = Objects.Find(obj->GetID()); if (it != Objects.End()) role = it->Item.Role; + else + { + for (const SpawnItem& item : SpawnQueue) + { + if (item.Object == obj) + { + if (item.HasOwnership) + role = item.Role; + break; + } + } + } } return role; } @@ -901,6 +925,18 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli auto& item = SpawnQueue[i]; if (item.Object == obj) { +#if !BUILD_RELEASE + if (ownerClientId == NetworkManager::LocalClientId) + { + // Ensure local client owns that object actually + CHECK(localRole == NetworkObjectRole::OwnedAuthoritative); + } + else + { + // Ensure local client doesn't own that object since it's owned by other client + CHECK(localRole != NetworkObjectRole::OwnedAuthoritative); + } +#endif item.HasOwnership = true; item.HierarchicalOwnership = hierarchical; item.OwnerClientId = ownerClientId; diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h index e0893cc64..389350e5f 100644 --- a/Source/Engine/Networking/NetworkReplicator.h +++ b/Source/Engine/Networking/NetworkReplicator.h @@ -104,14 +104,14 @@ public: /// /// The network object. /// The Client Id. - API_FUNCTION() static uint32 GetObjectOwnerClientId(ScriptingObject* obj); + API_FUNCTION() static uint32 GetObjectOwnerClientId(const ScriptingObject* obj); /// /// Gets the role of the network object used locally (eg. to check if can simulate object). /// /// The network object. /// The object role. - API_FUNCTION() static NetworkObjectRole GetObjectRole(ScriptingObject* obj); + API_FUNCTION() static NetworkObjectRole GetObjectRole(const ScriptingObject* obj); /// /// Checks if the network object is owned locally (thus current client has authority to manage it). @@ -119,7 +119,7 @@ public: /// Equivalent to GetObjectRole == OwnedAuthoritative. /// The network object. /// True if object is owned by this client, otherwise false. - API_FUNCTION() FORCE_INLINE static bool IsObjectOwned(ScriptingObject* obj) + API_FUNCTION() FORCE_INLINE static bool IsObjectOwned(const ScriptingObject* obj) { return GetObjectRole(obj) == NetworkObjectRole::OwnedAuthoritative; } @@ -130,7 +130,7 @@ public: /// Equivalent to GetObjectRole != Replicated. /// The network object. /// True if object is simulated on this client, otherwise false. - API_FUNCTION() FORCE_INLINE static bool IsObjectSimulated(ScriptingObject* obj) + API_FUNCTION() FORCE_INLINE static bool IsObjectSimulated(const ScriptingObject* obj) { return GetObjectRole(obj) != NetworkObjectRole::Replicated; } @@ -141,7 +141,7 @@ public: /// Equivalent to (GetObjectRole == Replicated or GetObjectRole == ReplicatedAutonomous). /// The network object. /// True if object is simulated on this client, otherwise false. - API_FUNCTION() FORCE_INLINE static bool IsObjectReplicated(ScriptingObject* obj) + API_FUNCTION() FORCE_INLINE static bool IsObjectReplicated(const ScriptingObject* obj) { const NetworkObjectRole role = GetObjectRole(obj); return role == NetworkObjectRole::Replicated || role == NetworkObjectRole::ReplicatedSimulated; From 0a5d37fd9b3cb8c9fafd1f6906dbac61afb00e82 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 7 May 2023 19:40:31 +0200 Subject: [PATCH 37/37] Add support for spawning multiple objects over network within a single group that is not from Prefabs #1066 --- .../Engine/Networking/NetworkReplicator.cpp | 81 +++++++++++++------ 1 file changed, 56 insertions(+), 25 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index b66f8d4cd..e150b1ea0 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -1505,8 +1505,8 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl } // Recreate object locally (spawn only root) - ScriptingObject* obj = nullptr; Actor* prefabInstance = nullptr; + Array objects; if (msgData.PrefabId.IsValid()) { const NetworkReplicatedObject* parent = ResolveObject(rootItem.ParentId); @@ -1525,7 +1525,7 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl { if (Objects.Contains(child->GetID())) { - obj = FindPrefabObject(child, rootItem.PrefabObjectID); + ScriptingObject* obj = FindPrefabObject(child, rootItem.PrefabObjectID); if (Objects.Contains(obj->GetID())) { // Other instance with already spawned network object @@ -1557,46 +1557,77 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl return; } } - if (!obj) - obj = FindPrefabObject(prefabInstance, rootItem.PrefabObjectID); - if (!obj) + + // Resolve objects from prefab instance + objects.Resize(msgData.ItemsCount); + for (int32 i = 0; i < msgData.ItemsCount; i++) { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} in prefab {}", rootItem.PrefabObjectID.ToString(), msgData.PrefabId.ToString()); - Delete(prefabInstance); - return; + auto& msgDataItem = msgDataItems[i]; + ScriptingObject* obj = FindPrefabObject(prefabInstance, msgDataItem.PrefabObjectID); + if (!obj) + { + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} in prefab {}", msgDataItem.PrefabObjectID.ToString(), msgData.PrefabId.ToString()); + Delete(prefabInstance); + return; + } + objects[i] = obj; } } - else + else if (msgData.ItemsCount == 1) { // Spawn object - if (msgData.ItemsCount != 1) - { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Only prefab object spawning can contain more than one object (for type {})", String(rootItem.ObjectTypeName)); - return; - } const ScriptingTypeHandle objectType = Scripting::FindScriptingType(rootItem.ObjectTypeName); - obj = ScriptingObject::NewObject(objectType); + ScriptingObject* obj = ScriptingObject::NewObject(objectType); if (!obj) { NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(rootItem.ObjectTypeName)); return; } + objects.Add(obj); + } + else + { + // Spawn objects + objects.Resize(msgData.ItemsCount); + for (int32 i = 0; i < msgData.ItemsCount; i++) + { + auto& msgDataItem = msgDataItems[i]; + const ScriptingTypeHandle objectType = Scripting::FindScriptingType(msgDataItem.ObjectTypeName); + ScriptingObject* obj = ScriptingObject::NewObject(objectType); + if (!obj) + { + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(msgDataItem.ObjectTypeName)); + for (ScriptingObject* e : objects) + Delete(e); + return; + } + objects[i] = obj; + if (i != 0) + { + // Link hierarchy of spawned objects before calling any networking code for them + if (auto sceneObject = ScriptingObject::Cast(obj)) + { + Actor* parent = nullptr; + for (int32 j = 0; j < i; j++) + { + if (msgDataItems[j].ObjectId == msgDataItem.ParentId) + { + parent = ScriptingObject::Cast(objects[j]); + break; + } + } + if (parent) + sceneObject->SetParent(parent); + } + } + } } // Setup all newly spawned objects for (int32 i = 0; i < msgData.ItemsCount; i++) { auto& msgDataItem = msgDataItems[i]; - if (i != 0) - { - obj = FindPrefabObject(prefabInstance, msgDataItem.PrefabObjectID); - if (!obj) - { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} in prefab {}", msgDataItem.PrefabObjectID.ToString(), msgData.PrefabId.ToString()); - Delete(prefabInstance); - return; - } - } + ScriptingObject* obj = objects[i]; if (!obj->IsRegistered()) obj->RegisterObject(); const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId);