Initial virtual file system implementation.
This commit is contained in:
3
TODO.md
3
TODO.md
@@ -14,7 +14,8 @@
|
||||
- ~~Reimplement unloading.~~
|
||||
- Finalize ResourceManager and ResourceLoader APIs for 1.0.
|
||||
- Add async API for ResourceManager.
|
||||
- Virtual file system.
|
||||
- ~~Virtual file system.~~
|
||||
- Custom PAK format using the VFS implementation.
|
||||
- (stretch goal) Streamed resource loading.
|
||||
|
||||
## Serialization
|
||||
|
||||
@@ -17,4 +17,4 @@ LinearVelocityRandom = 0.5
|
||||
ScaleBegin = 0.1
|
||||
ScaleEnd = 5.0
|
||||
ColorBegin = [255, 162, 0]
|
||||
ColorEnd = [64, 64, 64, 0]
|
||||
ColorEnd = [0, 0, 0, 0]
|
||||
|
||||
10
Voile.sln
10
Voile.sln
@@ -18,14 +18,7 @@ Global
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{393AA04F-A0DE-42F2-AAEC-6B2DCFB7A852}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{393AA04F-A0DE-42F2-AAEC-6B2DCFB7A852}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{393AA04F-A0DE-42F2-AAEC-6B2DCFB7A852}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{393AA04F-A0DE-42F2-AAEC-6B2DCFB7A852}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DA4FDEDC-AA81-4336-844F-562F9E763974}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DA4FDEDC-AA81-4336-844F-562F9E763974}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DA4FDEDC-AA81-4336-844F-562F9E763974}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -35,4 +28,7 @@ Global
|
||||
{DBA85D7B-0A91-405B-9078-5463F49AE47E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DBA85D7B-0A91-405B-9078-5463F49AE47E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Voile.Input;
|
||||
using Voile.Rendering;
|
||||
using Voile.Resources;
|
||||
using Voile.VFS;
|
||||
|
||||
namespace Voile
|
||||
{
|
||||
@@ -88,6 +89,7 @@ namespace Voile
|
||||
throw new NullReferenceException("No ResourceManager provided.");
|
||||
}
|
||||
|
||||
Mount();
|
||||
LoadResources();
|
||||
Ready();
|
||||
Run();
|
||||
@@ -239,6 +241,12 @@ namespace Voile
|
||||
Renderer?.Start(renderSettings);
|
||||
}
|
||||
|
||||
private void Mount()
|
||||
{
|
||||
var resourceRootMount = new FileSystemMountPoint(ResourceRoot);
|
||||
VirtualFileSystem.Mount(resourceRootMount);
|
||||
}
|
||||
|
||||
private List<IStartableSystem> _startableSystems = new();
|
||||
private List<IUpdatableSystem> _updatableSystems = new();
|
||||
private List<IUpdatableSystem> _renderableSystems = new();
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Voile.Resources.DataReaders;
|
||||
/// <summary>
|
||||
/// Reads key/value data from a TOML file.
|
||||
/// </summary>
|
||||
public class TomlDataReader : IStreamDataReader, IDataValidator, IStreamKeyValueGetter, IDisposable
|
||||
public class TomlDataReader : IStreamDataReader, IDataValidator, IStreamKeyValueGetter
|
||||
{
|
||||
public string ExpectedHeader { get; private set; } = string.Empty;
|
||||
public TomlDataReader(string expectedHeader)
|
||||
@@ -17,14 +17,7 @@ public class TomlDataReader : IStreamDataReader, IDataValidator, IStreamKeyValue
|
||||
|
||||
public void Read(Stream data)
|
||||
{
|
||||
if (data is not FileStream fs)
|
||||
{
|
||||
throw new ArgumentException("Toml data reader only supports file streams.");
|
||||
}
|
||||
|
||||
_fs = fs;
|
||||
|
||||
using (var reader = new StreamReader(_fs))
|
||||
using (var reader = new StreamReader(data))
|
||||
{
|
||||
_table = TOML.Parse(reader);
|
||||
_valid = _table.HasKey(ExpectedHeader);
|
||||
@@ -231,12 +224,6 @@ public class TomlDataReader : IStreamDataReader, IDataValidator, IStreamKeyValue
|
||||
|
||||
public bool Valid() => _valid;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_fs?.Dispose();
|
||||
}
|
||||
|
||||
private TomlTable? _table;
|
||||
private FileStream? _fs;
|
||||
private bool _valid;
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
|
||||
using Voile.VFS;
|
||||
|
||||
namespace Voile.Resources;
|
||||
|
||||
public class FontLoader : ResourceLoader<Font>
|
||||
@@ -11,9 +13,14 @@ public class FontLoader : ResourceLoader<Font>
|
||||
|
||||
protected override Font LoadResource(string path)
|
||||
{
|
||||
byte[] fileBuffer = File.ReadAllBytes(path);
|
||||
using Stream stream = VirtualFileSystem.Read(path);
|
||||
|
||||
byte[] fileBuffer = new byte[stream.Length];
|
||||
int bytesRead = stream.Read(fileBuffer, 0, fileBuffer.Length);
|
||||
var result = new Font(path, fileBuffer);
|
||||
result.BufferSize = fileBuffer.Length;
|
||||
|
||||
result.BufferSize = bytesRead;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ namespace Voile.Resources
|
||||
var resource = LoadResource(path);
|
||||
var guid = Guid.NewGuid();
|
||||
|
||||
|
||||
var loadedResources = ResourceManager.LoadedResources;
|
||||
var oldResourceGuid = loadedResources.FirstOrDefault(loadedResource => loadedResource.Value.Path == path).Key;
|
||||
|
||||
@@ -46,6 +45,7 @@ namespace Voile.Resources
|
||||
{
|
||||
foreach (var loadedResource in ResourceManager.LoadedResources)
|
||||
{
|
||||
if (loadedResource.Value is not T) continue;
|
||||
Load(loadedResource.Value.Path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Voile.Resources;
|
||||
using StbImageSharp;
|
||||
using Voile.VFS;
|
||||
|
||||
namespace Voile
|
||||
{
|
||||
@@ -18,7 +19,7 @@ namespace Voile
|
||||
protected override Texture2d LoadResource(string path)
|
||||
{
|
||||
ImageResult image;
|
||||
using (var stream = File.OpenRead(path))
|
||||
using (var stream = VirtualFileSystem.Read(path))
|
||||
{
|
||||
image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
using Voile.Utils;
|
||||
using Voile.VFS;
|
||||
|
||||
namespace Voile.Resources
|
||||
{
|
||||
@@ -92,10 +92,7 @@ namespace Voile.Resources
|
||||
T? resource = default;
|
||||
result = null;
|
||||
|
||||
var fullPath = Path.Combine(ResourceRoot, path);
|
||||
|
||||
// TODO: don't check if file doesn't exist in the file system, make it more generic but for now it's fine
|
||||
if (!File.Exists(fullPath))
|
||||
if (!VirtualFileSystem.FileExists(path))
|
||||
{
|
||||
_logger.Error($"File at \"{path}\" doesn't exist!");
|
||||
return false;
|
||||
@@ -108,7 +105,7 @@ namespace Voile.Resources
|
||||
return false;
|
||||
}
|
||||
|
||||
var extension = Path.GetExtension(fullPath);
|
||||
var extension = Path.GetExtension(path);
|
||||
var hasExtension = loader.SupportedExtensions.Any(ext => ext == extension);
|
||||
|
||||
if (!hasExtension)
|
||||
@@ -116,7 +113,7 @@ namespace Voile.Resources
|
||||
_logger.Error($"Extension {extension} is not supported!");
|
||||
}
|
||||
|
||||
var resourceGuid = loader.Load(fullPath);
|
||||
var resourceGuid = loader.Load(path);
|
||||
|
||||
if (!GetResource(resourceGuid, out T? loadedResource))
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Numerics;
|
||||
using Voile.Resources;
|
||||
using Voile.Resources.DataReaders;
|
||||
using Voile.Utils;
|
||||
using Voile.VFS;
|
||||
|
||||
namespace Voile.Systems.Particles;
|
||||
|
||||
@@ -62,9 +62,8 @@ public class ParticleEmitterSettingsResourceLoader : ResourceLoader<ParticleEmit
|
||||
{
|
||||
var settings = new ParticleEmitterSettings();
|
||||
|
||||
using (var reader = new TomlDataReader("ParticleEmitterSettings"))
|
||||
{
|
||||
reader.Read(File.Open(path, FileMode.Open));
|
||||
var reader = new TomlDataReader("ParticleEmitterSettings");
|
||||
reader.Read(VirtualFileSystem.Read(path));
|
||||
|
||||
settings.Local = reader.GetBool("Local", true);
|
||||
settings.MaxParticles = reader.GetInt("MaxParticles");
|
||||
@@ -83,7 +82,6 @@ public class ParticleEmitterSettingsResourceLoader : ResourceLoader<ParticleEmit
|
||||
settings.ColorEnd = reader.GetColor("ColorEnd", Color.Black);
|
||||
settings.LinearVelocityDamping = reader.GetFloat("LinearVelocityDamping", 1.0f);
|
||||
settings.AngularVelocityDamping = reader.GetFloat("AngularVelocityDamping", 1.0f);
|
||||
}
|
||||
|
||||
return new ParticleEmitterSettingsResource(path, settings);
|
||||
}
|
||||
|
||||
69
Voile/Source/VFS/FileSystemMountPoint.cs
Normal file
69
Voile/Source/VFS/FileSystemMountPoint.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Voile.Utils;
|
||||
|
||||
namespace Voile.VFS;
|
||||
|
||||
/// <summary>
|
||||
/// A file in the OS file system.
|
||||
/// </summary>
|
||||
public class FileSystemFile : VirtualFile
|
||||
{
|
||||
public FileSystemFile(string path)
|
||||
{
|
||||
_fsPath = path;
|
||||
}
|
||||
|
||||
public override Stream GetStream()
|
||||
{
|
||||
return new FileStream(_fsPath, FileMode.Open, FileAccess.Read);
|
||||
}
|
||||
|
||||
private string _fsPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="IVirtualMountPoint"/> implementation for an OS file system.
|
||||
/// </summary>
|
||||
public class FileSystemMountPoint : IVirtualMountPoint
|
||||
{
|
||||
public int Order => int.MaxValue;
|
||||
|
||||
public FileSystemMountPoint(string path)
|
||||
{
|
||||
_fsPath = path;
|
||||
}
|
||||
|
||||
public void Mount()
|
||||
{
|
||||
_logger.Info($"Mounting from file system path \"{_fsPath}\".");
|
||||
|
||||
int rootLength = _fsPath.Length;
|
||||
var files =
|
||||
Directory.GetFiles(_fsPath, "*", SearchOption.AllDirectories)
|
||||
.Select(p => p.Remove(0, rootLength))
|
||||
.ToList();
|
||||
|
||||
foreach (string file in files)
|
||||
{
|
||||
var relativePath = NormalizePath(file);
|
||||
var fullPath = NormalizePath(Path.Combine(_fsPath, file));
|
||||
_files[relativePath] = new FileSystemFile(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
public VirtualFile GetFile(string path) => _files[path];
|
||||
|
||||
public IDictionary<string, VirtualFile> GetFiles() => _files;
|
||||
|
||||
public bool HasFile(string path) => _files.ContainsKey(path);
|
||||
|
||||
private string NormalizePath(string path)
|
||||
{
|
||||
return path
|
||||
.Replace(@"\\", @"\")
|
||||
.Replace(@"\", @"/");
|
||||
}
|
||||
|
||||
private Logger _logger = new(nameof(FileSystemMountPoint));
|
||||
private string _fsPath;
|
||||
private Dictionary<string, VirtualFile> _files = new();
|
||||
}
|
||||
6
Voile/Source/VFS/IVirtualFile.cs
Normal file
6
Voile/Source/VFS/IVirtualFile.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Voile.VFS;
|
||||
|
||||
public abstract class VirtualFile
|
||||
{
|
||||
public abstract Stream GetStream();
|
||||
}
|
||||
33
Voile/Source/VFS/IVirtualMountPoint.cs
Normal file
33
Voile/Source/VFS/IVirtualMountPoint.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace Voile.VFS;
|
||||
|
||||
/// <summary>
|
||||
/// A virtual mounting point.
|
||||
/// </summary>
|
||||
public interface IVirtualMountPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// Order of mounting for this mount point. Lower values indicate higher priority for lookup.
|
||||
/// </summary>
|
||||
int Order { get; }
|
||||
/// <summary>
|
||||
/// Mounts this <see cref="IVirtualMountPoint"/>.
|
||||
/// </summary>
|
||||
void Mount();
|
||||
/// <summary>
|
||||
/// Gets a file.
|
||||
/// </summary>
|
||||
/// <param name="path">Relative path to the file.</param>
|
||||
/// <returns>An instance of <see cref="VirtualFile"/> if the file exists; otherwise, an exception is thrown.</returns>
|
||||
VirtualFile GetFile(string path);
|
||||
/// <summary>
|
||||
/// Gets all files available at this <see cref="IVirtualMountPoint"/>.
|
||||
/// </summary>
|
||||
/// <returns>A dictionary mapping a relative path to an instance of a <see cref="VirtualFile"/>.</returns>
|
||||
IDictionary<string, VirtualFile> GetFiles();
|
||||
/// <summary>
|
||||
/// Determines whether a file exists at the given relative path within this mount point.
|
||||
/// </summary>
|
||||
/// <param name="path">The relative path of the file to check.</param>
|
||||
/// <returns><c>true</c> if the file exists; otherwise, <c>false</c>.</returns>
|
||||
bool HasFile(string path);
|
||||
}
|
||||
59
Voile/Source/VFS/VirtualFileSystem.cs
Normal file
59
Voile/Source/VFS/VirtualFileSystem.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
namespace Voile.VFS;
|
||||
|
||||
/// <summary>
|
||||
/// A virtual file system that provides an abstract interface for manipulating files from various sources.
|
||||
/// </summary>
|
||||
public static class VirtualFileSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Mounts a <see cref="IVirtualMountPoint"/>.
|
||||
/// This will make files available for this mount point accessible.
|
||||
/// </summary>
|
||||
/// <param name="mountPoint"></param>
|
||||
public static void Mount(IVirtualMountPoint mountPoint)
|
||||
{
|
||||
mountPoint.Mount();
|
||||
_mounts.Add(mountPoint);
|
||||
|
||||
_mounts = _mounts.OrderBy(mount => mount.Order).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if file exists or not.
|
||||
/// </summary>
|
||||
/// <param name="path">Relative path to the file.</param>
|
||||
/// <returns></returns>
|
||||
public static bool FileExists(string path)
|
||||
{
|
||||
return _mounts.Any(mount => mount.HasFile(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="VirtualFile"/> from path.
|
||||
/// </summary>
|
||||
/// <param name="path">Relative path to the file.</param>
|
||||
/// <returns>A virtual file at the path.</returns>
|
||||
public static VirtualFile GetFile(string path)
|
||||
{
|
||||
var mount = _mounts.FirstOrDefault(mount => mount.HasFile(path));
|
||||
if (mount == null)
|
||||
{
|
||||
throw new FileNotFoundException($"File \"{path}\" was not found in any mounted point.");
|
||||
}
|
||||
|
||||
return mount.GetFile(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a file.
|
||||
/// </summary>
|
||||
/// <param name="path">Relative path to the file.</param>
|
||||
/// <returns>A readable stream.</returns>
|
||||
public static Stream Read(string path)
|
||||
{
|
||||
var file = GetFile(path);
|
||||
return file.GetStream();
|
||||
}
|
||||
|
||||
private static List<IVirtualMountPoint> _mounts = new();
|
||||
}
|
||||
Reference in New Issue
Block a user