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

Improve Excel Functionality #102

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
76 changes: 54 additions & 22 deletions src/Lumina/Excel/ExcelModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,19 +79,10 @@ public ExcelModule( GameData gameData )
/// that may be created anew or reused from a previous invocation of this method.</returns>
/// <remarks/>
/// <exception cref="NotSupportedException">Sheet was not a <see cref="ExcelVariant.Default"/>.</exception>
/// <inheritdoc cref="GetBaseSheet(Type, Nullable{Language}, string?)"/>
/// <inheritdoc cref="GetRawSheet{T}(Nullable{Language}, string?)"/>
public ExcelSheet< T > GetSheet< T >( Language? language = null, string? name = null ) where T : struct, IExcelRow< T >
{
var attr = GetSheetAttributes( typeof( T ) ) ?? throw new SheetAttributeMissingException( null, nameof( T ) );
name ??= attr.Name ?? throw new SheetNameEmptyException( nameof( name ) );

var rawSheet = GetRawSheetCore( name, language, out var variant );

if( VerifySheetChecksums && attr?.ColumnHash is { } hash && hash != rawSheet.ColumnHash )
throw new MismatchedColumnHashException( hash, rawSheet.ColumnHash, nameof( T ) );

if( variant != ExcelVariant.Default )
throw new NotSupportedException( $"Specified sheet variant {variant} is not supported; was expecting {ExcelVariant.Default}." );
var rawSheet = GetRawSheet<T>( language, name );

return new ExcelSheet< T >( rawSheet );
}
Expand All @@ -106,18 +97,9 @@ public ExcelSheet< T > GetSheet< T >( Language? language = null, string? name =
/// <inheritdoc cref="GetBaseSheet(Type, Nullable{Language}, string?)"/>
public SubrowExcelSheet< T > GetSubrowSheet< T >( Language? language = null, string? name = null ) where T : struct, IExcelSubrow< T >
{
var attr = GetSheetAttributes( typeof( T ) ) ?? throw new SheetAttributeMissingException( null, nameof( T ) );
name ??= attr.Name ?? throw new SheetNameEmptyException( nameof( name ) );

var rawSheet = GetRawSheetCore( name, language, out var variant );

if( VerifySheetChecksums && attr?.ColumnHash is { } hash && hash != rawSheet.ColumnHash )
throw new MismatchedColumnHashException( hash, rawSheet.ColumnHash, nameof( T ) );

if ( variant != ExcelVariant.Subrows )
throw new NotSupportedException( $"Specified sheet variant {variant} is not supported; was expecting {ExcelVariant.Subrows}." );
var rawSheet = GetRawSubrowSheet<T>( language, name );

return new SubrowExcelSheet< T >( (RawSubrowExcelSheet)rawSheet );
return new SubrowExcelSheet< T >( rawSheet );
}

/// <summary>Loads a typed <see cref="IExcelSheet"/>.</summary>
Expand Down Expand Up @@ -176,6 +158,56 @@ public IExcelSheet GetBaseSheet( Type rowType, Language? language = null, string
throw new InvalidOperationException( "Something went wrong" );
}

/// <summary>Loads a <see cref="RawExcelSheet"/>.</summary>
/// <param name="language">The requested sheet language. Leave <see langword="null"/> or empty to use the default language.</param>
/// <param name="name">The requested explicit sheet name. Leave <see langword="null"/> to use <typeparamref name="T"/>'s sheet name. Explicit names are necessary for quest/dungeon/cutscene sheets.</param>
/// <returns>An excel sheet corresponding to <typeparamref name="T"/>, <paramref name="language"/>, and <paramref name="name"/>
/// that may be created anew or reused from a previous invocation of this method.</returns>
/// <remarks/>
/// <exception cref="NotSupportedException">Sheet was not a <see cref="ExcelVariant.Default"/>.</exception>
/// <inheritdoc cref="GetBaseSheet(Type, Nullable{Language}, string?)"/>
[EditorBrowsable( EditorBrowsableState.Advanced )]
public RawExcelSheet GetRawSheet<T>( Language? language = null, string? name = null ) where T : struct, IExcelRow<T>
{
var attr = GetSheetAttributes( typeof( T ) ) ?? throw new SheetAttributeMissingException( null, nameof( T ) );
name ??= attr.Name ?? throw new SheetNameEmptyException( nameof( name ) );

var rawSheet = GetRawSheetCore( name, language, out var variant );

if( VerifySheetChecksums && attr?.ColumnHash is { } hash && hash != rawSheet.ColumnHash )
throw new MismatchedColumnHashException( hash, rawSheet.ColumnHash, nameof( T ) );

if( variant != ExcelVariant.Default )
throw new NotSupportedException( $"Specified sheet variant {variant} is not supported; was expecting {ExcelVariant.Default}." );

return rawSheet;
}

/// <summary>Loads an <see cref="RawSubrowExcelSheet"/>.</summary>
/// <param name="language">The requested sheet language. Leave <see langword="null"/> or empty to use the default language.</param>
/// <param name="name">The requested explicit sheet name. Leave <see langword="null"/> to use <typeparamref name="T"/>'s sheet name. Explicit names are necessary for quest/dungeon/cutscene sheets.</param>
/// <returns>An excel sheet corresponding to <typeparamref name="T"/>, <paramref name="language"/>, and <paramref name="name"/>
/// that may be created anew or reused from a previous invocation of this method.</returns>
/// <remarks/>
/// <exception cref="NotSupportedException">Sheet was not a <see cref="ExcelVariant.Subrows"/>.</exception>
/// <inheritdoc cref="GetBaseSheet(Type, Nullable{Language}, string?)"/>
[EditorBrowsable( EditorBrowsableState.Advanced )]
public RawSubrowExcelSheet GetRawSubrowSheet<T>( Language? language = null, string? name = null ) where T : struct, IExcelSubrow<T>
{
var attr = GetSheetAttributes( typeof( T ) ) ?? throw new SheetAttributeMissingException( null, nameof( T ) );
name ??= attr.Name ?? throw new SheetNameEmptyException( nameof( name ) );

var rawSheet = GetRawSheetCore( name, language, out var variant );

if( VerifySheetChecksums && attr?.ColumnHash is { } hash && hash != rawSheet.ColumnHash )
throw new MismatchedColumnHashException( hash, rawSheet.ColumnHash, nameof( T ) );

if( variant != ExcelVariant.Subrows )
throw new NotSupportedException( $"Specified sheet variant {variant} is not supported; was expecting {ExcelVariant.Subrows}." );

return (RawSubrowExcelSheet)rawSheet;
}

/// <summary>Loads a <see cref="RawExcelSheet"/>.</summary>
/// <param name="name">The requested sheet name.</param>
/// <param name="language">The requested sheet language. Leave <see langword="null"/> or empty to use the default language.</param>
Expand Down
41 changes: 41 additions & 0 deletions src/Lumina/Excel/IExtendedExcelRow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;

namespace Lumina.Excel;

/// <summary>
/// An extended interface for <see cref="IExcelRow{T}"/> to provide additional functionality and extension methods.
/// </summary>
/// <typeparam name="T">The type that implements the interface.</typeparam>
public interface IExtendedExcelRow< T > : IExcelRow< T >, IEquatable< T > where T : struct, IExcelRow< T >, IExtendedExcelRow< T >, IEquatable< T >
{
/// <summary>
/// Gets the <see cref="ExcelPage"/> containing the data for this row.
/// </summary>
ExcelPage Page { get; }

/// <summary>
/// Gets the row offset in the <see cref="Page"/>.
/// </summary>
uint Offset { get; }

bool IEquatable< T >.Equals( T other ) =>
Page == other.Page && RowId == other.RowId;

/// <summary>
/// Indicates whether <paramref name="l"/> and <paramref name="r"/> are equal.
/// </summary>
/// <param name="l">The left parameter.</param>
/// <param name="r">The right parameter.</param>
/// <returns><see langword="true"/> if <paramref name="l"/> and <paramref name="r"/> are equal; <see langword="false"/> otherwise.</returns>
virtual static bool operator ==( T l, T r ) =>
l.Equals( r );

/// <summary>
/// Indicates whether <paramref name="l"/> and <paramref name="r"/> are not equal.
/// </summary>
/// <param name="l">The left parameter.</param>
/// <param name="r">The right parameter.</param>
/// <returns><see langword="true"/> if <paramref name="l"/> and <paramref name="r"/> are not equal; <see langword="false"/> otherwise.</returns>
virtual static bool operator !=( T l, T r ) =>
!l.Equals( r );
}
27 changes: 27 additions & 0 deletions src/Lumina/Excel/IExtendedExcelSubrow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;

namespace Lumina.Excel;

/// <summary>
/// An extended interface for <see cref="IExcelSubrow{T}"/> to provide additional functionality and extension methods.
/// </summary>
/// <inheritdoc cref="IExtendedExcelRow{T}"/>
public interface IExtendedExcelSubrow< T > : IExcelSubrow<T>, IEquatable<T> where T : struct, IExcelSubrow< T >, IExtendedExcelSubrow< T >, IEquatable<T>
{
/// <inheritdoc cref="IExtendedExcelRow{T}.Page"/>
ExcelPage Page { get; }

/// <inheritdoc cref="IExtendedExcelRow{T}.Offset"/>
uint Offset { get; }

bool IEquatable<T>.Equals( T other ) =>
Page == other.Page && RowId == other.RowId && SubrowId == other.SubrowId;

/// <inheritdoc cref="IExtendedExcelRow{T}.operator=="/>
virtual static bool operator ==( T l, T r ) =>
l.Equals( r );

/// <inheritdoc cref="IExtendedExcelRow{T}.operator!="/>
virtual static bool operator !=( T l, T r ) =>
!l.Equals( r );
}
7 changes: 7 additions & 0 deletions src/Lumina/Excel/RawExcelSheet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@ public bool HasRow( uint rowId )
return !Unsafe.IsNullRef( in rowIndexRef );
}

[MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )]
internal T? GetRowOrDefault<T>( uint rowId ) where T : struct, IExcelRow<T>
{
ref readonly var lookup = ref GetRowLookupOrNullRef( rowId );
return Unsafe.IsNullRef( in lookup ) ? null : UnsafeCreateRow<T>( in lookup );
}

/// <summary>Gets a row lookup at the given index, if possible.</summary>
/// <param name="rowId">Index of the desired row.</param>
/// <returns>Lookup data for the desired row, or a null reference if no corresponding row exists.</returns>
Expand Down
2 changes: 1 addition & 1 deletion src/Lumina/Excel/RawRow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Lumina.Excel;
/// This type is designed to be used to read from arbitrary columns and offsets.
/// </remarks>
[Sheet]
public readonly struct RawRow( ExcelPage page, uint offset, uint row ) : IExcelRow<RawRow>
public readonly struct RawRow( ExcelPage page, uint offset, uint row ) : IExcelRow<RawRow>, IExtendedExcelRow<RawRow>
{
/// <summary>
/// The associated <see cref="ExcelPage"/> of the row.
Expand Down
7 changes: 7 additions & 0 deletions src/Lumina/Excel/RawSubrowExcelSheet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,11 @@
ref readonly var lookup = ref GetRowLookupOrNullRef( rowId );
return Unsafe.IsNullRef( in lookup ) ? throw new ArgumentOutOfRangeException( nameof( rowId ), rowId, null ) : lookup.SubrowCount;
}

[MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )]
public SubrowCollection<T>? GetRowOrDefault<T>( uint rowId ) where T : struct, IExcelSubrow<T>

Check warning on line 51 in src/Lumina/Excel/RawSubrowExcelSheet.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

'RawSubrowExcelSheet.GetRowOrDefault<T>(uint)' hides inherited member 'RawExcelSheet.GetRowOrDefault<T>(uint)'. Use the new keyword if hiding was intended.
{
ref readonly var lookup = ref GetRowLookupOrNullRef( rowId );
return Unsafe.IsNullRef( in lookup ) ? null : new( this, in lookup );
}
}
13 changes: 9 additions & 4 deletions src/Lumina/Excel/RowRef{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ namespace Lumina.Excel;
/// <param name="language">The associated language of the referenced row. Leave <see langword="null"/> to use <paramref name="module"/>'s default language.</param>
public struct RowRef< T >( ExcelModule? module, uint rowId, Language? language = null ) where T : struct, IExcelRow< T >
{
private ExcelSheet< T >? _sheet = null;
private ExcelSheet< T >? Sheet {
private RawExcelSheet? _sheet = null;
private RawExcelSheet? Sheet {
get {
if( module == null )
return null;
return _sheet ??= module.GetSheet< T >(
return _sheet ??= module.GetRawSheet< T >(
language == Data.Language.None ?
null : // Use default language if null (or fall back to None)
language
Expand Down Expand Up @@ -52,7 +52,12 @@ private ExcelSheet< T >? Sheet {
/// <summary>
/// Attempts to get the referenced row value. Is <see langword="null"/> if <see cref="RowId"/> does not exist in the sheet.
/// </summary>
public T? ValueNullable => Sheet?.GetRowOrDefault( rowId );
public T? ValueNullable => Sheet?.GetRowOrDefault<T>( rowId );

public RowRef( RawExcelSheet sheet, uint rowId ) : this( sheet.Module, rowId, sheet.Language )
{
_sheet = sheet;
}

private readonly RowRef ToGeneric() => RowRef.Create< T >( module, rowId, language );

Expand Down
28 changes: 21 additions & 7 deletions src/Lumina/Excel/SubrowCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,28 @@ namespace Lumina.Excel;
{
private readonly RawExcelSheet.RowOffsetLookup _lookup;

internal SubrowCollection( SubrowExcelSheet< T > sheet, scoped ref readonly RawExcelSheet.RowOffsetLookup lookup )
internal SubrowCollection( RawSubrowExcelSheet sheet, scoped ref readonly RawExcelSheet.RowOffsetLookup lookup )
{
Sheet = sheet;
_rawSheet = sheet;
_lookup = lookup;
}

[Obsolete( "Use RawSheet instead; Create an issue on GitHub if RawSheet does not fit your use case." )]
internal SubrowCollection( SubrowExcelSheet<T> sheet, scoped ref readonly RawExcelSheet.RowOffsetLookup lookup )
{
_sheet = sheet;
_lookup = lookup;
}

private RawSubrowExcelSheet? _rawSheet { get; }
private SubrowExcelSheet<T>? _sheet { get; }

/// <summary>Gets the associated raw sheet.</summary>
public RawSubrowExcelSheet RawSheet => _rawSheet ?? _sheet!.RawSheet;

/// <summary>Gets the associated sheet.</summary>
public SubrowExcelSheet< T > Sheet { get; }
[Obsolete("Use RawSheet instead; Create an issue on GitHub if RawSheet does not fit your use case.")]
public SubrowExcelSheet<T> Sheet => _sheet ?? new( RawSheet );

/// <summary>Gets the Row ID of the subrows contained within.</summary>
public uint RowId => _lookup.RowId;
Expand All @@ -35,7 +49,7 @@ public T this[ int index ] {
get {
ArgumentOutOfRangeException.ThrowIfNegative( index );
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual( index, Count );
return Sheet.RawSheet.UnsafeCreateSubrow< T >( in _lookup, unchecked( (ushort) index ) );
return RawSheet.UnsafeCreateSubrow< T >( in _lookup, unchecked( (ushort) index ) );
}
}

Expand All @@ -61,7 +75,7 @@ public int IndexOf( T item )
if( item.RowId != RowId || item.SubrowId >= Count )
return -1;

var row = Sheet.RawSheet.UnsafeCreateSubrow< T >( in _lookup, item.SubrowId );
var row = RawSheet.UnsafeCreateSubrow< T >( in _lookup, item.SubrowId );
return EqualityComparer< T >.Default.Equals( item, row ) ? item.SubrowId : -1;
}

Expand All @@ -76,7 +90,7 @@ public void CopyTo( T[] array, int arrayIndex )
if( Count > array.Length - arrayIndex )
throw new ArgumentException( "The number of elements in the source list is greater than the available space." );
for( var i = 0; i < Count; i++ )
array[ arrayIndex++ ] = Sheet.RawSheet.UnsafeCreateSubrow< T >( in _lookup, unchecked( (ushort) i ) );
array[ arrayIndex++ ] = RawSheet.UnsafeCreateSubrow< T >( in _lookup, unchecked( (ushort) i ) );
}

/// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
Expand Down Expand Up @@ -105,7 +119,7 @@ public bool MoveNext()
// UnsafeCreateSubrow must be called only when the preconditions are validated.
// If it is to be called on-demand from get_Current, then it may end up being called with invalid parameters,
// so we create the instance in advance here.
Current = subrowCollection.Sheet.RawSheet.UnsafeCreateSubrow< T >( in subrowCollection._lookup, unchecked( (ushort) _index ) );
Current = subrowCollection.RawSheet.UnsafeCreateSubrow< T >( in subrowCollection._lookup, unchecked( (ushort) _index ) );
return true;
}

Expand Down
14 changes: 7 additions & 7 deletions src/Lumina/Excel/SubrowExcelSheet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public sealed class SubrowExcelSheet< T >( RawSubrowExcelSheet sheet ) : ISubrow
public SubrowCollection< T >? GetRowOrDefault( uint rowId )
{
ref readonly var lookup = ref RawSheet.GetRowLookupOrNullRef( rowId );
return Unsafe.IsNullRef( in lookup ) ? null : new( this, in lookup );
return Unsafe.IsNullRef( in lookup ) ? null : new( RawSheet, in lookup );
}

/// <summary>
Expand All @@ -64,7 +64,7 @@ public bool TryGetRow( uint rowId, out SubrowCollection< T > row )
return false;
}

row = new( this, in lookup );
row = new( RawSheet, in lookup );
return true;
}

Expand All @@ -77,7 +77,7 @@ public bool TryGetRow( uint rowId, out SubrowCollection< T > row )
public SubrowCollection< T > GetRow( uint rowId )
{
ref readonly var lookup = ref RawSheet.GetRowLookupOrNullRef( rowId );
return Unsafe.IsNullRef( in lookup ) ? throw new ArgumentOutOfRangeException( nameof( rowId ), rowId, null ) : new( this, in lookup );
return Unsafe.IsNullRef( in lookup ) ? throw new ArgumentOutOfRangeException( nameof( rowId ), rowId, null ) : new( RawSheet, in lookup );
}

/// <summary>
Expand All @@ -91,7 +91,7 @@ public SubrowCollection< T > GetRowAt( int rowIndex )
ArgumentOutOfRangeException.ThrowIfNegative( rowIndex );
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual( rowIndex, RawSheet.OffsetLookupTable.Length );

return new( this, in RawSheet.UnsafeGetRowLookupAt( rowIndex ) );
return new( RawSheet, in RawSheet.UnsafeGetRowLookupAt( rowIndex ) );
}

/// <summary>
Expand Down Expand Up @@ -179,7 +179,7 @@ public T GetSubrowAt( int rowIndex, ushort subrowId )
public bool HasRow( uint rowId ) => RawSheet.HasRow( rowId );

/// <inheritdoc/>
public bool Contains( SubrowCollection< T > item ) => ReferenceEquals( item.Sheet, this ) && RawSheet.HasRow( item.RowId );
public bool Contains( SubrowCollection< T > item ) => ReferenceEquals( item.RawSheet, this ) && RawSheet.HasRow( item.RowId );

/// <inheritdoc/>
public void CopyTo( SubrowCollection< T >[] array, int arrayIndex )
Expand All @@ -189,7 +189,7 @@ public void CopyTo( SubrowCollection< T >[] array, int arrayIndex )
if( Count > array.Length - arrayIndex )
throw new ArgumentException( "The number of elements in the source list is greater than the available space." );
foreach( var lookup in RawSheet.OffsetLookupTable )
array[ arrayIndex++ ] = new( this, in lookup );
array[ arrayIndex++ ] = new( RawSheet, in lookup );
}

void ICollection< SubrowCollection< T > >.Add( SubrowCollection< T > item ) => throw new NotSupportedException();
Expand Down Expand Up @@ -228,7 +228,7 @@ public bool MoveNext()
// UnsafeGetRowLookupAt must be called only when the preconditions are validated.
// If it is to be called on-demand from get_Current, then it may end up being called with invalid parameters,
// so we create the instance in advance here.
Current = new( sheet, in sheet.RawSheet.UnsafeGetRowLookupAt( _index ) );
Current = new( sheet.RawSheet, in sheet.RawSheet.UnsafeGetRowLookupAt( _index ) );
return true;
}

Expand Down
Loading
Loading