wpf游戏引擎的contentBrowser实现

1.contentBrowser.cs

using EnvDTE;

using Microsoft.VisualStudio.Shell.Interop;

using PrimalEditor.Common;

using PrimalEditor.Utilities;

using PrimalEditor.GameProject;

using System;

using System.Collections.Generic;

using System.Collections.ObjectModel;

using System.Diagnostics;

using System.IO;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Windows;

using Project = PrimalEditor.GameProject.Project;

using Microsoft.Win32;

namespace PrimalEditor.Content

{

sealed class ContentInfo

{

public static int IconWidth => 90;

public byte[] Icon { get; }

public byte[] IconSmall { get; }

public string FullPath { get;}

public string FileName => Path.GetFileNameWithoutExtension(FullPath);

public bool IsDirectory { get; }

public DateTime DateModified { get; }

public long? Size { get; }

public ContentInfo(string fullPath, byte[] icon = null, byte[] smallIcon = null, DateTime? lastModified = null)

{

Debug.Assert(File.Exists(fullPath) || Directory.Exists(fullPath));

var info = new FileInfo(fullPath);

IsDirectory = ContentHelper.IsDirectory(fullPath);

DateModified = lastModified ?? info.LastWriteTime;

Size = IsDirectory ? (long?)null : info.Length;

Icon = icon;

IconSmall = IconSmall ?? icon;

FullPath = fullPath;

}

}

class ContentBrowser : ViewModelBase,IDisposable

{

private static readonly object _lock = new object();

private static readonly DelayEventTimer _refreshTimer = new DelayEventTimer(TimeSpan.FromMicroseconds(250));

private static readonly FileSystemWatcher _contentWatcher = new FileSystemWatcher()

{

IncludeSubdirectories = true,

Filter = "",

NotifyFilter = NotifyFilters.CreationTime |

NotifyFilters.DirectoryName |

NotifyFilters.FileName |

NotifyFilters.LastWrite

};

private string _cacheFilePath = string.Empty;

private static readonly Dictionary<string, ContentInfo> _contentInfoCache = new Dictionary<string, ContentInfo>();

public string ContentFolder { get; }

private readonly ObservableCollection<ContentInfo> _folderContent = new ObservableCollection<ContentInfo>();

public ReadOnlyObservableCollection<ContentInfo> FolderContent { get; }

private string _selectedFolder;

public string SelectedFolder

{

get => _selectedFolder;

set

{

if (_selectedFolder != value)

{

_selectedFolder = value;

if (!string.IsNullOrEmpty(_selectedFolder))

{

GetFolderContent();

}

OnPropertyChanged(nameof(SelectedFolder));

}

}

}

private async void GetFolderContent()

{

var folderContent = new List<ContentInfo>();

await Task.Run(() => {

folderContent = GetFolderContent(SelectedFolder);

});

_folderContent.Clear();

folderContent.ForEach(x => _folderContent.Add(x));

}

private static List<ContentInfo> GetFolderContent(string path)

{

Debug.Assert(!string.IsNullOrEmpty(path));

var folderContent = new List<ContentInfo>();

try

{

foreach (var dir in Directory.GetDirectories(path))

{

folderContent.Add(new ContentInfo(dir));

}

//Get files

lock (_lock)

{

foreach (var file in Directory.GetFiles(path, $"*{Asset.AssetFileExtension}"))

{

var fileInfo = new FileInfo(file);

if (!_contentInfoCache.ContainsKey(file) ||

_contentInfoCache[file].DateModified.IsOlder(fileInfo.LastWriteTime))

{

var info = AssetRegistry.GetAssetInfo(file) ?? Asset.GetAssetInfo(file);

Debug.Assert(info != null);

_contentInfoCache[file] = new ContentInfo(file, info.Icon);

}

Debug.Assert(_contentInfoCache.ContainsKey(file));

folderContent.Add(_contentInfoCache[file]);

}

}

}catch (Exception ex)

{

Debug.WriteLine(ex.Message);

}

return folderContent;

}

private async void OnContentModified(object sender, FileSystemEventArgs e)

{

if (Path.GetDirectoryName(e.FullPath) != SelectedFolder) return;

await Application.Current.Dispatcher.BeginInvoke(new Action(() =>

{

_refreshTimer.Trigger();

}));

}

private void Refresh(object? sender, DelayEventTimerArgs e)

{

GetFolderContent();

}

private static void SaveInfoCache(string file)

{

lock (_lock)

{

using var writer = new BinaryWriter(File.Open(file, FileMode.Create, FileAccess.Write));

writer.Write(_contentInfoCache.Keys.Count);

foreach (var key in _contentInfoCache.Keys)

{

var info = _contentInfoCache[key];

writer.Write(key);

writer.Write(info.DateModified.ToBinary());

writer.Write(info.Icon.Length);

writer.Write(info.Icon);

}

}

}

private static void LoadInfoCache(string file)

{

if (!File.Exists(file)) return;

try

{

lock (_lock)

{

using var reader = new BinaryReader(File.Open(file, FileMode.Open, FileAccess.Read));

var numEntries = reader.ReadInt32();

_contentInfoCache.Clear();

for (int i = 0; i < numEntries; ++i)

{

var assetFile = reader.ReadString();

var date = DateTime.FromBinary(reader.ReadInt64());

var iconSize = reader.ReadInt32();

var icon = reader.ReadBytes(iconSize);

//Cache only the files that still exist

if (File.Exists(assetFile))

{

_contentInfoCache[assetFile] = new ContentInfo(assetFile, icon, null, date);

}

}

}

} catch (Exception ex)

{

Debug.WriteLine(ex.Message);

Logger.Log(MessageType.Warning, "Failed to read Content Browser cache file");

_contentInfoCache.Clear();

}

}

public void Dispose()

{

((IDisposable)_contentWatcher).Dispose();

if (!string.IsNullOrEmpty(_cacheFilePath))

{

SaveInfoCache(_cacheFilePath);

_cacheFilePath = string.Empty;

}

}

public ContentBrowser(Project project)

{

Debug.Assert(project != null);

var contentFolder = project.ContentPath;

Debug.Assert(!string.IsNullOrEmpty(contentFolder.Trim()));

contentFolder = Path.TrimEndingDirectorySeparator(contentFolder);

ContentFolder = contentFolder;

SelectedFolder = contentFolder;

FolderContent = new ReadOnlyObservableCollection<ContentInfo>(_folderContent);

if (string.IsNullOrEmpty(_cacheFilePath))

{

_cacheFilePath = $@"{project.Path}.Primal\ContentInfoCache.bin";

LoadInfoCache(_cacheFilePath);

}

_contentWatcher.Path = contentFolder;

_contentWatcher.Changed += OnContentModified;

_contentWatcher.Created += OnContentModified;

_contentWatcher.Deleted += OnContentModified;

_contentWatcher.Renamed += OnContentModified;

_contentWatcher.EnableRaisingEvents = true;

_refreshTimer.Triggered += Refresh;

}

}

}