Merge remote-tracking branch 'origin/master'

This commit is contained in:
Wojtek Figat
2025-11-09 23:22:37 +01:00
20 changed files with 194 additions and 111 deletions

View File

@@ -4,7 +4,7 @@
"Major": 1, "Major": 1,
"Minor": 11, "Minor": 11,
"Revision": 0, "Revision": 0,
"Build": 6803 "Build": 6804
}, },
"Company": "Flax", "Company": "Flax",
"Copyright": "Copyright (c) 2012-2025 Wojciech Figat. All rights reserved.", "Copyright": "Copyright (c) 2012-2025 Wojciech Figat. All rights reserved.",

View File

@@ -1,6 +1,7 @@
// Copyright (c) Wojciech Figat. All rights reserved. // Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Dedicated namespace FlaxEditor.CustomEditors.Dedicated
{ {
@@ -11,7 +12,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
[CustomEditor(typeof(EnvironmentProbe)), DefaultEditor] [CustomEditor(typeof(EnvironmentProbe)), DefaultEditor]
public class EnvironmentProbeEditor : ActorEditor public class EnvironmentProbeEditor : ActorEditor
{ {
private FlaxEngine.GUI.Button _bake; private Button _bake;
/// <inheritdoc /> /// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
@@ -20,8 +21,9 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (Values.HasDifferentTypes == false) if (Values.HasDifferentTypes == false)
{ {
layout.Space(10); var group = layout.Group("Bake");
_bake = layout.Button("Bake").Button; group.Panel.ItemsMargin = new Margin(Utilities.Constants.UIMargin * 2);
_bake = group.Button("Bake").Button;
_bake.Clicked += BakeButtonClicked; _bake.Clicked += BakeButtonClicked;
} }
} }

View File

@@ -1,6 +1,7 @@
// Copyright (c) Wojciech Figat. All rights reserved. // Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Dedicated namespace FlaxEditor.CustomEditors.Dedicated
{ {
@@ -19,8 +20,9 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (Values.HasDifferentTypes == false) if (Values.HasDifferentTypes == false)
{ {
// Add 'Bake' button // Add 'Bake' button
layout.Space(10); var group = layout.Group("Bake");
var button = layout.Button("Bake"); group.Panel.ItemsMargin = new Margin(Utilities.Constants.UIMargin * 2);
var button = group.Button("Bake");
button.Button.Clicked += BakeButtonClicked; button.Button.Clicked += BakeButtonClicked;
} }
} }

View File

@@ -650,7 +650,7 @@ namespace FlaxEditor.CustomEditors.Editors
panel.Panel.Size = new Float2(0, 18); panel.Panel.Size = new Float2(0, 18);
panel.Panel.Margin = new Margin(0, 0, Utilities.Constants.UIMargin, 0); panel.Panel.Margin = new Margin(0, 0, Utilities.Constants.UIMargin, 0);
var removeButton = panel.Button("-", "Remove the last item"); var removeButton = panel.Button("-", "Remove the last item.");
removeButton.Button.Size = new Float2(16, 16); removeButton.Button.Size = new Float2(16, 16);
removeButton.Button.Enabled = size > _minCount; removeButton.Button.Enabled = size > _minCount;
removeButton.Button.AnchorPreset = AnchorPresets.TopRight; removeButton.Button.AnchorPreset = AnchorPresets.TopRight;
@@ -661,7 +661,7 @@ namespace FlaxEditor.CustomEditors.Editors
Resize(Count - 1); Resize(Count - 1);
}; };
var addButton = panel.Button("+", "Add a new item"); var addButton = panel.Button("+", "Add a new item.");
addButton.Button.Size = new Float2(16, 16); addButton.Button.Size = new Float2(16, 16);
addButton.Button.Enabled = (!NotNullItems || size > 0) && size < _maxCount; addButton.Button.Enabled = (!NotNullItems || size > 0) && size < _maxCount;
addButton.Button.AnchorPreset = AnchorPresets.TopRight; addButton.Button.AnchorPreset = AnchorPresets.TopRight;

View File

@@ -104,7 +104,7 @@ namespace FlaxEditor.CustomEditors.Editors
public event Action<TypePickerControl> TypePickerValueChanged; public event Action<TypePickerControl> TypePickerValueChanged;
/// <summary> /// <summary>
/// The custom callback for types validation. Cane be used to implement a rule for types to pick. /// The custom callback for types validation. Can be used to implement a rule for types to pick.
/// </summary> /// </summary>
public Func<ScriptType, bool> CheckValid; public Func<ScriptType, bool> CheckValid;
@@ -353,7 +353,13 @@ namespace FlaxEditor.CustomEditors.Editors
} }
if (!string.IsNullOrEmpty(typeReference.CheckMethod)) if (!string.IsNullOrEmpty(typeReference.CheckMethod))
{ {
var parentType = ParentEditor.Values[0].GetType(); var parentEditor = ParentEditor;
// Find actual parent editor if parent editor is collection editor
while (parentEditor.GetType().IsAssignableTo(typeof(CollectionEditor)))
parentEditor = parentEditor.ParentEditor;
var parentType = parentEditor.Values[0].GetType();
var method = parentType.GetMethod(typeReference.CheckMethod, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); var method = parentType.GetMethod(typeReference.CheckMethod, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (method != null) if (method != null)
{ {

View File

@@ -726,7 +726,7 @@ namespace FlaxEditor.Surface.Archetypes
private void OnSurfaceMouseUp(ref Float2 mouse, MouseButton buttons, ref bool handled) private void OnSurfaceMouseUp(ref Float2 mouse, MouseButton buttons, ref bool handled)
{ {
if (handled) if (handled || Surface.Context != Context)
return; return;
// Check click over the connection // Check click over the connection
@@ -751,7 +751,7 @@ namespace FlaxEditor.Surface.Archetypes
private void OnSurfaceMouseDoubleClick(ref Float2 mouse, MouseButton buttons, ref bool handled) private void OnSurfaceMouseDoubleClick(ref Float2 mouse, MouseButton buttons, ref bool handled)
{ {
if (handled) if (handled || Surface.Context != Context)
return; return;
// Check double click over the connection // Check double click over the connection

View File

@@ -2,11 +2,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.Loader;
using System.Runtime.Serialization.Formatters.Binary;
using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Editors; using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
@@ -18,6 +15,7 @@ namespace FlaxEditor.Surface
class AttributesEditor : ContextMenuBase class AttributesEditor : ContextMenuBase
{ {
private CustomEditorPresenter _presenter; private CustomEditorPresenter _presenter;
private Proxy _proxy;
private byte[] _oldData; private byte[] _oldData;
private class Proxy private class Proxy
@@ -72,11 +70,11 @@ namespace FlaxEditor.Surface
/// Initializes a new instance of the <see cref="AttributesEditor"/> class. /// Initializes a new instance of the <see cref="AttributesEditor"/> class.
/// </summary> /// </summary>
/// <param name="attributes">The attributes list to edit.</param> /// <param name="attributes">The attributes list to edit.</param>
/// <param name="attributeType">The allowed attribute types to use.</param> /// <param name="attributeTypes">The allowed attribute types to use.</param>
public AttributesEditor(Attribute[] attributes, IList<Type> attributeType) public AttributesEditor(Attribute[] attributes, IList<Type> attributeTypes)
{ {
// Context menu dimensions // Context menu dimensions
const float width = 340.0f; const float width = 375.0f;
const float height = 370.0f; const float height = 370.0f;
Size = new Float2(width, height); Size = new Float2(width, height);
@@ -88,61 +86,68 @@ namespace FlaxEditor.Surface
Parent = this Parent = this
}; };
// Buttons // Ok and Cancel Buttons
float buttonsWidth = (width - 16.0f) * 0.5f; float buttonsWidth = (width - 12.0f) * 0.5f;
float buttonsHeight = 20.0f; float buttonsHeight = 20.0f;
var cancelButton = new Button(4.0f, title.Bottom + 4.0f, buttonsWidth, buttonsHeight) var okButton = new Button(4.0f, Bottom - 4.0f - buttonsHeight, buttonsWidth, buttonsHeight)
{
Text = "Ok",
Parent = this
};
okButton.Clicked += OnOkButtonClicked;
var cancelButton = new Button(okButton.Right + 4.0f, okButton.Y, buttonsWidth, buttonsHeight)
{ {
Text = "Cancel", Text = "Cancel",
Parent = this Parent = this
}; };
cancelButton.Clicked += Hide; cancelButton.Clicked += Hide;
var okButton = new Button(cancelButton.Right + 4.0f, cancelButton.Y, buttonsWidth, buttonsHeight)
{
Text = "OK",
Parent = this
};
okButton.Clicked += OnOkButtonClicked;
// Actual panel // Actual panel used to display attributes
var panel1 = new Panel(ScrollBars.Vertical) var panel1 = new Panel(ScrollBars.Vertical)
{ {
Bounds = new Rectangle(0, okButton.Bottom + 4.0f, width, height - okButton.Bottom - 2.0f), Bounds = new Rectangle(0, title.Bottom + 4.0f, width, height - buttonsHeight - title.Height - 14.0f),
Parent = this Parent = this
}; };
var editor = new CustomEditorPresenter(null); var editor = new CustomEditorPresenter(null);
editor.Panel.AnchorPreset = AnchorPresets.HorizontalStretchTop; editor.Panel.AnchorPreset = AnchorPresets.HorizontalStretchTop;
editor.Panel.IsScrollable = true; editor.Panel.IsScrollable = true;
editor.Panel.Parent = panel1; editor.Panel.Parent = panel1;
editor.Panel.Tag = attributeType; editor.Panel.Tag = attributeTypes;
_presenter = editor; _presenter = editor;
// Cache 'previous' state to check if attributes were edited after operation // Cache 'previous' state to check if attributes were edited after operation
_oldData = SurfaceMeta.GetAttributesData(attributes); _oldData = SurfaceMeta.GetAttributesData(attributes);
editor.Select(new Proxy _proxy = new Proxy
{ {
Value = attributes, Value = attributes,
}); };
editor.Select(_proxy);
_presenter.Modified += OnPresenterModified;
OnPresenterModified();
}
private void OnPresenterModified()
{
if (_proxy.Value.Length == 0)
{
var label = _presenter.Label("No attributes.\nPress the \"+\" button to add a new one and then select an attribute type using the \"Type\" dropdown.", TextAlignment.Center);
label.Label.Wrapping = TextWrapping.WrapWords;
label.Control.Height = 35f;
label.Label.Margin = new Margin(10f);
label.Label.TextColor = label.Label.TextColorHighlighted = Style.Current.ForegroundGrey;
}
} }
private void OnOkButtonClicked() private void OnOkButtonClicked()
{ {
var newValue = ((Proxy)_presenter.Selection[0]).Value; var newValue = ((Proxy)_presenter.Selection[0]).Value;
for (int i = 0; i < newValue.Length; i++) newValue = newValue.Where(v => v != null).ToArray();
{
if (newValue[i] == null)
{
MessageBox.Show("One of the attributes is null. Please set it to the valid object.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
var newData = SurfaceMeta.GetAttributesData(newValue); var newData = SurfaceMeta.GetAttributesData(newValue);
if (!_oldData.SequenceEqual(newData)) if (!_oldData.SequenceEqual(newData))
{
Edited?.Invoke(newValue); Edited?.Invoke(newValue);
}
Hide(); Hide();
} }
@@ -183,7 +188,9 @@ namespace FlaxEditor.Surface
{ {
_presenter = null; _presenter = null;
_oldData = null; _oldData = null;
_proxy = null;
Edited = null; Edited = null;
_presenter.Modified -= OnPresenterModified;
base.OnDestroy(); base.OnDestroy();
} }

View File

@@ -214,22 +214,25 @@ namespace FlaxEditor.Surface
if (!_isRenaming) if (!_isRenaming)
Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center);
// Close button if (Surface.CanEdit)
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
// Color button
Render2D.DrawSprite(style.Settings, _colorButtonRect, _colorButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
// Check if is resizing
if (_isResizing)
{ {
// Draw overlay // Close button
Render2D.FillRectangle(_resizeButtonRect, style.Selection); Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
}
// Resize button // Color button
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey); Render2D.DrawSprite(style.Settings, _colorButtonRect, _colorButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
// Check if is resizing
if (_isResizing)
{
// Draw overlay
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
}
// Resize button
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
}
// Selection outline // Selection outline
if (_isSelected) if (_isSelected)

View File

@@ -140,7 +140,7 @@ namespace FlaxEditor.Windows
} }
private string _cacheFolder; private string _cacheFolder;
private Guid _assetId; private AssetItem _item;
private Surface _surface; private Surface _surface;
private Label _loadingLabel; private Label _loadingLabel;
private CancellationTokenSource _token; private CancellationTokenSource _token;
@@ -163,13 +163,13 @@ namespace FlaxEditor.Windows
public AssetReferencesGraphWindow(Editor editor, AssetItem assetItem) public AssetReferencesGraphWindow(Editor editor, AssetItem assetItem)
: base(editor, false, ScrollBars.None) : base(editor, false, ScrollBars.None)
{ {
Title = assetItem.ShortName + " References"; _item = assetItem;
Title = _item.ShortName + " References";
_tempFolder = StringUtils.NormalizePath(Path.GetDirectoryName(Globals.TemporaryFolder)); _tempFolder = StringUtils.NormalizePath(Path.GetDirectoryName(Globals.TemporaryFolder));
_cacheFolder = Path.Combine(Globals.ProjectCacheFolder, "References"); _cacheFolder = Path.Combine(Globals.ProjectCacheFolder, "References");
if (!Directory.Exists(_cacheFolder)) if (!Directory.Exists(_cacheFolder))
Directory.CreateDirectory(_cacheFolder); Directory.CreateDirectory(_cacheFolder);
_assetId = assetItem.ID;
_surface = new Surface(this) _surface = new Surface(this)
{ {
AnchorPreset = AnchorPresets.StretchAll, AnchorPreset = AnchorPresets.StretchAll,
@@ -194,6 +194,7 @@ namespace FlaxEditor.Windows
_nodesAssets.Add(assetId); _nodesAssets.Add(assetId);
var node = new AssetNode((uint)_nodes.Count + 1, _surface.Context, GraphNodes[0], GraphGroups[0], assetId); var node = new AssetNode((uint)_nodes.Count + 1, _surface.Context, GraphNodes[0], GraphGroups[0], assetId);
_nodes.Add(node); _nodes.Add(node);
return node; return node;
} }
@@ -392,8 +393,7 @@ namespace FlaxEditor.Windows
_nodesAssets = new HashSet<Guid>(); _nodesAssets = new HashSet<Guid>();
var searchLevel = 4; // TODO: make it as an option (somewhere in window UI) var searchLevel = 4; // TODO: make it as an option (somewhere in window UI)
// TODO: add option to filter assets by type (eg. show only textures as leaf nodes) // TODO: add option to filter assets by type (eg. show only textures as leaf nodes)
var assetNode = SpawnNode(_assetId); var assetNode = SpawnNode(_item.ID);
// TODO: add some outline or tint color to the main node
BuildGraph(assetNode, searchLevel, false); BuildGraph(assetNode, searchLevel, false);
ArrangeGraph(assetNode, false); ArrangeGraph(assetNode, false);
BuildGraph(assetNode, searchLevel, true); BuildGraph(assetNode, searchLevel, true);
@@ -402,6 +402,10 @@ namespace FlaxEditor.Windows
return; return;
_progress = 100.0f; _progress = 100.0f;
var commentRect = assetNode.EditorBounds;
commentRect.Expand(80f);
_surface.Context.CreateComment(ref commentRect, _item.ShortName, Color.Green);
// Update UI // Update UI
FlaxEngine.Scripting.InvokeOnUpdate(() => FlaxEngine.Scripting.InvokeOnUpdate(() =>
{ {

View File

@@ -142,6 +142,7 @@ namespace FlaxEditor.Windows
{ {
Title = "Content"; Title = "Content";
Icon = editor.Icons.Folder32; Icon = editor.Icons.Folder32;
var style = Style.Current;
FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
@@ -164,6 +165,8 @@ namespace FlaxEditor.Windows
_navigationBar = new NavigationBar _navigationBar = new NavigationBar
{ {
Parent = _toolStrip, Parent = _toolStrip,
ScrollbarTrackColor = style.Background,
ScrollbarThumbColor = style.ForegroundGrey,
}; };
// Split panel // Split panel
@@ -179,7 +182,7 @@ namespace FlaxEditor.Windows
var headerPanel = new ContainerControl var headerPanel = new ContainerControl
{ {
AnchorPreset = AnchorPresets.HorizontalStretchTop, AnchorPreset = AnchorPresets.HorizontalStretchTop,
BackgroundColor = Style.Current.Background, BackgroundColor = style.Background,
IsScrollable = false, IsScrollable = false,
Offsets = new Margin(0, 0, 0, 18 + 6), Offsets = new Margin(0, 0, 0, 18 + 6),
}; };

View File

@@ -666,7 +666,7 @@ void Asset::onLoaded()
{ {
onLoaded_MainThread(); onLoaded_MainThread();
} }
else if (OnLoaded.IsBinded()) else if (OnLoaded.IsBinded() || _references.HasItems())
{ {
Function<void()> action; Function<void()> action;
action.Bind<Asset, &Asset::onLoaded>(this); action.Bind<Asset, &Asset::onLoaded>(this);

View File

@@ -218,10 +218,14 @@ Asset::LoadResult MaterialInstance::load()
Guid baseMaterialId; Guid baseMaterialId;
headerStream.Read(baseMaterialId); headerStream.Read(baseMaterialId);
auto baseMaterial = Content::LoadAsync<MaterialBase>(baseMaterialId); auto baseMaterial = Content::LoadAsync<MaterialBase>(baseMaterialId);
if (baseMaterial)
baseMaterial->AddReference();
// Load parameters // Load parameters
if (Params.Load(&headerStream)) if (Params.Load(&headerStream))
{ {
if (baseMaterial)
baseMaterial->RemoveReference();
LOG(Warning, "Cannot load material parameters."); LOG(Warning, "Cannot load material parameters.");
return LoadResult::CannotLoadData; return LoadResult::CannotLoadData;
} }
@@ -239,6 +243,7 @@ Asset::LoadResult MaterialInstance::load()
ParamsChanged(); ParamsChanged();
} }
baseMaterial->RemoveReference();
return LoadResult::Ok; return LoadResult::Ok;
} }

View File

@@ -87,6 +87,10 @@ public:
/// </summary> /// </summary>
double LastAccessTime = 0.0; double LastAccessTime = 0.0;
/// <summary>
/// Flag set to indicate that chunk is during loading (atomic access to sync multiple reading threads).
/// </summary>
int64 IsLoading = 0;
/// <summary> /// <summary>
/// The chunk data. /// The chunk data.
/// </summary> /// </summary>
@@ -146,7 +150,7 @@ public:
/// </summary> /// </summary>
FORCE_INLINE bool IsLoaded() const FORCE_INLINE bool IsLoaded() const
{ {
return Data.IsValid(); return Data.IsValid() && Platform::AtomicRead(&IsLoading) == 0;
} }
/// <summary> /// <summary>
@@ -154,7 +158,7 @@ public:
/// </summary> /// </summary>
FORCE_INLINE bool IsMissing() const FORCE_INLINE bool IsMissing() const
{ {
return Data.IsInvalid(); return !IsLoaded();
} }
/// <summary> /// <summary>

View File

@@ -5,6 +5,7 @@
#include "FlaxPackage.h" #include "FlaxPackage.h"
#include "ContentStorageManager.h" #include "ContentStorageManager.h"
#include "Engine/Core/Log.h" #include "Engine/Core/Log.h"
#include "Engine/Core/ScopeExit.h"
#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Platform/File.h" #include "Engine/Platform/File.h"
#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerCPU.h"
@@ -246,6 +247,7 @@ FlaxStorage::~FlaxStorage()
ASSERT(IsDisposed()); ASSERT(IsDisposed());
CHECK(_chunksLock == 0); CHECK(_chunksLock == 0);
CHECK(_refCount == 0); CHECK(_refCount == 0);
CHECK(_isUnloadingData == 0);
ASSERT(_chunks.IsEmpty()); ASSERT(_chunks.IsEmpty());
#if USE_EDITOR #if USE_EDITOR
@@ -261,6 +263,22 @@ FlaxStorage::~FlaxStorage()
#endif #endif
} }
void FlaxStorage::LockChunks()
{
RETRY:
Platform::InterlockedIncrement(&_chunksLock);
if (Platform::AtomicRead(&_isUnloadingData) != 0)
{
// Someone else is closing file handles or freeing chunks so wait for it to finish and retry
Platform::InterlockedDecrement(&_chunksLock);
do
{
Platform::Sleep(1);
} while (Platform::AtomicRead(&_isUnloadingData) != 0);
goto RETRY;
}
}
FlaxStorage::LockData FlaxStorage::LockSafe() FlaxStorage::LockData FlaxStorage::LockSafe()
{ {
auto lock = LockData(this); auto lock = LockData(this);
@@ -689,7 +707,6 @@ bool FlaxStorage::LoadAssetHeader(const Guid& id, AssetInitData& data)
return true; return true;
} }
// Load header
return LoadAssetHeader(e, data); return LoadAssetHeader(e, data);
} }
@@ -699,7 +716,10 @@ bool FlaxStorage::LoadAssetChunk(FlaxChunk* chunk)
ASSERT(IsLoaded()); ASSERT(IsLoaded());
ASSERT(chunk != nullptr && _chunks.Contains(chunk)); ASSERT(chunk != nullptr && _chunks.Contains(chunk));
// Check if already loaded // Protect against loading the same chunk from multiple threads at once
while (Platform::InterlockedCompareExchange(&chunk->IsLoading, 1, 0) != 0)
Platform::Sleep(1);
SCOPE_EXIT{ Platform::AtomicStore(&chunk->IsLoading, 0); };
if (chunk->IsLoaded()) if (chunk->IsLoaded())
return false; return false;
@@ -776,12 +796,10 @@ bool FlaxStorage::LoadAssetChunk(FlaxChunk* chunk)
// Raw data // Raw data
chunk->Data.Read(stream, size); chunk->Data.Read(stream, size);
} }
ASSERT(chunk->IsLoaded());
chunk->RegisterUsage(); chunk->RegisterUsage();
} }
UnlockChunks(); UnlockChunks();
return failed; return failed;
} }
@@ -1420,10 +1438,12 @@ FileReadStream* FlaxStorage::OpenFile()
bool FlaxStorage::CloseFileHandles() bool FlaxStorage::CloseFileHandles()
{ {
// Guard the whole process so if new thread wants to lock the chunks will need to wait for this to end
Platform::InterlockedIncrement(&_isUnloadingData);
SCOPE_EXIT{ Platform::InterlockedDecrement(&_isUnloadingData); };
if (Platform::AtomicRead(&_chunksLock) == 0 && Platform::AtomicRead(&_files) == 0) if (Platform::AtomicRead(&_chunksLock) == 0 && Platform::AtomicRead(&_files) == 0)
{ return false; // Early out when no files are opened
return false;
}
PROFILE_CPU(); PROFILE_CPU();
PROFILE_MEM(ContentFiles); PROFILE_MEM(ContentFiles);
@@ -1496,9 +1516,21 @@ void FlaxStorage::Tick(double time)
{ {
auto chunk = _chunks.Get()[i]; auto chunk = _chunks.Get()[i];
const bool wasUsed = (time - chunk->LastAccessTime) < unusedDataChunksLifetime; const bool wasUsed = (time - chunk->LastAccessTime) < unusedDataChunksLifetime;
if (!wasUsed && chunk->IsLoaded() && EnumHasNoneFlags(chunk->Flags, FlaxChunkFlags::KeepInMemory)) if (!wasUsed &&
chunk->IsLoaded() &&
EnumHasNoneFlags(chunk->Flags, FlaxChunkFlags::KeepInMemory) &&
Platform::AtomicRead(&chunk->IsLoading) == 0)
{ {
// Guard the unloading so if other thread wants to lock the chunks will need to wait for this to end
Platform::InterlockedIncrement(&_isUnloadingData);
if (Platform::AtomicRead(&_chunksLock) != 0 || Platform::AtomicRead(&chunk->IsLoading) != 0)
{
// Someone started loading so skip ticking
Platform::InterlockedDecrement(&_isUnloadingData);
return;
}
chunk->Unload(); chunk->Unload();
Platform::InterlockedDecrement(&_isUnloadingData);
} }
wasAnyUsed |= wasUsed; wasAnyUsed |= wasUsed;
} }

View File

@@ -90,6 +90,7 @@ protected:
int64 _refCount = 0; int64 _refCount = 0;
int64 _chunksLock = 0; int64 _chunksLock = 0;
int64 _files = 0; int64 _files = 0;
int64 _isUnloadingData = 0;
double _lastRefLostTime; double _lastRefLostTime;
CriticalSection _loadLocker; CriticalSection _loadLocker;
@@ -129,10 +130,7 @@ public:
/// <summary> /// <summary>
/// Locks the storage chunks data to prevent disposing them. Also ensures that file handles won't be closed while chunks are locked. /// Locks the storage chunks data to prevent disposing them. Also ensures that file handles won't be closed while chunks are locked.
/// </summary> /// </summary>
FORCE_INLINE void LockChunks() void LockChunks();
{
Platform::InterlockedIncrement(&_chunksLock);
}
/// <summary> /// <summary>
/// Unlocks the storage chunks data. /// Unlocks the storage chunks data.

View File

@@ -338,10 +338,10 @@ public:
StreamTextureMipTask(StreamingTexture* texture, int32 mipIndex, Task* rootTask) StreamTextureMipTask(StreamingTexture* texture, int32 mipIndex, Task* rootTask)
: GPUUploadTextureMipTask(texture->GetTexture(), mipIndex, Span<byte>(nullptr, 0), 0, 0, false) : GPUUploadTextureMipTask(texture->GetTexture(), mipIndex, Span<byte>(nullptr, 0), 0, 0, false)
, _streamingTexture(texture) , _streamingTexture(texture)
, _rootTask(rootTask ? rootTask : this) , _rootTask(rootTask)
, _dataLock(_streamingTexture->GetOwner()->LockData()) , _dataLock(_streamingTexture->GetOwner()->LockData())
{ {
_streamingTexture->_streamingTasks.Add(_rootTask); _streamingTexture->_streamingTasks.Add(this);
_texture.Released.Bind<StreamTextureMipTask, &StreamTextureMipTask::OnResourceReleased2>(this); _texture.Released.Bind<StreamTextureMipTask, &StreamTextureMipTask::OnResourceReleased2>(this);
} }
@@ -357,7 +357,7 @@ private:
if (_streamingTexture) if (_streamingTexture)
{ {
ScopeLock lock(_streamingTexture->GetOwner()->GetOwnerLocker()); ScopeLock lock(_streamingTexture->GetOwner()->GetOwnerLocker());
_streamingTexture->_streamingTasks.Remove(_rootTask); _streamingTexture->_streamingTasks.Remove(this);
_streamingTexture = nullptr; _streamingTexture = nullptr;
} }
} }
@@ -422,6 +422,15 @@ protected:
GPUUploadTextureMipTask::OnFail(); GPUUploadTextureMipTask::OnFail();
} }
void OnCancel() override
{
GPUUploadTextureMipTask::OnCancel();
// Cancel the root task too (eg. mip loading from asset)
if (_rootTask != nullptr)
_rootTask->Cancel();
}
}; };
Task* StreamingTexture::CreateStreamingTask(int32 residency) Task* StreamingTexture::CreateStreamingTask(int32 residency)

View File

@@ -42,38 +42,38 @@ public:
/// <summary> /// <summary>
/// The reflections texture resolution. /// The reflections texture resolution.
/// </summary> /// </summary>
API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"Probe\")") API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"Quality\")")
ProbeCubemapResolution CubemapResolution = ProbeCubemapResolution::UseGraphicsSettings; ProbeCubemapResolution CubemapResolution = ProbeCubemapResolution::UseGraphicsSettings;
/// <summary>
/// The probe update mode.
/// </summary>
API_FIELD(Attributes = "EditorOrder(10), EditorDisplay(\"Quality\")")
ProbeUpdateMode UpdateMode = ProbeUpdateMode::Manual;
/// <summary> /// <summary>
/// The reflections brightness. /// The reflections brightness.
/// </summary> /// </summary>
API_FIELD(Attributes="EditorOrder(10), Limit(0, 1000, 0.01f), EditorDisplay(\"Probe\")") API_FIELD(Attributes="EditorOrder(0), Limit(0, 1000, 0.01f), EditorDisplay(\"Probe\")")
float Brightness = 1.0f; float Brightness = 1.0f;
/// <summary> /// <summary>
/// The probe rendering order. The higher values are render later (on top). /// The probe rendering order. The higher values are render later (on top).
/// </summary> /// </summary>
API_FIELD(Attributes = "EditorOrder(25), EditorDisplay(\"Probe\")") API_FIELD(Attributes = "EditorOrder(20), EditorDisplay(\"Probe\")")
int32 SortOrder = 0; int32 SortOrder = 0;
/// <summary>
/// The probe update mode.
/// </summary>
API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Probe\")")
ProbeUpdateMode UpdateMode = ProbeUpdateMode::Manual;
/// <summary> /// <summary>
/// The probe capture camera near plane distance. /// The probe capture camera near plane distance.
/// </summary> /// </summary>
API_FIELD(Attributes="EditorOrder(30), Limit(0, float.MaxValue, 0.01f), EditorDisplay(\"Probe\")") API_FIELD(Attributes="EditorOrder(25), Limit(0, float.MaxValue, 0.01f), EditorDisplay(\"Probe\")")
float CaptureNearPlane = 10.0f; float CaptureNearPlane = 10.0f;
public: public:
/// <summary> /// <summary>
/// Gets the probe radius. /// Gets the probe radius.
/// </summary> /// </summary>
API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(3000.0f), Limit(0), EditorDisplay(\"Probe\")") API_PROPERTY(Attributes="EditorOrder(15), DefaultValue(3000.0f), Limit(0), EditorDisplay(\"Probe\")")
float GetRadius() const; float GetRadius() const;
/// <summary> /// <summary>

View File

@@ -5,6 +5,9 @@
#include "Types.h" #include "Types.h"
#include "Engine/Core/Types/StringView.h" #include "Engine/Core/Types/StringView.h"
#include "Engine/Core/Types/Guid.h" #include "Engine/Core/Types/Guid.h"
#if PLATFORM_ARCH_ARM64
#include "Engine/Core/Core.h"
#endif
class MMethod; class MMethod;
class BinaryModule; class BinaryModule;

View File

@@ -37,22 +37,22 @@
<ItemGroup> <ItemGroup>
<Reference Include="Ionic.Zip.Reduced"> <Reference Include="Ionic.Zip.Reduced">
<HintPath>..\..\..\Source\Platforms\DotNet\Ionic.Zip.Reduced.dll</HintPath> <HintPath>..\..\Platforms\DotNet\Ionic.Zip.Reduced.dll</HintPath>
</Reference> </Reference>
<Reference Include="System.Text.Encoding.CodePages"> <Reference Include="System.Text.Encoding.CodePages">
<HintPath>..\..\..\Source\Platforms\DotNet\System.Text.Encoding.CodePages.dll</HintPath> <HintPath>..\..\Platforms\DotNet\System.Text.Encoding.CodePages.dll</HintPath>
</Reference> </Reference>
<Reference Include="Mono.Cecil"> <Reference Include="Mono.Cecil">
<HintPath>..\..\..\Source\Platforms\DotNet\Mono.Cecil.dll</HintPath> <HintPath>..\..\Platforms\DotNet\Mono.Cecil.dll</HintPath>
</Reference> </Reference>
<Reference Include="Microsoft.VisualStudio.Setup.Configuration.Interop"> <Reference Include="Microsoft.VisualStudio.Setup.Configuration.Interop">
<HintPath>..\..\..\Source\Platforms\DotNet\Microsoft.VisualStudio.Setup.Configuration.Interop.dll</HintPath> <HintPath>..\..\Platforms\DotNet\Microsoft.VisualStudio.Setup.Configuration.Interop.dll</HintPath>
</Reference> </Reference>
<Reference Include="Microsoft.CodeAnalysis.CSharp"> <Reference Include="Microsoft.CodeAnalysis.CSharp">
<HintPath>..\..\..\Source\Platforms\DotNet\Microsoft.CodeAnalysis.CSharp.dll</HintPath> <HintPath>..\..\Platforms\DotNet\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
</Reference> </Reference>
<Reference Include="Microsoft.CodeAnalysis"> <Reference Include="Microsoft.CodeAnalysis">
<HintPath>..\..\..\Source\Platforms\DotNet\Microsoft.CodeAnalysis.dll</HintPath> <HintPath>..\..\Platforms\DotNet\Microsoft.CodeAnalysis.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -19,7 +19,7 @@ namespace Flax.Build
/// <summary> /// <summary>
/// Specifies the minimum CPU architecture type to support (on x86/x64). /// Specifies the minimum CPU architecture type to support (on x86/x64).
/// </summary> /// </summary>
[CommandLine("winCpuArch", "<arch>", "Specifies the minimum CPU architecture type to support (om x86/x64).")] [CommandLine("winCpuArch", "<arch>", "Specifies the minimum CPU architecture type to support (on x86/x64).")]
public static CpuArchitecture WindowsCpuArch = CpuArchitecture.SSE4_2; // 99.78% support on PC according to Steam Hardware & Software Survey: September 2025 (https://store.steampowered.com/hwsurvey/) public static CpuArchitecture WindowsCpuArch = CpuArchitecture.SSE4_2; // 99.78% support on PC according to Steam Hardware & Software Survey: September 2025 (https://store.steampowered.com/hwsurvey/)
} }
} }
@@ -76,22 +76,27 @@ namespace Flax.Build.Platforms
options.LinkEnv.InputLibraries.Add("oleaut32.lib"); options.LinkEnv.InputLibraries.Add("oleaut32.lib");
options.LinkEnv.InputLibraries.Add("delayimp.lib"); options.LinkEnv.InputLibraries.Add("delayimp.lib");
if (options.Architecture == TargetArchitecture.ARM64) options.CompileEnv.CpuArchitecture = Configuration.WindowsCpuArch;
if (options.Architecture == TargetArchitecture.x64)
{
if (_minVersion.Major <= 7 && options.CompileEnv.CpuArchitecture == CpuArchitecture.AVX2)
{
// Old Windows had lower support ratio for latest CPU features
options.CompileEnv.CpuArchitecture = CpuArchitecture.AVX;
}
if (_minVersion.Major >= 11 && options.CompileEnv.CpuArchitecture == CpuArchitecture.AVX)
{
// Windows 11 has hard requirement on SSE4.2
options.CompileEnv.CpuArchitecture = CpuArchitecture.SSE4_2;
}
}
else if (options.Architecture == TargetArchitecture.ARM64)
{ {
options.CompileEnv.PreprocessorDefinitions.Add("USE_SOFT_INTRINSICS"); options.CompileEnv.PreprocessorDefinitions.Add("USE_SOFT_INTRINSICS");
options.LinkEnv.InputLibraries.Add("softintrin.lib"); options.LinkEnv.InputLibraries.Add("softintrin.lib");
} if (options.CompileEnv.CpuArchitecture != CpuArchitecture.None)
options.CompileEnv.CpuArchitecture = CpuArchitecture.NEON;
options.CompileEnv.CpuArchitecture = Configuration.WindowsCpuArch;
if (_minVersion.Major <= 7 && options.CompileEnv.CpuArchitecture == CpuArchitecture.AVX2)
{
// Old Windows had lower support ratio for latest CPU features
options.CompileEnv.CpuArchitecture = CpuArchitecture.AVX;
}
if (_minVersion.Major >= 11 && options.CompileEnv.CpuArchitecture == CpuArchitecture.AVX)
{
// Windows 11 has hard requirement on SSE4.2
options.CompileEnv.CpuArchitecture = CpuArchitecture.SSE4_2;
} }
} }