Skip to content

Commit

Permalink
Merge pull request #55 from Nexus-Mods/nx-extensions
Browse files Browse the repository at this point in the history
Added: Extensions for Nx Library And Support for Writing to Memory Mapped Files
  • Loading branch information
Sewer56 authored Jul 30, 2024
2 parents 1c7e6dd + 0781561 commit 3f26d06
Show file tree
Hide file tree
Showing 22 changed files with 679 additions and 93 deletions.
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

0 comments on commit 3f26d06

Please sign in to comment.