Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added: Extensions for Nx Library And Support for Writing to Memory Mapped Files #55

Merged
merged 6 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 23 additions & 64 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2,76 +2,35 @@ root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 120

# Microsoft .NET properties
csharp_new_line_before_members_in_object_initializers = false
csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion
csharp_style_prefer_utf8_string_literals = true:suggestion
csharp_style_var_elsewhere = true:warning
csharp_style_var_for_built_in_types = true:warning
csharp_style_var_when_type_is_apparent = true:warning
dotnet_naming_rule.unity_serialized_field_rule.import_to_resharper = True
dotnet_naming_rule.unity_serialized_field_rule.resharper_description = Unity serialized field
dotnet_naming_rule.unity_serialized_field_rule.resharper_guid = 5f0fdb63-c892-4d2c-9324-15c80b22a7ef
dotnet_naming_rule.unity_serialized_field_rule.severity = warning
dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style
dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols
dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = *
dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds =
dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field
dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
dotnet_style_qualification_for_event = false:warning
dotnet_style_qualification_for_field = false:warning
dotnet_style_qualification_for_method = false:warning
dotnet_style_qualification_for_property = false:warning
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion

# ReSharper properties
resharper_apply_auto_detected_rules = false
resharper_autodetect_indent_settings = true
resharper_csharp_empty_block_style = together_same_line
resharper_csharp_stick_comment = false
resharper_outdent_statement_labels = true
resharper_show_autodetect_configure_formatting_tip = false
resharper_use_indent_from_vs = false
resharper_wrap_lines = true

# ReSharper inspection severities
resharper_arrange_redundant_parentheses_highlighting = hint
resharper_arrange_type_member_modifiers_highlighting = hint
resharper_arrange_type_modifiers_highlighting = hint
resharper_built_in_type_reference_style_for_member_access_highlighting = hint
resharper_built_in_type_reference_style_highlighting = hint

[*.cs]
indent_size = 4
indent_style = space
tab_width = 4

# CS4014: Task not awaited
dotnet_diagnostic.cs4014.severity = error

# CS8509: Missing switch case for named enum value
dotnet_diagnostic.CS8509.severity = error

# CS824: Missing switch case for unnamed enum value
dotnet_diagnostic.CS8524.severity = none
indent_size = 4

# Enums should not have duplicate values
dotnet_diagnostic.CA1069.severity = error
[{*.har,*.jsb2,*.jsb3,*.json,*.jsonc,*.postman_collection,*.postman_collection.json,*.postman_environment,*.postman_environment.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,bowerrc,jest.config}]
indent_style = space
indent_size = 2

[{*.yaml,*.yml}]
indent_style = space
indent_size = 2

[*.csproj]
[{*.bash,*.sh,*.zsh}]
indent_style = space
indent_size = 2

[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,c++m,cc,ccm,cginc,compute,cp,cpp,cppm,cs,cshtml,cu,cuh,cxx,cxxm,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,ixx,master,ml,mli,mpp,mq4,mq5,mqh,mxx,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,uxml,vb,xaml,xamlx,xoml,xsd}]
indent_style = space
indent_size = 4
tab_width = 4

# Verify settings
[*.{received,verified}.{txt,xml,json}]
charset = "utf-8-bom"
end_of_line = lf
indent_size =
indent_style =
insert_final_newline = false
tab_width =
trim_trailing_whitespace = false

20 changes: 20 additions & 0 deletions .globalconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
is_global = true

# CS4014: Task not awaited
dotnet_diagnostic.cs4014.severity = error

# CS8509: Missing switch case for named enum value
dotnet_diagnostic.CS8509.severity = error

# CS824: Missing switch case for unnamed enum value
dotnet_diagnostic.CS8524.severity = none

# Enums should not have duplicate values
dotnet_diagnostic.CA1069.severity = error

# Non-constant fields should not be visible
dotnet_diagnostic.CA2211.severity = error

# Don't call Enumerable.Cast<T> or Enumerable.OfType<T> with incompatible types
dotnet_diagnostic.CA2021.severity = error

20 changes: 20 additions & 0 deletions NexusMods.Paths.sln
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Paths.TestingHelp
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Paths.Benchmarks", "src\NexusMods.Paths.Benchmarks\NexusMods.Paths.Benchmarks.csproj", "{E86B44A1-D57E-4B0B-8F62-869E1784C49C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{1351E5E7-6B95-405D-92CE-D7ECAF67464C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Paths.Extensions.Nx", "src\Extensions\NexusMods.Paths.Extensions.Nx\NexusMods.Paths.Extensions.Nx.csproj", "{4656B671-8002-461D-8C4C-74A77546C187}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{332D93F6-E1F1-4F51-88D6-B4B364DDBDBC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Paths.Extensions.Nx.Tests", "tests\Extensions\NexusMods.Paths.Extensions.Nx.Tests\NexusMods.Paths.Extensions.Nx.Tests.csproj", "{D5012909-9405-4DE0-84E7-354A5EFDF0FC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -42,6 +50,10 @@ Global
{30CBEB4A-E0C0-4B11-A0CF-F97BFACEEF89} = {6ED01F9D-5E12-4EB2-9601-64A2ADC719DE}
{FABE9B73-49FF-472C-9013-F52876F25E1D} = {0377EBE6-F147-4233-86AD-32C821B9567E}
{E86B44A1-D57E-4B0B-8F62-869E1784C49C} = {0377EBE6-F147-4233-86AD-32C821B9567E}
{1351E5E7-6B95-405D-92CE-D7ECAF67464C} = {0377EBE6-F147-4233-86AD-32C821B9567E}
{4656B671-8002-461D-8C4C-74A77546C187} = {1351E5E7-6B95-405D-92CE-D7ECAF67464C}
{332D93F6-E1F1-4F51-88D6-B4B364DDBDBC} = {6ED01F9D-5E12-4EB2-9601-64A2ADC719DE}
{D5012909-9405-4DE0-84E7-354A5EFDF0FC} = {332D93F6-E1F1-4F51-88D6-B4B364DDBDBC}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A92DED3D-BC67-4E04-9A06-9A1B302B3070}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
Expand All @@ -60,5 +72,13 @@ Global
{E86B44A1-D57E-4B0B-8F62-869E1784C49C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E86B44A1-D57E-4B0B-8F62-869E1784C49C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E86B44A1-D57E-4B0B-8F62-869E1784C49C}.Release|Any CPU.Build.0 = Release|Any CPU
{4656B671-8002-461D-8C4C-74A77546C187}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4656B671-8002-461D-8C4C-74A77546C187}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4656B671-8002-461D-8C4C-74A77546C187}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4656B671-8002-461D-8C4C-74A77546C187}.Release|Any CPU.Build.0 = Release|Any CPU
{D5012909-9405-4DE0-84E7-354A5EFDF0FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D5012909-9405-4DE0-84E7-354A5EFDF0FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D5012909-9405-4DE0-84E7-354A5EFDF0FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D5012909-9405-4DE0-84E7-354A5EFDF0FC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using NexusMods.Archives.Nx.Interfaces;

namespace NexusMods.Paths.Extensions.Nx.FileProviders.FileData;

/// <summary>
/// Provides access to data of a Paths memory-mapped file.
/// </summary>
public unsafe class PathsMemoryMappedFileData : IFileData
{
private readonly MemoryMappedFileHandle _memoryMappedFileHandle;
private readonly bool _disposeHandle;
private bool _disposed;

/// <inheritdoc />
public byte* Data { get; }

/// <inheritdoc />
public ulong DataLength { get; }

/// <summary>
/// Paths memory mapped file data.
/// </summary>
/// <param name="handle">The handle to use</param>
/// <param name="start">The start offset in the file</param>
/// <param name="length">The length of the data to map</param>
/// <param name="disposeHandle">Disposes the handle on close.</param>
public PathsMemoryMappedFileData(MemoryMappedFileHandle handle, ulong start, ulong length, bool disposeHandle = true)
{
_memoryMappedFileHandle = handle;
_disposeHandle = disposeHandle;
if (start >= handle.Length)
{
Data = handle.Pointer;
DataLength = 0;
}
else
{
Data = handle.Pointer + start;
DataLength = Math.Min(length, handle.Length - start);
}
}

/// <inheritdoc />
~PathsMemoryMappedFileData() => Dispose();

/// <inheritdoc />
public void Dispose()
{
if (_disposed)
return;

_disposed = true;

if (_disposeHandle)
_memoryMappedFileHandle.Dispose();

GC.SuppressFinalize(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using NexusMods.Archives.Nx.Interfaces;
using NexusMods.Paths.Extensions.Nx.FileProviders.FileData;
using System.IO;
using System.IO.MemoryMappedFiles;

namespace NexusMods.Paths.Extensions.Nx.FileProviders;

/// <summary>
/// A provider for creating <see cref="IFileData" /> instances from an absolute path
/// </summary>
public class FromAbsolutePathProvider : IFileDataProvider
{
/// <summary>
/// The full path to the file from which the data will be fetched.
/// </summary>
public required AbsolutePath FilePath { get; init; }

/// <inheritdoc />
public IFileData GetFileData(ulong start, ulong length)
{
// TODO: This could probably be better, as it's unoptimal for chunked files.
// Ideally the file should be opened once in the provider and then calls in GetFileData
// could work on slices of the larger MMF.
// This however requires a change in Nx itself, which should be done at some point.
var fileSystem = FilePath.FileSystem;
var handle = fileSystem.CreateMemoryMappedFile(FilePath, FileMode.Open, MemoryMappedFileAccess.Read, 0);
return new PathsMemoryMappedFileData(handle, start, length);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using NexusMods.Archives.Nx.FileProviders.FileData;
using NexusMods.Archives.Nx.Headers.Managed;
using NexusMods.Archives.Nx.Interfaces;
using NexusMods.Paths.Extensions.Nx.FileProviders.FileData;

namespace NexusMods.Paths.Extensions.Nx.FileProviders;

/// <summary>
/// A provider for creating <see cref="IFileData" /> instances which allow
/// the user to output information to an absolute path.
/// </summary>
public class OutputAbsolutePathProvider : IOutputDataProvider
{
/// <inheritdoc />
public string RelativePath { get; }

/// <inheritdoc />
public FileEntry Entry { get; }

/// <summary>
/// Full path to the file.
/// </summary>
public AbsolutePath FullPath { get; }

private readonly MemoryMappedFileHandle? _mappedFileHandle;
private bool _isDisposed;
private readonly bool _isEmpty;

/// <summary>
/// Initializes outputting a file to an absolute path.
/// </summary>
/// <param name="fullPath">The absolute path to output the file.</param>
/// <param name="relativePath">The relative path of the file (context from the Nx archive).</param>
/// <param name="entry">The individual file entry (context from the Nx archive).</param>
public OutputAbsolutePathProvider(AbsolutePath fullPath, string relativePath, FileEntry entry)
{
RelativePath = relativePath;
Entry = entry;
FullPath = fullPath;

TryCreate:
try
{
if (entry.DecompressedSize <= 0)
{
using var _ = FullPath.FileSystem.CreateFile(FullPath);
_isEmpty = true;
return;
}

// Ensure the directory exists
FullPath.FileSystem.CreateDirectory(FullPath.Parent);

// Delete the file if it exists to ensure we start with an empty file
if (FullPath.FileSystem.FileExists(FullPath))
FullPath.FileSystem.DeleteFile(FullPath);

// Create the memory mapped file
_mappedFileHandle = FullPath.FileSystem.CreateMemoryMappedFile(FullPath, FileMode.CreateNew, MemoryMappedFileAccess.ReadWrite, entry.DecompressedSize);
}
catch (DirectoryNotFoundException)
{
// This is written this way because explicit check is slow.
FullPath.FileSystem.CreateDirectory(FullPath.Parent);
goto TryCreate;
}
}

/// <inheritdoc />
public IFileData GetFileData(ulong start, ulong length)
{
if (_isEmpty)
return new ArrayFileData(Array.Empty<byte>(), 0, 0);

return new PathsMemoryMappedFileData(_mappedFileHandle!.Value, start, length, false);
}

/// <inheritdoc />
~OutputAbsolutePathProvider() => Dispose();

/// <inheritdoc />
public void Dispose()
{
if (_isDisposed)
return;

_isDisposed = true;
_mappedFileHandle?.Dispose();
GC.SuppressFinalize(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$([MSBuild]::GetPathOfFileAbove('NuGet.Build.props', '$(MSBuildThisFileDirectory)../../'))" />

<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NexusMods.Archives.Nx" Version="0.5.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\NexusMods.Paths\NexusMods.Paths.csproj" />
</ItemGroup>

</Project>
6 changes: 3 additions & 3 deletions src/NexusMods.Paths/FileSystemAbstraction/BaseFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -424,9 +424,9 @@ public virtual UnixFileMode GetUnixFileMode(AbsolutePath absolutePath)
}

/// <inheritdoc/>
public MemoryMappedFileHandle CreateMemoryMappedFile(AbsolutePath absPath, FileMode mode, MemoryMappedFileAccess access)
public MemoryMappedFileHandle CreateMemoryMappedFile(AbsolutePath absPath, FileMode mode, MemoryMappedFileAccess access, ulong fileSize)
{
return InternalCreateMemoryMappedFile(GetMappedPath(absPath), mode, access);
return InternalCreateMemoryMappedFile(GetMappedPath(absPath), mode, access, fileSize);
}

#endregion
Expand Down Expand Up @@ -470,6 +470,6 @@ public MemoryMappedFileHandle CreateMemoryMappedFile(AbsolutePath absPath, FileM
protected abstract void InternalMoveFile(AbsolutePath source, AbsolutePath dest, bool overwrite);

/// <inheritdoc cref="IFileSystem.CreateMemoryMappedFile"/>
protected abstract MemoryMappedFileHandle InternalCreateMemoryMappedFile(AbsolutePath absPath, FileMode mode, MemoryMappedFileAccess access);
protected abstract MemoryMappedFileHandle InternalCreateMemoryMappedFile(AbsolutePath absPath, FileMode mode, MemoryMappedFileAccess access, ulong fileSize);
#endregion
}
3 changes: 2 additions & 1 deletion src/NexusMods.Paths/FileSystemAbstraction/IFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -289,5 +289,6 @@ Stream OpenFile(AbsolutePath path,
/// <param name="absPath">Path of the file to memory map.</param>
/// <param name="mode">The mode the file is opened with.</param>
/// <param name="access">What you intend to do with the memory mapped file.</param>
MemoryMappedFileHandle CreateMemoryMappedFile(AbsolutePath absPath, FileMode mode, MemoryMappedFileAccess access);
/// <param name="fileSize">The size of the file, if creating a new file.</param>
MemoryMappedFileHandle CreateMemoryMappedFile(AbsolutePath absPath, FileMode mode, MemoryMappedFileAccess access, ulong fileSize);
}
Loading
Loading