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/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, 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/Editor.cs b/Source/Editor/Editor.cs index f50e954ad..e3c71fba8 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -10,7 +10,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; @@ -314,6 +313,7 @@ namespace FlaxEditor _areModulesInited = true; // Preload initial scene asset + try { var startupSceneMode = Options.Options.General.StartupSceneMode; if (startupSceneMode == GeneralOptions.StartupSceneModes.LastOpened && !ProjectCache.HasCustomData(ProjectDataLastScene)) @@ -330,12 +330,23 @@ 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 lastScenes = JsonSerializer.Deserialize(lastSceneIdName); + foreach (var scene in lastScenes) + { + var lastScene = scene; + Internal_LoadAsset(ref lastScene); + } + } break; } } } + catch (Exception) + { + // Ignore errors + } InitializationStart?.Invoke(); @@ -408,58 +419,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) && Guid.TryParse(lastSceneIdName, out var lastSceneId)) + var startupSceneMode = Options.Options.General.StartupSceneMode; + if (startupSceneMode == GeneralOptions.StartupSceneModes.LastOpened && !ProjectCache.HasCustomData(ProjectDataLastScene)) { - var lastScene = ContentDatabase.Find(lastSceneId); - if (lastScene is SceneItem) + // 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) { - Editor.Log("Loading last opened scene"); - Scene.OpenScene(lastSceneId); + Editor.Log("Loading default project scene"); + Scene.OpenScene(defaultSceneId); + + // Use spawn point + Windows.EditWin.Viewport.ViewRay = GameProject.DefaultSceneSpawn; + } + 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) + { + // Ignore errors } } @@ -669,11 +691,14 @@ namespace FlaxEditor // Start exit StateMachine.GoToState(); - // Cache last opened scene + // Cache last opened scenes { - var lastSceneId = Level.ScenesCount > 0 ? Level.Scenes[0].ID : 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, lastSceneId.ToString()); + ProjectCache.SetCustomData(ProjectDataLastScene, JsonSerializer.Serialize(lastSceneIds)); ProjectCache.SetCustomData(ProjectDataLastSceneSpawn, JsonSerializer.Serialize(lastSceneSpawn)); } diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index 5736a9f26..27b28596f 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -626,7 +626,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; ActorNode.Root.Spawn(actor, spawnParent); @@ -667,6 +676,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) { 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), } }, 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. /// 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/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 12eb0d07e..7aca2526a 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -489,7 +489,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); } /// @@ -657,6 +660,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; 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 2d83920c8..6ba13a2d5 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 = Utils.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 47a2b6534..891792d79 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -637,9 +637,10 @@ void DebugDrawService::Update() desc.VS = shader->GetVS("VS"); // Default - desc.PS = shader->GetPS("PS"); + desc.PS = shader->GetPS("PS", 0); desc.PrimitiveTopologyType = PrimitiveTopologyType::Line; failed |= DebugDrawPsLinesDefault.Create(desc); + desc.PS = shader->GetPS("PS", 1); desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle; failed |= DebugDrawPsTrianglesDefault.Create(desc); desc.Wireframe = true; @@ -647,9 +648,10 @@ void DebugDrawService::Update() // Depth Test desc.Wireframe = false; - desc.PS = shader->GetPS("PS_DepthTest"); + desc.PS = shader->GetPS("PS", 2); desc.PrimitiveTopologyType = PrimitiveTopologyType::Line; failed |= DebugDrawPsLinesDepthTest.Create(desc); + desc.PS = shader->GetPS("PS", 3); desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle; failed |= DebugDrawPsTrianglesDepthTest.Create(desc); desc.Wireframe = true; diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index a408bc41a..9e3bc1915 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; } @@ -1210,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); 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); } diff --git a/Source/Engine/Level/Tags.cpp b/Source/Engine/Level/Tags.cpp index b9b3b1f79..f540cb112 100644 --- a/Source/Engine/Level/Tags.cpp +++ b/Source/Engine/Level/Tags.cpp @@ -55,7 +55,21 @@ Tag Tags::Get(const StringView& tagName) return tag; } -bool Tags::HasTag(const Array& list, const Tag& tag) +Array Tags::GetSubTags(Tag parentTag) +{ + Array subTags; + const String& parentTagName = parentTag.ToString(); + for (int32 i = 0; i < List.Count(); i++) + { + const Tag tag(i + 1); + const String& tagName = List[i]; + if (tagName.StartsWith(parentTagName) && parentTag.Index != tag.Index) + subTags.Add(tag); + } + return subTags; +} + +bool Tags::HasTag(const Array& list, const Tag tag) { if (tag.Index == 0) return false; @@ -69,7 +83,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..864adcda7 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. @@ -99,7 +106,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 +114,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. 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) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 32e8d97d3..a61d7577f 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; @@ -1469,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); @@ -1489,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 @@ -1521,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); diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h index b2d2e30eb..2ab97a51b 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; diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp index 597c7695c..905d1a11c 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->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 = 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->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 = 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->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 = 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->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; } // Particle Emitter Function 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()); diff --git a/Source/Engine/Scripting/ScriptingObject.h b/Source/Engine/Scripting/ScriptingObject.h index 6332e13a4..6eb95dd81 100644 --- a/Source/Engine/Scripting/ScriptingObject.h +++ b/Source/Engine/Scripting/ScriptingObject.h @@ -251,3 +251,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/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 3ac96891b..b9706b724 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -26,12 +26,14 @@ 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) { 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); @@ -50,6 +52,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); @@ -117,9 +162,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(); } @@ -136,9 +181,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(); } @@ -156,9 +201,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(); } @@ -171,21 +216,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(); } /// @@ -209,22 +253,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; } @@ -237,22 +279,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; } @@ -265,8 +306,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); @@ -286,14 +326,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(); } /// 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 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); + } } diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp index 597f761cd..37154e09f 100644 --- a/Source/Engine/Visject/VisjectGraph.cpp +++ b/Source/Engine/Visject/VisjectGraph.cpp @@ -1275,16 +1275,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->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 = (Float2)node->Values[0]; - auto b = (Float2)node->Values[1]; + 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) @@ -1294,8 +1294,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->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), @@ -1306,8 +1306,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->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), diff --git a/Source/Shaders/DebugDraw.shader b/Source/Shaders/DebugDraw.shader index 34d1b9108..018129302 100644 --- a/Source/Shaders/DebugDraw.shader +++ b/Source/Shaders/DebugDraw.shader @@ -27,36 +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 PS_DepthTest(VS2PS input) : SV_Target -{ - PerformDepthTest(input.Position); - return PerformFakeLighting(input.Position, input.Color); + return color; }