Merge remote-tracking branch 'origin/master' into 1.6

# Conflicts:
#	.github/workflows/tests.yml
#	Source/Engine/Content/JsonAsset.cs
This commit is contained in:
Wojtek Figat
2023-05-07 19:46:29 +02:00
31 changed files with 603 additions and 311 deletions

BIN
Content/Shaders/DebugDraw.flax (Stored with Git LFS)

Binary file not shown.

View File

@@ -45,7 +45,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
var upperRightCell = new VerticalPanel var upperRightCell = new VerticalPanel
{ {
ClipChildren = false, ClipChildren = false,
Pivot = new Float2(0.0f, 0.0f), Pivot = new Float2(0.00001f, 0.0f),
Offset = new Float2(-labelsWidth, 0), Offset = new Float2(-labelsWidth, 0),
Rotation = -90, Rotation = -90,
Spacing = 0, Spacing = 0,

View File

@@ -145,7 +145,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
else else
{ {
// No localization so initialize with empty table // 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<LocalizedStringTable>(); var table = FlaxEngine.Content.CreateVirtualAsset<LocalizedStringTable>();
table.Locale = culture.Name; table.Locale = culture.Name;
if (!table.Save(path)) if (!table.Save(path))

View File

@@ -10,7 +10,6 @@ using FlaxEditor.Content;
using FlaxEditor.Content.Import; using FlaxEditor.Content.Import;
using FlaxEditor.Content.Settings; using FlaxEditor.Content.Settings;
using FlaxEditor.Content.Thumbnails; using FlaxEditor.Content.Thumbnails;
using FlaxEditor.GUI;
using FlaxEditor.Modules; using FlaxEditor.Modules;
using FlaxEditor.Modules.SourceCodeEditing; using FlaxEditor.Modules.SourceCodeEditing;
using FlaxEditor.Options; using FlaxEditor.Options;
@@ -314,6 +313,7 @@ namespace FlaxEditor
_areModulesInited = true; _areModulesInited = true;
// Preload initial scene asset // Preload initial scene asset
try
{ {
var startupSceneMode = Options.Options.General.StartupSceneMode; var startupSceneMode = Options.Options.General.StartupSceneMode;
if (startupSceneMode == GeneralOptions.StartupSceneModes.LastOpened && !ProjectCache.HasCustomData(ProjectDataLastScene)) if (startupSceneMode == GeneralOptions.StartupSceneModes.LastOpened && !ProjectCache.HasCustomData(ProjectDataLastScene))
@@ -330,12 +330,23 @@ namespace FlaxEditor
} }
case GeneralOptions.StartupSceneModes.LastOpened: case GeneralOptions.StartupSceneModes.LastOpened:
{ {
if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName) && Guid.TryParse(lastSceneIdName, out var lastSceneId)) if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName))
Internal_LoadAsset(ref lastSceneId); {
var lastScenes = JsonSerializer.Deserialize<Guid[]>(lastSceneIdName);
foreach (var scene in lastScenes)
{
var lastScene = scene;
Internal_LoadAsset(ref lastScene);
}
}
break; break;
} }
} }
} }
catch (Exception)
{
// Ignore errors
}
InitializationStart?.Invoke(); InitializationStart?.Invoke();
@@ -408,58 +419,69 @@ namespace FlaxEditor
} }
// Load scene // Load scene
try
// scene cmd line argument
var scene = ContentDatabase.Find(_startupSceneCmdLine);
if (scene is SceneItem)
{ {
Editor.Log("Loading scene specified in command line"); // Scene cmd line argument
Scene.OpenScene(_startupSceneCmdLine); var scene = ContentDatabase.Find(_startupSceneCmdLine);
return; if (scene is SceneItem)
}
// 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)
{ {
Editor.Log("Loading default project scene"); Editor.Log("Loading scene specified in command line");
Scene.OpenScene(defaultSceneId); Scene.OpenScene(_startupSceneCmdLine);
return;
// Use spawn point
Windows.EditWin.Viewport.ViewRay = GameProject.DefaultSceneSpawn;
} }
break; var startupSceneMode = Options.Options.General.StartupSceneMode;
} if (startupSceneMode == GeneralOptions.StartupSceneModes.LastOpened && !ProjectCache.HasCustomData(ProjectDataLastScene))
case GeneralOptions.StartupSceneModes.LastOpened:
{
if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName) && Guid.TryParse(lastSceneIdName, out var lastSceneId))
{ {
var lastScene = ContentDatabase.Find(lastSceneId); // Fallback to default project scene if nothing saved in the cache
if (lastScene is SceneItem) 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"); Editor.Log("Loading default project scene");
Scene.OpenScene(lastSceneId); 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<Guid[]>(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 // Restore view
if (ProjectCache.TryGetCustomData(ProjectDataLastSceneSpawn, out var lastSceneSpawnName)) if (ProjectCache.TryGetCustomData(ProjectDataLastSceneSpawn, out var lastSceneSpawnName))
Windows.EditWin.Viewport.ViewRay = JsonSerializer.Deserialize<Ray>(lastSceneSpawnName); Windows.EditWin.Viewport.ViewRay = JsonSerializer.Deserialize<Ray>(lastSceneSpawnName);
} }
break;
}
} }
break;
} }
catch (Exception)
{
// Ignore errors
} }
} }
@@ -669,11 +691,14 @@ namespace FlaxEditor
// Start exit // Start exit
StateMachine.GoToState<ClosingState>(); StateMachine.GoToState<ClosingState>();
// 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; var lastSceneSpawn = Windows.EditWin.Viewport.ViewRay;
ProjectCache.SetCustomData(ProjectDataLastScene, lastSceneId.ToString()); ProjectCache.SetCustomData(ProjectDataLastScene, JsonSerializer.Serialize(lastSceneIds));
ProjectCache.SetCustomData(ProjectDataLastSceneSpawn, JsonSerializer.Serialize(lastSceneSpawn)); ProjectCache.SetCustomData(ProjectDataLastSceneSpawn, JsonSerializer.Serialize(lastSceneSpawn));
} }

View File

@@ -626,7 +626,16 @@ namespace FlaxEditor.SceneGraph.GUI
{ {
var item = _dragAssets.Objects[i]; var item = _dragAssets.Objects[i];
var actor = item.OnEditorDrop(this); var actor = item.OnEditorDrop(this);
actor.StaticFlags = spawnParent.StaticFlags; if (spawnParent.GetType() != typeof(Scene))
{
// Set all Actors static flags to match parents
List<Actor> childActors = new List<Actor>();
GetActorsTree(childActors, actor);
foreach (var child in childActors)
{
child.StaticFlags = spawnParent.StaticFlags;
}
}
actor.Name = item.ShortName; actor.Name = item.ShortName;
actor.Transform = spawnParent.Transform; actor.Transform = spawnParent.Transform;
ActorNode.Root.Spawn(actor, spawnParent); ActorNode.Root.Spawn(actor, spawnParent);
@@ -667,6 +676,16 @@ namespace FlaxEditor.SceneGraph.GUI
return result; return result;
} }
private void GetActorsTree(List<Actor> 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) private bool ValidateDragActor(ActorNode actorNode)
{ {

View File

@@ -730,12 +730,8 @@ namespace FlaxEditor.Surface.Archetypes
Elements = new[] Elements = new[]
{ {
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0), NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0),
NodeElementArchetype.Factory.Input(0, "Min", true, typeof(float), 1, 0),
NodeElementArchetype.Factory.Text(0, 0, "Min", 30.0f, 18.0f), NodeElementArchetype.Factory.Input(1, "Max", true, typeof(float), 2, 1),
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),
} }
}, },
new NodeArchetype new NodeArchetype
@@ -753,14 +749,8 @@ namespace FlaxEditor.Surface.Archetypes
Elements = new[] Elements = new[]
{ {
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float2), 0), NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float2), 0),
NodeElementArchetype.Factory.Input(0, "Min", true, typeof(Float2), 1, 0),
NodeElementArchetype.Factory.Text(0, 0, "Min", 30.0f, 18.0f), NodeElementArchetype.Factory.Input(1, "Max", true, typeof(Float2), 2, 1),
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),
} }
}, },
new NodeArchetype new NodeArchetype
@@ -778,16 +768,8 @@ namespace FlaxEditor.Surface.Archetypes
Elements = new[] Elements = new[]
{ {
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 0), NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 0),
NodeElementArchetype.Factory.Input(0, "Min", true, typeof(Float3), 1, 0),
NodeElementArchetype.Factory.Text(0, 0, "Min", 30.0f, 18.0f), NodeElementArchetype.Factory.Input(1, "Max", true, typeof(Float3), 2, 1),
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),
} }
}, },
new NodeArchetype new NodeArchetype
@@ -805,18 +787,8 @@ namespace FlaxEditor.Surface.Archetypes
Elements = new[] Elements = new[]
{ {
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float4), 0), NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float4), 0),
NodeElementArchetype.Factory.Input(0, "Min", true, typeof(Float4), 1, 0),
NodeElementArchetype.Factory.Text(0, 0, "Min", 30.0f, 18.0f), NodeElementArchetype.Factory.Input(1, "Max", true, typeof(Float4), 2, 1),
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),
} }
}, },

View File

@@ -47,6 +47,8 @@ AssetReference<MaterialInstance> CustomTextureMaterial;
ModelInstanceEntries InstanceBuffers[static_cast<int32>(IconTypes::MAX)]; ModelInstanceEntries InstanceBuffers[static_cast<int32>(IconTypes::MAX)];
Dictionary<ScriptingTypeHandle, IconTypes> ActorTypeToIconType; Dictionary<ScriptingTypeHandle, IconTypes> ActorTypeToIconType;
Dictionary<ScriptingTypeHandle, AssetReference<Texture>> ActorTypeToTexture; Dictionary<ScriptingTypeHandle, AssetReference<Texture>> ActorTypeToTexture;
Dictionary<ScriptingObjectReference<Actor>, AssetReference<Texture>> ActorToTexture;
Dictionary<AssetReference<Texture>, AssetReference<MaterialBase>> TextureToMaterial;
class ViewportIconsRendererService : public EngineService class ViewportIconsRendererService : public EngineService
{ {
@@ -103,10 +105,18 @@ void ViewportIconsRenderer::AddActor(Actor* actor)
actor->GetSceneRendering()->AddViewportIcon(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) void ViewportIconsRenderer::RemoveActor(Actor* actor)
{ {
CHECK(actor && actor->GetScene()); CHECK(actor && actor->GetScene());
actor->GetSceneRendering()->RemoveViewportIcon(actor); actor->GetSceneRendering()->RemoveViewportIcon(actor);
ActorToTexture.Remove(actor);
} }
void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene* scene, Mesh::DrawInfo& draw) void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene* scene, Mesh::DrawInfo& draw)
@@ -128,7 +138,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene
ScriptingTypeHandle typeHandle = icon->GetTypeHandle(); ScriptingTypeHandle typeHandle = icon->GetTypeHandle();
draw.Buffer = nullptr; draw.Buffer = nullptr;
if (ActorTypeToTexture.TryGet(typeHandle, texture)) if (ActorToTexture.TryGet(icon, texture) || ActorTypeToTexture.TryGet(typeHandle, texture))
{ {
// Use custom texture // Use custom texture
draw.Buffer = &InstanceBuffers[static_cast<int32>(IconTypes::CustomTexture)]; draw.Buffer = &InstanceBuffers[static_cast<int32>(IconTypes::CustomTexture)];
@@ -137,9 +147,20 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene
// Lazy-init (use in-built icon material with custom texture) // Lazy-init (use in-built icon material with custom texture)
draw.Buffer->Setup(1); draw.Buffer->Setup(1);
draw.Buffer->At(0).ReceiveDecals = false; 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<MaterialBase> 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)) else if (ActorTypeToIconType.TryGet(typeHandle, iconType))
{ {
@@ -170,6 +191,8 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor
Matrix m1, m2, world; Matrix m1, m2, world;
BoundingSphere sphere(actor->GetPosition() - renderContext.View.Origin, ICON_RADIUS); BoundingSphere sphere(actor->GetPosition() - renderContext.View.Origin, ICON_RADIUS);
IconTypes iconType; IconTypes iconType;
AssetReference<Texture> texture;
if (frustum.Intersects(sphere) && ActorTypeToIconType.TryGet(actor->GetTypeHandle(), iconType)) if (frustum.Intersects(sphere) && ActorTypeToIconType.TryGet(actor->GetTypeHandle(), iconType))
{ {
// Create world matrix // Create world matrix
@@ -182,7 +205,39 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor
// Draw icon // Draw icon
GeometryDrawStateData drawState; GeometryDrawStateData drawState;
draw.DrawState = &drawState; draw.DrawState = &drawState;
draw.Buffer = &InstanceBuffers[static_cast<int32>(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<int32>(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<MaterialBase> 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<int32>(iconType)];
}
draw.World = &world; draw.World = &world;
draw.Bounds = sphere; draw.Bounds = sphere;
QuadModel->Draw(renderContext, draw); QuadModel->Draw(renderContext, draw);
@@ -196,9 +251,10 @@ bool ViewportIconsRendererService::Init()
{ {
QuadModel = Content::LoadAsyncInternal<Model>(TEXT("Engine/Models/Quad")); QuadModel = Content::LoadAsyncInternal<Model>(TEXT("Engine/Models/Quad"));
#define INIT(type, path) \ #define INIT(type, path) \
InstanceBuffers[static_cast<int32>(IconTypes::type)].Setup(1); \ InstanceBuffers[static_cast<int32>(IconTypes::type)].Setup(1); \
InstanceBuffers[static_cast<int32>(IconTypes::type)][0].ReceiveDecals = false; \ InstanceBuffers[static_cast<int32>(IconTypes::type)][0].ReceiveDecals = false; \
InstanceBuffers[static_cast<int32>(IconTypes::type)][0].Material = Content::LoadAsyncInternal<MaterialInstance>(TEXT(path)) InstanceBuffers[static_cast<int32>(IconTypes::type)][0].ShadowsMode = ShadowsCastingMode::None; \
InstanceBuffers[static_cast<int32>(IconTypes::type)][0].Material = Content::LoadAsyncInternal<MaterialInstance>(TEXT(path))
INIT(PointLight, "Editor/Icons/PointLight"); INIT(PointLight, "Editor/Icons/PointLight");
INIT(DirectionalLight, "Editor/Icons/DirectionalLight"); INIT(DirectionalLight, "Editor/Icons/DirectionalLight");
INIT(EnvironmentProbe, "Editor/Icons/EnvironmentProbe"); INIT(EnvironmentProbe, "Editor/Icons/EnvironmentProbe");
@@ -236,4 +292,6 @@ void ViewportIconsRendererService::Dispose()
for (int32 i = 0; i < ARRAY_COUNT(InstanceBuffers); i++) for (int32 i = 0; i < ARRAY_COUNT(InstanceBuffers); i++)
InstanceBuffers[i].Release(); InstanceBuffers[i].Release();
ActorTypeToIconType.Clear(); ActorTypeToIconType.Clear();
ActorToTexture.Clear();
TextureToMaterial.Clear();
} }

View File

@@ -37,6 +37,13 @@ public:
/// <param name="actor">The actor to register for icon drawing.</param> /// <param name="actor">The actor to register for icon drawing.</param>
API_FUNCTION() static void AddActor(Actor* actor); API_FUNCTION() static void AddActor(Actor* actor);
/// <summary>
/// Adds actor to the viewport icon rendering.
/// </summary>
/// <param name="actor">The actor to register for icon drawing.</param>
/// <param name="iconTexture">The icon texture to draw.</param>
API_FUNCTION() static void AddActorWithTexture(Actor* actor, Texture* iconTexture);
/// <summary> /// <summary>
/// Removes actor from the viewport icon rendering. /// Removes actor from the viewport icon rendering.
/// </summary> /// </summary>

View File

@@ -126,6 +126,10 @@ namespace FlaxEditor.Windows.Assets
_presenter.NoSelectionText = "Failed to load asset. See log for more. " + ex.Message.Replace('\n', ' '); _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 else
{ {
_presenter.NoSelectionText = string.Format("Missing type '{0}'.", dataTypeName); _presenter.NoSelectionText = string.Format("Missing type '{0}'.", dataTypeName);

View File

@@ -489,7 +489,10 @@ namespace FlaxEditor.Windows
/// <param name="item">The item to delete.</param> /// <param name="item">The item to delete.</param>
public void Delete(ContentItem item) public void Delete(ContentItem item)
{ {
Delete(Editor.Instance.Windows.ContentWin.View.Selection); var items = View.Selection;
if (items.Count == 0)
items = new List<ContentItem>() { item };
Delete(items);
} }
/// <summary> /// <summary>
@@ -657,6 +660,11 @@ namespace FlaxEditor.Windows
throw new ArgumentNullException(nameof(proxy)); throw new ArgumentNullException(nameof(proxy));
string name = initialName ?? proxy.NewItemName; 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; ContentFolder parentFolder = CurrentViewFolder;
string parentFolderPath = parentFolder.Path; string parentFolderPath = parentFolder.Path;

View File

@@ -1002,17 +1002,12 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo&
} }
#if ASSETS_LOADING_EXTRA_VERIFICATION #if ASSETS_LOADING_EXTRA_VERIFICATION
// Ensure we have valid asset info
ASSERT(assetInfo.TypeName.HasChars() && assetInfo.Path.HasChars());
// Check if file exists // Check if file exists
if (!FileSystem::FileExists(assetInfo.Path)) if (!FileSystem::FileExists(assetInfo.Path))
{ {
LOG(Error, "Cannot find file '{0}'", assetInfo.Path); LOG(Error, "Cannot find file '{0}'", assetInfo.Path);
return nullptr; return nullptr;
} }
#endif #endif
// Find asset factory based in its type // Find asset factory based in its type

View File

@@ -35,6 +35,11 @@ namespace FlaxEngine
return null; return null;
var dataTypeName = DataTypeName; 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(); var assemblies = Utils.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++) for (int i = 0; i < assemblies.Length; i++)
{ {

View File

@@ -637,9 +637,10 @@ void DebugDrawService::Update()
desc.VS = shader->GetVS("VS"); desc.VS = shader->GetVS("VS");
// Default // Default
desc.PS = shader->GetPS("PS"); desc.PS = shader->GetPS("PS", 0);
desc.PrimitiveTopologyType = PrimitiveTopologyType::Line; desc.PrimitiveTopologyType = PrimitiveTopologyType::Line;
failed |= DebugDrawPsLinesDefault.Create(desc); failed |= DebugDrawPsLinesDefault.Create(desc);
desc.PS = shader->GetPS("PS", 1);
desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle; desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle;
failed |= DebugDrawPsTrianglesDefault.Create(desc); failed |= DebugDrawPsTrianglesDefault.Create(desc);
desc.Wireframe = true; desc.Wireframe = true;
@@ -647,9 +648,10 @@ void DebugDrawService::Update()
// Depth Test // Depth Test
desc.Wireframe = false; desc.Wireframe = false;
desc.PS = shader->GetPS("PS_DepthTest"); desc.PS = shader->GetPS("PS", 2);
desc.PrimitiveTopologyType = PrimitiveTopologyType::Line; desc.PrimitiveTopologyType = PrimitiveTopologyType::Line;
failed |= DebugDrawPsLinesDepthTest.Create(desc); failed |= DebugDrawPsLinesDepthTest.Create(desc);
desc.PS = shader->GetPS("PS", 3);
desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle; desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle;
failed |= DebugDrawPsTrianglesDepthTest.Create(desc); failed |= DebugDrawPsTrianglesDepthTest.Create(desc);
desc.Wireframe = true; desc.Wireframe = true;

View File

@@ -903,12 +903,11 @@ bool LevelImpl::unloadScene(Scene* scene)
bool LevelImpl::unloadScenes() bool LevelImpl::unloadScenes()
{ {
auto scenes = Level::Scenes; 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])) if (unloadScene(scenes[i]))
return true; return true;
} }
return false; 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())); 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 // Fire event
CallSceneEvent(SceneEventType::OnSceneSaved, scene, sceneId); CallSceneEvent(SceneEventType::OnSceneSaved, scene, sceneId);

View File

@@ -131,6 +131,8 @@ public:
Dictionary<Guid, int32> PrefabInstanceIdToDataIndex; Dictionary<Guid, int32> PrefabInstanceIdToDataIndex;
public: public:
typedef Array<PrefabInstanceData> PrefabInstancesData;
/// <summary> /// <summary>
/// Collects all the valid prefab instances to update on prefab data synchronization. /// Collects all the valid prefab instances to update on prefab data synchronization.
/// </summary> /// </summary>
@@ -138,15 +140,15 @@ public:
/// <param name="prefabId">The prefab asset identifier.</param> /// <param name="prefabId">The prefab asset identifier.</param>
/// <param name="defaultInstance">The default instance (prefab internal, can be null).</param> /// <param name="defaultInstance">The default instance (prefab internal, can be null).</param>
/// <param name="targetActor">The target actor (optional actor to skip for counting, can be null).</param> /// <param name="targetActor">The target actor (optional actor to skip for counting, can be null).</param>
static void CollectPrefabInstances(Array<PrefabInstanceData>& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor); static void CollectPrefabInstances(PrefabInstancesData& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor);
/// <summary> /// <summary>
/// Serializes all the prefab instances local changes to restore on prefab data synchronization. /// Serializes all the prefab instances local changes to restore on prefab data synchronization.
/// </summary> /// </summary>
/// <param name="prefabInstancesData">The prefab instances data.</param> /// <param name="prefabInstancesData">The prefab instances data.</param>
/// <param name="tmpBuffer">The temporary json buffer (cleared before and after usage).</param> /// <param name="tmpBuffer">The temporary json buffer (cleared before and after usage).</param>
/// <param name="sceneObjects">The scene objects collection cache (cleared before and after usage).</param> /// <param name="prefab">Source prefab.</param>
static void SerializePrefabInstances(Array<PrefabInstanceData>& prefabInstancesData, rapidjson_flax::StringBuffer& tmpBuffer, SceneObjectsListCacheType& sceneObjects); static void SerializePrefabInstances(PrefabInstancesData& prefabInstancesData, rapidjson_flax::StringBuffer& tmpBuffer, const Prefab* prefab);
/// <summary> /// <summary>
/// Synchronizes the prefab instances by applying changes from the diff data and restoring the local changes captured by SerializePrefabInstances. /// Synchronizes the prefab instances by applying changes from the diff data and restoring the local changes captured by SerializePrefabInstances.
@@ -158,7 +160,7 @@ public:
/// <param name="prefabObjectIdToDiffData">The hash table that maps the prefab object id to json data for the given prefab object.</param> /// <param name="prefabObjectIdToDiffData">The hash table that maps the prefab object id to json data for the given prefab object.</param>
/// <param name="newPrefabObjectIds">The collection of the new prefab objects ids added to prefab during changes synchronization or modifications apply.</param> /// <param name="newPrefabObjectIds">The collection of the new prefab objects ids added to prefab during changes synchronization or modifications apply.</param>
/// <returns>True if failed, otherwise false.</returns> /// <returns>True if failed, otherwise false.</returns>
static bool SynchronizePrefabInstances(Array<PrefabInstanceData>& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, const IdToDataLookupType& prefabObjectIdToDiffData, const Array<Guid>& newPrefabObjectIds); static bool SynchronizePrefabInstances(PrefabInstancesData& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, const IdToDataLookupType& prefabObjectIdToDiffData, const Array<Guid>& newPrefabObjectIds);
/// <summary> /// <summary>
/// Synchronizes the prefab instances by applying changes from the diff data and restoring the local changes captured by SerializePrefabInstances. /// Synchronizes the prefab instances by applying changes from the diff data and restoring the local changes captured by SerializePrefabInstances.
@@ -171,10 +173,10 @@ public:
/// <param name="oldObjectsIds">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).</param> /// <param name="oldObjectsIds">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).</param>
/// <param name="newObjectIds">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).</param> /// <param name="newObjectIds">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).</param>
/// <returns>True if failed, otherwise false.</returns> /// <returns>True if failed, otherwise false.</returns>
static bool SynchronizePrefabInstances(Array<PrefabInstanceData>& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, rapidjson_flax::StringBuffer& tmpBuffer, const Array<Guid>& oldObjectsIds, const Array<Guid>& newObjectIds); static bool SynchronizePrefabInstances(PrefabInstancesData& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, rapidjson_flax::StringBuffer& tmpBuffer, const Array<Guid>& oldObjectsIds, const Array<Guid>& newObjectIds);
}; };
void PrefabInstanceData::CollectPrefabInstances(Array<PrefabInstanceData>& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor) void PrefabInstanceData::CollectPrefabInstances(PrefabInstancesData& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor)
{ {
ScopeLock lock(PrefabManager::PrefabsReferencesLocker); ScopeLock lock(PrefabManager::PrefabsReferencesLocker);
if (PrefabManager::PrefabsReferences.ContainsKey(prefabId)) if (PrefabManager::PrefabsReferences.ContainsKey(prefabId))
@@ -207,11 +209,12 @@ void PrefabInstanceData::CollectPrefabInstances(Array<PrefabInstanceData>& prefa
} }
} }
void PrefabInstanceData::SerializePrefabInstances(Array<PrefabInstanceData>& prefabInstancesData, rapidjson_flax::StringBuffer& tmpBuffer, SceneObjectsListCacheType& sceneObjects) void PrefabInstanceData::SerializePrefabInstances(PrefabInstancesData& prefabInstancesData, rapidjson_flax::StringBuffer& tmpBuffer, const Prefab* prefab)
{ {
if (prefabInstancesData.IsEmpty()) if (prefabInstancesData.IsEmpty())
return; return;
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
sceneObjects->EnsureCapacity(prefab->ObjectsCount * 4);
for (int32 dataIndex = 0; dataIndex < prefabInstancesData.Count(); dataIndex++) for (int32 dataIndex = 0; dataIndex < prefabInstancesData.Count(); dataIndex++)
{ {
auto& instance = prefabInstancesData[dataIndex]; auto& instance = prefabInstancesData[dataIndex];
@@ -253,12 +256,10 @@ void PrefabInstanceData::SerializePrefabInstances(Array<PrefabInstanceData>& pre
instance.PrefabInstanceIdToDataIndex.Add(obj->GetSceneObjectId(), i); instance.PrefabInstanceIdToDataIndex.Add(obj->GetSceneObjectId(), i);
} }
} }
sceneObjects->Clear();
tmpBuffer.Clear(); tmpBuffer.Clear();
} }
bool PrefabInstanceData::SynchronizePrefabInstances(Array<PrefabInstanceData>& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, const IdToDataLookupType& prefabObjectIdToDiffData, const Array<Guid>& newPrefabObjectIds) bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, const IdToDataLookupType& prefabObjectIdToDiffData, const Array<Guid>& newPrefabObjectIds)
{ {
for (int32 instanceIndex = 0; instanceIndex < prefabInstancesData.Count(); instanceIndex++) for (int32 instanceIndex = 0; instanceIndex < prefabInstancesData.Count(); instanceIndex++)
{ {
@@ -492,7 +493,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(Array<PrefabInstanceData>& p
return false; return false;
} }
bool PrefabInstanceData::SynchronizePrefabInstances(Array<PrefabInstanceData>& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, rapidjson_flax::StringBuffer& tmpBuffer, const Array<Guid>& oldObjectsIds, const Array<Guid>& newObjectIds) bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, rapidjson_flax::StringBuffer& tmpBuffer, const Array<Guid>& oldObjectsIds, const Array<Guid>& newObjectIds)
{ {
if (prefabInstancesData.IsEmpty()) if (prefabInstancesData.IsEmpty())
return false; return false;
@@ -588,8 +589,6 @@ bool FindCyclicReferences(Actor* actor, const Guid& prefabRootId)
bool Prefab::ApplyAll(Actor* targetActor) 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(); PROFILE_CPU();
const auto startTime = DateTime::NowUTC(); const auto startTime = DateTime::NowUTC();
@@ -695,22 +694,40 @@ bool Prefab::ApplyAll(Actor* targetActor)
ObjectsRemovalService::Flush(); ObjectsRemovalService::Flush();
// Collect existing prefab instances (this and nested ones) to cache 'before' state used later to restore it
PrefabInstancesData thisPrefabInstancesData;
Array<PrefabInstancesData> 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 // Use internal call to improve shared collections memory sharing
if (ApplyAllInternal(targetActor, true)) if (ApplyAllInternal(targetActor, true, thisPrefabInstancesData))
return true; return true;
SyncNestedPrefabs(allPrefabs); SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData);
const auto endTime = DateTime::NowUTC(); const auto endTime = DateTime::NowUTC();
LOG(Info, "Prefab updated! {0} ms", (int32)(endTime - startTime).GetTotalMilliseconds()); LOG(Info, "Prefab updated! {0} ms", (int32)(endTime - startTime).GetTotalMilliseconds());
return false; return false;
} }
bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab) bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab, PrefabInstancesData& prefabInstancesData)
{ {
PROFILE_CPU_NAMED("Prefab.Apply"); PROFILE_CPU_NAMED("Prefab.Apply");
ScopeLock lock(Locker); ScopeLock lock(Locker);
const auto prefabId = GetID(); const auto prefabId = GetID();
// Gather all scene objects in target instance (reused later) // 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); SceneQuery::GetAllSerializableSceneObjects(targetActor, *targetObjects.Value);
if (PrefabManager::FilterPrefabInstancesToSave(prefabId, *targetObjects.Value)) if (PrefabManager::FilterPrefabInstancesToSave(prefabId, *targetObjects.Value))
return true; return true;
LOG(Info, "Applying prefab changes from actor {0} (total objects count: {2}) to {1}...", targetActor->ToString(), ToString(), targetObjects->Count()); LOG(Info, "Applying prefab changes from actor {0} (total objects count: {2}) to {1}...", targetActor->ToString(), ToString(), targetObjects->Count());
Array<Guid> oldObjectsIds = ObjectsIds; Array<Guid> oldObjectsIds = ObjectsIds;
// Serialize to json data // 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? // TODO: what if user applied prefab with references to the other objects from scene? clear them or what?
JsonTools::ChangeIds(diffDataDocument, objectInstanceIdToPrefabObjectId); JsonTools::ChangeIds(diffDataDocument, objectInstanceIdToPrefabObjectId);
} }
dataBuffer.Clear(); dataBuffer.Clear();
// Fetch all existing prefab instances
Array<PrefabInstanceData> prefabInstancesData;
PrefabInstanceData::CollectPrefabInstances(prefabInstancesData, prefabId, _defaultInstance, targetActor);
// Serialize all prefab instances data (temporary storage)
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get(); CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
sceneObjects->EnsureCapacity(ObjectsCount * 4);
PrefabInstanceData::SerializePrefabInstances(prefabInstancesData, dataBuffer, sceneObjects);
// Destroy default instance and some cache data in Prefab // Destroy default instance and some cache data in Prefab
DeleteDefaultInstance(); DeleteDefaultInstance();
@@ -1204,18 +1210,7 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
return false; return false;
} }
bool Prefab::SyncChanges(const NestedPrefabsList& allPrefabs) bool Prefab::SyncChangesInternal(PrefabInstancesData& prefabInstancesData)
{
// Use internal call to improve shared collections memory sharing
if (SyncChangesInternal())
return true;
SyncNestedPrefabs(allPrefabs);
return false;
}
bool Prefab::SyncChangesInternal()
{ {
PROFILE_CPU_NAMED("Prefab.SyncChanges"); PROFILE_CPU_NAMED("Prefab.SyncChanges");
@@ -1252,10 +1247,10 @@ bool Prefab::SyncChangesInternal()
AutoActorCleanup cleanupDefaultInstance(targetActor); AutoActorCleanup cleanupDefaultInstance(targetActor);
// Apply changes // Apply changes
return ApplyAllInternal(targetActor, false); return ApplyAllInternal(targetActor, false, prefabInstancesData);
} }
void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs) void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData) const
{ {
PROFILE_CPU(); PROFILE_CPU();
LOG(Info, "Updating referencing prefabs"); LOG(Info, "Updating referencing prefabs");
@@ -1274,9 +1269,13 @@ void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs)
continue; 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(); ObjectsRemovalService::Flush();
} }

View File

@@ -82,12 +82,12 @@ public:
private: private:
#if USE_EDITOR #if USE_EDITOR
typedef Array<class PrefabInstanceData> PrefabInstancesData;
typedef Array<AssetReference<Prefab>> NestedPrefabsList; typedef Array<AssetReference<Prefab>> NestedPrefabsList;
bool ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab); bool ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab, PrefabInstancesData& prefabInstancesData);
bool UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, rapidjson_flax::StringBuffer& tmpBuffer); bool UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, rapidjson_flax::StringBuffer& tmpBuffer);
bool SyncChanges(const NestedPrefabsList& allPrefabs); bool SyncChangesInternal(PrefabInstancesData& prefabInstancesData);
bool SyncChangesInternal(); void SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData) const;
void SyncNestedPrefabs(const NestedPrefabsList& allPrefabs);
#endif #endif
void DeleteDefaultInstance(); void DeleteDefaultInstance();

View File

@@ -7,17 +7,14 @@
Actor* SceneQuery::RaycastScene(const Ray& ray) Actor* SceneQuery::RaycastScene(const Ray& ray)
{ {
PROFILE_CPU(); PROFILE_CPU();
#if SCENE_QUERIES_WITH_LOCK #if SCENE_QUERIES_WITH_LOCK
ScopeLock lock(Level::ScenesLock); ScopeLock lock(Level::ScenesLock);
#endif #endif
Actor* target; Actor* target;
Actor* minTarget = nullptr; Actor* minTarget = nullptr;
Real distance; Real distance;
Vector3 normal; Vector3 normal;
Real minDistance = MAX_Real; Real minDistance = MAX_Real;
for (int32 i = 0; i < Level::Scenes.Count(); i++) for (int32 i = 0; i < Level::Scenes.Count(); i++)
{ {
target = Level::Scenes[i]->Intersects(ray, distance, normal); target = Level::Scenes[i]->Intersects(ray, distance, normal);
@@ -30,7 +27,6 @@ Actor* SceneQuery::RaycastScene(const Ray& ray)
} }
} }
} }
return minTarget; return minTarget;
} }
@@ -38,16 +34,13 @@ bool GetAllSceneObjectsQuery(Actor* actor, Array<SceneObject*>& objects)
{ {
objects.Add(actor); objects.Add(actor);
objects.Add(reinterpret_cast<SceneObject* const*>(actor->Scripts.Get()), actor->Scripts.Count()); objects.Add(reinterpret_cast<SceneObject* const*>(actor->Scripts.Get()), actor->Scripts.Count());
return true; return true;
} }
void SceneQuery::GetAllSceneObjects(Actor* root, Array<SceneObject*>& objects) void SceneQuery::GetAllSceneObjects(Actor* root, Array<SceneObject*>& objects)
{ {
ASSERT(root); ASSERT(root);
PROFILE_CPU(); PROFILE_CPU();
Function<bool(Actor*, Array<SceneObject*>&)> func(GetAllSceneObjectsQuery); Function<bool(Actor*, Array<SceneObject*>&)> func(GetAllSceneObjectsQuery);
root->TreeExecuteChildren<Array<SceneObject*>&>(func, objects); root->TreeExecuteChildren<Array<SceneObject*>&>(func, objects);
} }
@@ -56,19 +49,15 @@ bool GetAllSerializableSceneObjectsQuery(Actor* actor, Array<SceneObject*>& obje
{ {
if (EnumHasAnyFlags(actor->HideFlags, HideFlags::DontSave)) if (EnumHasAnyFlags(actor->HideFlags, HideFlags::DontSave))
return false; return false;
objects.Add(actor); objects.Add(actor);
objects.Add(reinterpret_cast<SceneObject* const*>(actor->Scripts.Get()), actor->Scripts.Count()); objects.Add(reinterpret_cast<SceneObject* const*>(actor->Scripts.Get()), actor->Scripts.Count());
return true; return true;
} }
void SceneQuery::GetAllSerializableSceneObjects(Actor* root, Array<SceneObject*>& objects) void SceneQuery::GetAllSerializableSceneObjects(Actor* root, Array<SceneObject*>& objects)
{ {
ASSERT(root); ASSERT(root);
PROFILE_CPU(); PROFILE_CPU();
Function<bool(Actor*, Array<SceneObject*>&)> func(GetAllSerializableSceneObjectsQuery); Function<bool(Actor*, Array<SceneObject*>&)> func(GetAllSerializableSceneObjectsQuery);
root->TreeExecute<Array<SceneObject*>&>(func, objects); root->TreeExecute<Array<SceneObject*>&>(func, objects);
} }
@@ -82,9 +71,7 @@ bool GetAllActorsQuery(Actor* actor, Array<Actor*>& actors)
void SceneQuery::GetAllActors(Actor* root, Array<Actor*>& actors) void SceneQuery::GetAllActors(Actor* root, Array<Actor*>& actors)
{ {
PROFILE_CPU(); PROFILE_CPU();
ASSERT(root); ASSERT(root);
Function<bool(Actor*, Array<Actor*>&)> func(GetAllActorsQuery); Function<bool(Actor*, Array<Actor*>&)> func(GetAllActorsQuery);
root->TreeExecuteChildren<Array<Actor*>&>(func, actors); root->TreeExecuteChildren<Array<Actor*>&>(func, actors);
} }
@@ -92,11 +79,9 @@ void SceneQuery::GetAllActors(Actor* root, Array<Actor*>& actors)
void SceneQuery::GetAllActors(Array<Actor*>& actors) void SceneQuery::GetAllActors(Array<Actor*>& actors)
{ {
PROFILE_CPU(); PROFILE_CPU();
#if SCENE_QUERIES_WITH_LOCK #if SCENE_QUERIES_WITH_LOCK
ScopeLock lock(Level::ScenesLock); ScopeLock lock(Level::ScenesLock);
#endif #endif
for (int32 i = 0; i < Level::Scenes.Count(); i++) for (int32 i = 0; i < Level::Scenes.Count(); i++)
GetAllActors(Level::Scenes[i], actors); GetAllActors(Level::Scenes[i], actors);
} }

View File

@@ -55,7 +55,21 @@ Tag Tags::Get(const StringView& tagName)
return tag; return tag;
} }
bool Tags::HasTag(const Array<Tag>& list, const Tag& tag) Array<Tag> Tags::GetSubTags(Tag parentTag)
{
Array<Tag> 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<Tag>& list, const Tag tag)
{ {
if (tag.Index == 0) if (tag.Index == 0)
return false; return false;
@@ -69,7 +83,7 @@ bool Tags::HasTag(const Array<Tag>& list, const Tag& tag)
return false; return false;
} }
bool Tags::HasTagExact(const Array<Tag>& list, const Tag& tag) bool Tags::HasTagExact(const Array<Tag>& list, const Tag tag)
{ {
if (tag.Index == 0) if (tag.Index == 0)
return false; return false;

View File

@@ -92,6 +92,13 @@ API_CLASS(Static) class FLAXENGINE_API Tags
/// <returns>The tag.</returns> /// <returns>The tag.</returns>
API_FUNCTION() static Tag Get(const StringView& tagName); API_FUNCTION() static Tag Get(const StringView& tagName);
/// <summary>
/// Get all subtags of the specific Tag
/// </summary>
/// <param name="tag"></param>
/// <returns></returns>
API_FUNCTION() static Array<Tag> GetSubTags(Tag tag);
public: public:
/// <summary> /// <summary>
/// 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. /// 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:
/// <param name="list">The tags list to use.</param> /// <param name="list">The tags list to use.</param>
/// <param name="tag">The tag to check.</param> /// <param name="tag">The tag to check.</param>
/// <returns>True if given tag is contained by the list of tags. Returns false for empty list.</returns> /// <returns>True if given tag is contained by the list of tags. Returns false for empty list.</returns>
static bool HasTag(const Array<Tag>& list, const Tag& tag); static bool HasTag(const Array<Tag>& list, const Tag tag);
/// <summary> /// <summary>
/// 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. /// 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:
/// <param name="list">The tags list to use.</param> /// <param name="list">The tags list to use.</param>
/// <param name="tag">The tag to check.</param> /// <param name="tag">The tag to check.</param>
/// <returns>True if given tag is contained by the list of tags. Returns false for empty list.</returns> /// <returns>True if given tag is contained by the list of tags. Returns false for empty list.</returns>
static bool HasTagExact(const Array<Tag>& list, const Tag& tag); static bool HasTagExact(const Array<Tag>& list, const Tag tag);
/// <summary> /// <summary>
/// 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. /// 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.

View File

@@ -15,6 +15,7 @@ REGISTER_JSON_ASSET(LocalizedStringTable, "FlaxEngine.LocalizedStringTable", tru
LocalizedStringTable::LocalizedStringTable(const SpawnParams& params, const AssetInfo* info) LocalizedStringTable::LocalizedStringTable(const SpawnParams& params, const AssetInfo* info)
: JsonAssetBase(params, info) : JsonAssetBase(params, info)
{ {
DataTypeName = TypeName;
} }
void LocalizedStringTable::AddString(const StringView& id, const StringView& value) void LocalizedStringTable::AddString(const StringView& id, const StringView& value)

View File

@@ -861,7 +861,7 @@ void NetworkReplicator::DespawnObject(ScriptingObject* obj)
DeleteNetworkObject(obj); DeleteNetworkObject(obj);
} }
uint32 NetworkReplicator::GetObjectOwnerClientId(ScriptingObject* obj) uint32 NetworkReplicator::GetObjectOwnerClientId(const ScriptingObject* obj)
{ {
uint32 id = NetworkManager::ServerClientId; uint32 id = NetworkManager::ServerClientId;
if (obj) if (obj)
@@ -870,11 +870,23 @@ uint32 NetworkReplicator::GetObjectOwnerClientId(ScriptingObject* obj)
const auto it = Objects.Find(obj->GetID()); const auto it = Objects.Find(obj->GetID());
if (it != Objects.End()) if (it != Objects.End())
id = it->Item.OwnerClientId; id = it->Item.OwnerClientId;
else
{
for (const SpawnItem& item : SpawnQueue)
{
if (item.Object == obj)
{
if (item.HasOwnership)
id = item.OwnerClientId;
break;
}
}
}
} }
return id; return id;
} }
NetworkObjectRole NetworkReplicator::GetObjectRole(ScriptingObject* obj) NetworkObjectRole NetworkReplicator::GetObjectRole(const ScriptingObject* obj)
{ {
NetworkObjectRole role = NetworkObjectRole::None; NetworkObjectRole role = NetworkObjectRole::None;
if (obj) if (obj)
@@ -883,6 +895,18 @@ NetworkObjectRole NetworkReplicator::GetObjectRole(ScriptingObject* obj)
const auto it = Objects.Find(obj->GetID()); const auto it = Objects.Find(obj->GetID());
if (it != Objects.End()) if (it != Objects.End())
role = it->Item.Role; role = it->Item.Role;
else
{
for (const SpawnItem& item : SpawnQueue)
{
if (item.Object == obj)
{
if (item.HasOwnership)
role = item.Role;
break;
}
}
}
} }
return role; return role;
} }
@@ -901,6 +925,18 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli
auto& item = SpawnQueue[i]; auto& item = SpawnQueue[i];
if (item.Object == obj) 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.HasOwnership = true;
item.HierarchicalOwnership = hierarchical; item.HierarchicalOwnership = hierarchical;
item.OwnerClientId = ownerClientId; item.OwnerClientId = ownerClientId;
@@ -1469,8 +1505,8 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl
} }
// Recreate object locally (spawn only root) // Recreate object locally (spawn only root)
ScriptingObject* obj = nullptr;
Actor* prefabInstance = nullptr; Actor* prefabInstance = nullptr;
Array<ScriptingObject*> objects;
if (msgData.PrefabId.IsValid()) if (msgData.PrefabId.IsValid())
{ {
const NetworkReplicatedObject* parent = ResolveObject(rootItem.ParentId); const NetworkReplicatedObject* parent = ResolveObject(rootItem.ParentId);
@@ -1489,7 +1525,7 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl
{ {
if (Objects.Contains(child->GetID())) if (Objects.Contains(child->GetID()))
{ {
obj = FindPrefabObject(child, rootItem.PrefabObjectID); ScriptingObject* obj = FindPrefabObject(child, rootItem.PrefabObjectID);
if (Objects.Contains(obj->GetID())) if (Objects.Contains(obj->GetID()))
{ {
// Other instance with already spawned network object // Other instance with already spawned network object
@@ -1521,46 +1557,77 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl
return; return;
} }
} }
if (!obj)
obj = FindPrefabObject(prefabInstance, rootItem.PrefabObjectID); // Resolve objects from prefab instance
if (!obj) 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()); auto& msgDataItem = msgDataItems[i];
Delete(prefabInstance); ScriptingObject* obj = FindPrefabObject(prefabInstance, msgDataItem.PrefabObjectID);
return; 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 // 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); const ScriptingTypeHandle objectType = Scripting::FindScriptingType(rootItem.ObjectTypeName);
obj = ScriptingObject::NewObject(objectType); ScriptingObject* obj = ScriptingObject::NewObject(objectType);
if (!obj) if (!obj)
{ {
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(rootItem.ObjectTypeName)); NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(rootItem.ObjectTypeName));
return; 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<SceneObject>(obj))
{
Actor* parent = nullptr;
for (int32 j = 0; j < i; j++)
{
if (msgDataItems[j].ObjectId == msgDataItem.ParentId)
{
parent = ScriptingObject::Cast<Actor>(objects[j]);
break;
}
}
if (parent)
sceneObject->SetParent(parent);
}
}
}
} }
// Setup all newly spawned objects // Setup all newly spawned objects
for (int32 i = 0; i < msgData.ItemsCount; i++) for (int32 i = 0; i < msgData.ItemsCount; i++)
{ {
auto& msgDataItem = msgDataItems[i]; auto& msgDataItem = msgDataItems[i];
if (i != 0) ScriptingObject* obj = objects[i];
{
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;
}
}
if (!obj->IsRegistered()) if (!obj->IsRegistered())
obj->RegisterObject(); obj->RegisterObject();
const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId); const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId);

View File

@@ -104,14 +104,14 @@ public:
/// </summary> /// </summary>
/// <param name="obj">The network object.</param> /// <param name="obj">The network object.</param>
/// <returns>The Client Id.</returns> /// <returns>The Client Id.</returns>
API_FUNCTION() static uint32 GetObjectOwnerClientId(ScriptingObject* obj); API_FUNCTION() static uint32 GetObjectOwnerClientId(const ScriptingObject* obj);
/// <summary> /// <summary>
/// Gets the role of the network object used locally (eg. to check if can simulate object). /// Gets the role of the network object used locally (eg. to check if can simulate object).
/// </summary> /// </summary>
/// <param name="obj">The network object.</param> /// <param name="obj">The network object.</param>
/// <returns>The object role.</returns> /// <returns>The object role.</returns>
API_FUNCTION() static NetworkObjectRole GetObjectRole(ScriptingObject* obj); API_FUNCTION() static NetworkObjectRole GetObjectRole(const ScriptingObject* obj);
/// <summary> /// <summary>
/// Checks if the network object is owned locally (thus current client has authority to manage it). /// Checks if the network object is owned locally (thus current client has authority to manage it).
@@ -119,7 +119,7 @@ public:
/// <remarks>Equivalent to GetObjectRole == OwnedAuthoritative.</remarks> /// <remarks>Equivalent to GetObjectRole == OwnedAuthoritative.</remarks>
/// <param name="obj">The network object.</param> /// <param name="obj">The network object.</param>
/// <returns>True if object is owned by this client, otherwise false.</returns> /// <returns>True if object is owned by this client, otherwise false.</returns>
API_FUNCTION() FORCE_INLINE static bool IsObjectOwned(ScriptingObject* obj) API_FUNCTION() FORCE_INLINE static bool IsObjectOwned(const ScriptingObject* obj)
{ {
return GetObjectRole(obj) == NetworkObjectRole::OwnedAuthoritative; return GetObjectRole(obj) == NetworkObjectRole::OwnedAuthoritative;
} }
@@ -130,7 +130,7 @@ public:
/// <remarks>Equivalent to GetObjectRole != Replicated.</remarks> /// <remarks>Equivalent to GetObjectRole != Replicated.</remarks>
/// <param name="obj">The network object.</param> /// <param name="obj">The network object.</param>
/// <returns>True if object is simulated on this client, otherwise false.</returns> /// <returns>True if object is simulated on this client, otherwise false.</returns>
API_FUNCTION() FORCE_INLINE static bool IsObjectSimulated(ScriptingObject* obj) API_FUNCTION() FORCE_INLINE static bool IsObjectSimulated(const ScriptingObject* obj)
{ {
return GetObjectRole(obj) != NetworkObjectRole::Replicated; return GetObjectRole(obj) != NetworkObjectRole::Replicated;
} }
@@ -141,7 +141,7 @@ public:
/// <remarks>Equivalent to (GetObjectRole == Replicated or GetObjectRole == ReplicatedAutonomous).</remarks> /// <remarks>Equivalent to (GetObjectRole == Replicated or GetObjectRole == ReplicatedAutonomous).</remarks>
/// <param name="obj">The network object.</param> /// <param name="obj">The network object.</param>
/// <returns>True if object is simulated on this client, otherwise false.</returns> /// <returns>True if object is simulated on this client, otherwise false.</returns>
API_FUNCTION() FORCE_INLINE static bool IsObjectReplicated(ScriptingObject* obj) API_FUNCTION() FORCE_INLINE static bool IsObjectReplicated(const ScriptingObject* obj)
{ {
const NetworkObjectRole role = GetObjectRole(obj); const NetworkObjectRole role = GetObjectRole(obj);
return role == NetworkObjectRole::Replicated || role == NetworkObjectRole::ReplicatedSimulated; return role == NetworkObjectRole::Replicated || role == NetworkObjectRole::ReplicatedSimulated;

View File

@@ -388,33 +388,33 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va
// Random Float Range // Random Float Range
case 213: case 213:
{ {
auto& a = node->Values[0].AsFloat; auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat();
auto& b = node->Values[1].AsFloat; auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat();
value = writeLocal(VariantType::Float, String::Format(TEXT("lerp({0}, {1}, RAND)"), a, b), node); value = writeLocal(VariantType::Float, String::Format(TEXT("lerp({0}, {1}, RAND)"), a.Value, b.Value), node);
break; break;
} }
// Random Vector2 Range // Random Vector2 Range
case 214: case 214:
{ {
auto& a = node->Values[0].AsFloat2(); auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat2();
auto& b = node->Values[1].AsFloat2(); auto b = tryGetValue(node->TryGetBox(2), 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); 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; break;
} }
// Random Vector3 Range // Random Vector3 Range
case 215: case 215:
{ {
auto& a = node->Values[0].AsFloat3(); auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat3();
auto& b = node->Values[1].AsFloat3(); auto b = tryGetValue(node->TryGetBox(2), 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); 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; break;
} }
// Random Vector4 Range // Random Vector4 Range
case 216: case 216:
{ {
auto& a = node->Values[0].AsFloat4(); auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat4();
auto& b = node->Values[1].AsFloat4(); auto b = tryGetValue(node->TryGetBox(2), 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); 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; break;
} }
// Particle Emitter Function // Particle Emitter Function

View File

@@ -212,6 +212,12 @@ Asset::LoadResult ParticleSystem::load()
int32 version; int32 version;
stream.ReadInt32(&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) switch (version)
{ {
case 1: case 1:
@@ -281,13 +287,7 @@ Asset::LoadResult ParticleSystem::load()
stream.ReadInt32(&key.First); stream.ReadInt32(&key.First);
stream.Read(key.Second); stream.Read(key.Second);
stream.ReadCommonValue(&value); stream.ReadCommonValue(&value);
SKIP_UNUSED_PARAM_OVERRIDE();
#if USE_EDITOR
// Skip unused parameters
if (key.First < 0 || key.First >= Emitters.Count() || Emitters[key.First]->Graph.GetParameter(key.Second) == nullptr)
continue;
#endif
EmittersParametersOverrides.Add(key, Variant(value)); EmittersParametersOverrides.Add(key, Variant(value));
} }
} }
@@ -361,13 +361,7 @@ Asset::LoadResult ParticleSystem::load()
stream.ReadInt32(&key.First); stream.ReadInt32(&key.First);
stream.Read(key.Second); stream.Read(key.Second);
stream.ReadCommonValue(&value); stream.ReadCommonValue(&value);
SKIP_UNUSED_PARAM_OVERRIDE();
#if USE_EDITOR
// Skip unused parameters
if (key.First < 0 || key.First >= Emitters.Count() || Emitters[key.First]->Graph.GetParameter(key.Second) == nullptr)
continue;
#endif
EmittersParametersOverrides[key] = Variant(value); EmittersParametersOverrides[key] = Variant(value);
} }
} }
@@ -440,13 +434,7 @@ Asset::LoadResult ParticleSystem::load()
stream.ReadInt32(&key.First); stream.ReadInt32(&key.First);
stream.Read(key.Second); stream.Read(key.Second);
stream.ReadVariant(&value); stream.ReadVariant(&value);
SKIP_UNUSED_PARAM_OVERRIDE();
#if USE_EDITOR
// Skip unused parameters
if (key.First < 0 || key.First >= Emitters.Count() || Emitters[key.First]->Graph.GetParameter(key.Second) == nullptr)
continue;
#endif
EmittersParametersOverrides[key] = value; EmittersParametersOverrides[key] = value;
} }
} }
@@ -457,6 +445,7 @@ Asset::LoadResult ParticleSystem::load()
LOG(Warning, "Unknown timeline version {0}.", version); LOG(Warning, "Unknown timeline version {0}.", version);
return LoadResult::InvalidData; return LoadResult::InvalidData;
} }
#undef SKIP_UNUSED_PARAM_OVERRIDE
#if !BUILD_RELEASE #if !BUILD_RELEASE
_debugName = StringUtils::GetFileNameWithoutExtension(GetPath()); _debugName = StringUtils::GetFileNameWithoutExtension(GetPath());

View File

@@ -251,3 +251,5 @@ API_CLASS(InBuild) class FLAXENGINE_API PersistentScriptingObject : public Scrip
public: public:
PersistentScriptingObject(const SpawnParams& params); PersistentScriptingObject(const SpawnParams& params);
}; };
extern FLAXENGINE_API class ScriptingObject* FindObject(const Guid& id, class MClass* type);

View File

@@ -37,7 +37,7 @@ namespace FlaxEngine.Json
{ {
// Skip serialization as reference id for the root object serialization (eg. Script) // Skip serialization as reference id for the root object serialization (eg. Script)
var cache = JsonSerializer.Current.Value; 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; return false;
} }

View File

@@ -26,12 +26,14 @@ namespace FlaxEngine.Json
public JsonSerializerInternalWriter SerializerWriter; public JsonSerializerInternalWriter SerializerWriter;
public UnmanagedMemoryStream MemoryStream; public UnmanagedMemoryStream MemoryStream;
public StreamReader Reader; public StreamReader Reader;
public bool IsDuringSerialization; public bool IsWriting;
public bool IsReading;
public unsafe SerializerCache(JsonSerializerSettings settings) public unsafe SerializerCache(JsonSerializerSettings settings)
{ {
JsonSerializer = Newtonsoft.Json.JsonSerializer.CreateDefault(settings); JsonSerializer = Newtonsoft.Json.JsonSerializer.CreateDefault(settings);
JsonSerializer.Formatting = Formatting.Indented; JsonSerializer.Formatting = Formatting.Indented;
JsonSerializer.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
StringBuilder = new StringBuilder(256); StringBuilder = new StringBuilder(256);
StringWriter = new StringWriter(StringBuilder, CultureInfo.InvariantCulture); StringWriter = new StringWriter(StringBuilder, CultureInfo.InvariantCulture);
SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer); SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer);
@@ -50,6 +52,49 @@ namespace FlaxEngine.Json
DateFormatString = JsonSerializer.DateFormatString, 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); internal static JsonSerializerSettings Settings = CreateDefaultSettings(false);
@@ -117,9 +162,9 @@ namespace FlaxEngine.Json
var cache = isManagedOnly ? CacheManagedOnly.Value : Cache.Value; var cache = isManagedOnly ? CacheManagedOnly.Value : Cache.Value;
Current.Value = cache; Current.Value = cache;
cache.StringBuilder.Clear(); cache.WriteBegin();
cache.IsDuringSerialization = true;
cache.SerializerWriter.Serialize(cache.JsonWriter, obj, type); cache.SerializerWriter.Serialize(cache.JsonWriter, obj, type);
cache.WriteEnd();
return cache.StringBuilder.ToString(); return cache.StringBuilder.ToString();
} }
@@ -136,9 +181,9 @@ namespace FlaxEngine.Json
var cache = isManagedOnly ? CacheManagedOnly.Value : Cache.Value; var cache = isManagedOnly ? CacheManagedOnly.Value : Cache.Value;
Current.Value = cache; Current.Value = cache;
cache.StringBuilder.Clear(); cache.WriteBegin();
cache.IsDuringSerialization = true;
cache.SerializerWriter.Serialize(cache.JsonWriter, obj, type); cache.SerializerWriter.Serialize(cache.JsonWriter, obj, type);
cache.WriteEnd();
return cache.StringBuilder.ToString(); return cache.StringBuilder.ToString();
} }
@@ -156,9 +201,9 @@ namespace FlaxEngine.Json
var cache = isManagedOnly ? CacheManagedOnly.Value : Cache.Value; var cache = isManagedOnly ? CacheManagedOnly.Value : Cache.Value;
Current.Value = cache; Current.Value = cache;
cache.StringBuilder.Clear(); cache.WriteBegin();
cache.IsDuringSerialization = true;
cache.SerializerWriter.SerializeDiff(cache.JsonWriter, obj, type, other); cache.SerializerWriter.SerializeDiff(cache.JsonWriter, obj, type, other);
cache.WriteEnd();
return cache.StringBuilder.ToString(); return cache.StringBuilder.ToString();
} }
@@ -171,21 +216,20 @@ namespace FlaxEngine.Json
public static void Deserialize(object input, string json) public static void Deserialize(object input, string json)
{ {
var cache = Cache.Value; var cache = Cache.Value;
cache.IsDuringSerialization = false; cache.ReadBegin();
Current.Value = cache;
using (JsonReader reader = new JsonTextReader(new StringReader(json))) using (JsonReader reader = new JsonTextReader(new StringReader(json)))
{ {
cache.JsonSerializer.Populate(reader, input); cache.JsonSerializer.Populate(reader, input);
if (cache.JsonSerializer.CheckAdditionalContent)
if (!cache.JsonSerializer.CheckAdditionalContent)
return;
while (reader.Read())
{ {
if (reader.TokenType != JsonToken.Comment) while (reader.Read())
throw new Exception("Additional text found in JSON string after finishing deserializing object."); {
if (reader.TokenType != JsonToken.Comment)
throw new Exception("Additional text found in JSON string after finishing deserializing object.");
}
} }
} }
cache.ReadEnd();
} }
/// <summary> /// <summary>
@@ -209,22 +253,20 @@ namespace FlaxEngine.Json
{ {
object result; object result;
var cache = Cache.Value; var cache = Cache.Value;
cache.IsDuringSerialization = false; cache.ReadBegin();
Current.Value = cache;
using (JsonReader reader = new JsonTextReader(new StringReader(json))) using (JsonReader reader = new JsonTextReader(new StringReader(json)))
{ {
result = cache.JsonSerializer.Deserialize(reader, objectType); result = cache.JsonSerializer.Deserialize(reader, objectType);
if (cache.JsonSerializer.CheckAdditionalContent)
if (!cache.JsonSerializer.CheckAdditionalContent)
return result;
while (reader.Read())
{ {
if (reader.TokenType != JsonToken.Comment) while (reader.Read())
throw new Exception("Additional text found in JSON string after finishing deserializing object."); {
if (reader.TokenType != JsonToken.Comment)
throw new Exception("Additional text found in JSON string after finishing deserializing object.");
}
} }
} }
cache.ReadEnd();
return result; return result;
} }
@@ -237,22 +279,21 @@ namespace FlaxEngine.Json
{ {
object result; object result;
var cache = Cache.Value; var cache = Cache.Value;
cache.IsDuringSerialization = false; cache.ReadBegin();
Current.Value = cache;
using (JsonReader reader = new JsonTextReader(new StringReader(json))) using (JsonReader reader = new JsonTextReader(new StringReader(json)))
{ {
result = cache.JsonSerializer.Deserialize(reader); result = cache.JsonSerializer.Deserialize(reader);
if (!cache.JsonSerializer.CheckAdditionalContent) if (cache.JsonSerializer.CheckAdditionalContent)
return result;
while (reader.Read())
{ {
if (reader.TokenType != JsonToken.Comment) while (reader.Read())
throw new Exception("Additional text found in JSON string after finishing deserializing object."); {
if (reader.TokenType != JsonToken.Comment)
throw new Exception("Additional text found in JSON string after finishing deserializing object.");
}
} }
} }
cache.ReadEnd();
return result; return result;
} }
@@ -265,8 +306,7 @@ namespace FlaxEngine.Json
public static unsafe void Deserialize(object input, byte* jsonBuffer, int jsonLength) public static unsafe void Deserialize(object input, byte* jsonBuffer, int jsonLength)
{ {
var cache = Cache.Value; var cache = Cache.Value;
cache.IsDuringSerialization = false; cache.ReadBegin();
Current.Value = cache;
/*// Debug json string reading /*// Debug json string reading
cache.MemoryStream.Initialize(jsonBuffer, jsonLength); cache.MemoryStream.Initialize(jsonBuffer, jsonLength);
@@ -286,14 +326,16 @@ namespace FlaxEngine.Json
{ {
cache.JsonSerializer.Populate(jsonReader, input); cache.JsonSerializer.Populate(jsonReader, input);
} }
if (cache.JsonSerializer.CheckAdditionalContent)
if (!cache.JsonSerializer.CheckAdditionalContent)
return;
while (jsonReader.Read())
{ {
if (jsonReader.TokenType != JsonToken.Comment) while (jsonReader.Read())
throw new Exception("Additional text found in JSON string after finishing deserializing object."); {
if (jsonReader.TokenType != JsonToken.Comment)
throw new Exception("Additional text found in JSON string after finishing deserializing object.");
}
} }
cache.ReadEnd();
} }
/// <summary> /// <summary>

View File

@@ -456,7 +456,7 @@ namespace Serialization
Guid id; Guid id;
Deserialize(stream, id, modifier); Deserialize(stream, id, modifier);
modifier->IdsMapping.TryGet(id, id); modifier->IdsMapping.TryGet(id, id);
v = (T*)FindObject(id, T::GetStaticClass()); v = (T*)::FindObject(id, T::GetStaticClass());
} }
// Scripting Object Reference // Scripting Object Reference

View File

@@ -245,4 +245,89 @@ TEST_CASE("Prefabs")
Content::DeleteAsset(prefabA); Content::DeleteAsset(prefabA);
Content::DeleteAsset(prefabB); 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<Prefab> testActorPrefab = Content::CreateVirtualAsset<Prefab>();
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<Prefab> nestedActorPrefab = Content::CreateVirtualAsset<Prefab>();
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<Actor> testActor = PrefabManager::SpawnPrefab(testActorPrefab);
ScriptingObjectReference<Actor> 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);
}
} }

View File

@@ -1275,16 +1275,16 @@ void VisjectExecutor::ProcessGroupParticles(Box* box, Node* node, Value& value)
// Random Float Range // Random Float Range
case 213: case 213:
{ {
auto& a = node->Values[0].AsFloat; auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat;
auto& b = node->Values[1].AsFloat; auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat;
value = Math::Lerp(a, b, RAND); value = Math::Lerp(a, b, RAND);
break; break;
} }
// Random Vector2 Range // Random Vector2 Range
case 214: case 214:
{ {
auto a = (Float2)node->Values[0]; auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat2();
auto b = (Float2)node->Values[1]; auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat2();
value = Float2( value = Float2(
Math::Lerp(a.X, b.X, RAND), Math::Lerp(a.X, b.X, RAND),
Math::Lerp(a.Y, b.Y, RAND) Math::Lerp(a.Y, b.Y, RAND)
@@ -1294,8 +1294,8 @@ void VisjectExecutor::ProcessGroupParticles(Box* box, Node* node, Value& value)
// Random Vector3 Range // Random Vector3 Range
case 215: case 215:
{ {
auto a = (Float3)node->Values[0]; auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat3();
auto b = (Float3)node->Values[1]; auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat3();
value = Float3( value = Float3(
Math::Lerp(a.X, b.X, RAND), Math::Lerp(a.X, b.X, RAND),
Math::Lerp(a.Y, b.Y, RAND), Math::Lerp(a.Y, b.Y, RAND),
@@ -1306,8 +1306,8 @@ void VisjectExecutor::ProcessGroupParticles(Box* box, Node* node, Value& value)
// Random Vector4 Range // Random Vector4 Range
case 216: case 216:
{ {
auto a = (Float4)node->Values[0]; auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat4();
auto b = (Float4)node->Values[1]; auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat4();
value = Float4( value = Float4(
Math::Lerp(a.X, b.X, RAND), Math::Lerp(a.X, b.X, RAND),
Math::Lerp(a.Y, b.Y, RAND), Math::Lerp(a.Y, b.Y, RAND),

View File

@@ -27,36 +27,31 @@ VS2PS VS(float3 Position : POSITION, float4 Color : COLOR)
return output; 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 // Depth test manually if compositing editor primitives
FLATTEN FLATTEN
if (EnableDepthTest) if (EnableDepthTest)
{ {
float sceneDepthDeviceZ = SceneDepthTexture.Load(int3(svPosition.xy, 0)).r; float sceneDepthDeviceZ = SceneDepthTexture.Load(int3(input.Position.xy, 0)).r;
float interpolatedDeviceZ = svPosition.z; float interpolatedDeviceZ = input.Position.z;
clip(sceneDepthDeviceZ - interpolatedDeviceZ); 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 // 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)); float3 n = normalize(float3(-ddx(depth), -ddy(depth), 1.0f));
c.rgb *= saturate(abs(dot(n, float3(0, 1, 0))) + 0.5f); color.rgb *= saturate(abs(dot(n, float3(0, 1, 0))) + 0.5f);
return c; #endif
}
META_PS(true, FEATURE_LEVEL_ES2) return color;
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);
} }