diff --git a/src/Lumina/Excel/ExcelModule.cs b/src/Lumina/Excel/ExcelModule.cs index 706f3820..76aab28f 100644 --- a/src/Lumina/Excel/ExcelModule.cs +++ b/src/Lumina/Excel/ExcelModule.cs @@ -79,19 +79,10 @@ public ExcelModule( GameData gameData ) /// that may be created anew or reused from a previous invocation of this method. /// /// Sheet was not a . - /// + /// 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( language, name ); return new ExcelSheet< T >( rawSheet ); } @@ -106,18 +97,9 @@ public ExcelSheet< T > GetSheet< T >( Language? language = null, string? name = /// 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( language, name ); - return new SubrowExcelSheet< T >( (RawSubrowExcelSheet)rawSheet ); + return new SubrowExcelSheet< T >( rawSheet ); } /// Loads a typed . @@ -176,6 +158,56 @@ public IExcelSheet GetBaseSheet( Type rowType, Language? language = null, string throw new InvalidOperationException( "Something went wrong" ); } + /// Loads a . + /// The requested sheet language. Leave or empty to use the default language. + /// The requested explicit sheet name. Leave to use 's sheet name. Explicit names are necessary for quest/dungeon/cutscene sheets. + /// An excel sheet corresponding to , , and + /// that may be created anew or reused from a previous invocation of this method. + /// + /// Sheet was not a . + /// + [EditorBrowsable( EditorBrowsableState.Advanced )] + public RawExcelSheet GetRawSheet( Language? language = null, string? name = null ) where T : struct, IExcelRow + { + 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; + } + + /// Loads an . + /// The requested sheet language. Leave or empty to use the default language. + /// The requested explicit sheet name. Leave to use 's sheet name. Explicit names are necessary for quest/dungeon/cutscene sheets. + /// An excel sheet corresponding to , , and + /// that may be created anew or reused from a previous invocation of this method. + /// + /// Sheet was not a . + /// + [EditorBrowsable( EditorBrowsableState.Advanced )] + public RawSubrowExcelSheet GetRawSubrowSheet( Language? language = null, string? name = null ) where T : struct, IExcelSubrow + { + 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; + } + /// Loads a . /// The requested sheet name. /// The requested sheet language. Leave or empty to use the default language. diff --git a/src/Lumina/Excel/IExtendedExcelRow.cs b/src/Lumina/Excel/IExtendedExcelRow.cs new file mode 100644 index 00000000..e96d6ddb --- /dev/null +++ b/src/Lumina/Excel/IExtendedExcelRow.cs @@ -0,0 +1,41 @@ +using System; + +namespace Lumina.Excel; + +/// +/// An extended interface for to provide additional functionality and extension methods. +/// +/// The type that implements the interface. +public interface IExtendedExcelRow< T > : IExcelRow< T >, IEquatable< T > where T : struct, IExcelRow< T >, IExtendedExcelRow< T >, IEquatable< T > +{ + /// + /// Gets the containing the data for this row. + /// + ExcelPage Page { get; } + + /// + /// Gets the row offset in the . + /// + uint Offset { get; } + + bool IEquatable< T >.Equals( T other ) => + Page == other.Page && RowId == other.RowId; + + /// + /// Indicates whether and are equal. + /// + /// The left parameter. + /// The right parameter. + /// if and are equal; otherwise. + virtual static bool operator ==( T l, T r ) => + l.Equals( r ); + + /// + /// Indicates whether and are not equal. + /// + /// The left parameter. + /// The right parameter. + /// if and are not equal; otherwise. + virtual static bool operator !=( T l, T r ) => + !l.Equals( r ); +} diff --git a/src/Lumina/Excel/IExtendedExcelSubrow.cs b/src/Lumina/Excel/IExtendedExcelSubrow.cs new file mode 100644 index 00000000..6b4dc9e7 --- /dev/null +++ b/src/Lumina/Excel/IExtendedExcelSubrow.cs @@ -0,0 +1,27 @@ +using System; + +namespace Lumina.Excel; + +/// +/// An extended interface for to provide additional functionality and extension methods. +/// +/// +public interface IExtendedExcelSubrow< T > : IExcelSubrow, IEquatable where T : struct, IExcelSubrow< T >, IExtendedExcelSubrow< T >, IEquatable +{ + /// + ExcelPage Page { get; } + + /// + uint Offset { get; } + + bool IEquatable.Equals( T other ) => + Page == other.Page && RowId == other.RowId && SubrowId == other.SubrowId; + + /// + virtual static bool operator ==( T l, T r ) => + l.Equals( r ); + + /// + virtual static bool operator !=( T l, T r ) => + !l.Equals( r ); +} diff --git a/src/Lumina/Excel/RawExcelSheet.cs b/src/Lumina/Excel/RawExcelSheet.cs index 8255080b..62d04557 100644 --- a/src/Lumina/Excel/RawExcelSheet.cs +++ b/src/Lumina/Excel/RawExcelSheet.cs @@ -200,6 +200,13 @@ public bool HasRow( uint rowId ) return !Unsafe.IsNullRef( in rowIndexRef ); } + [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] + internal T? GetRowOrDefault( uint rowId ) where T : struct, IExcelRow + { + ref readonly var lookup = ref GetRowLookupOrNullRef( rowId ); + return Unsafe.IsNullRef( in lookup ) ? null : UnsafeCreateRow( in lookup ); + } + /// Gets a row lookup at the given index, if possible. /// Index of the desired row. /// Lookup data for the desired row, or a null reference if no corresponding row exists. diff --git a/src/Lumina/Excel/RawRow.cs b/src/Lumina/Excel/RawRow.cs index 0e966ae0..ca365632 100644 --- a/src/Lumina/Excel/RawRow.cs +++ b/src/Lumina/Excel/RawRow.cs @@ -12,7 +12,7 @@ namespace Lumina.Excel; /// This type is designed to be used to read from arbitrary columns and offsets. /// [Sheet] -public readonly struct RawRow( ExcelPage page, uint offset, uint row ) : IExcelRow +public readonly struct RawRow( ExcelPage page, uint offset, uint row ) : IExcelRow, IExtendedExcelRow { /// /// The associated of the row. diff --git a/src/Lumina/Excel/RawSubrowExcelSheet.cs b/src/Lumina/Excel/RawSubrowExcelSheet.cs index e6a0ee72..a4e09700 100644 --- a/src/Lumina/Excel/RawSubrowExcelSheet.cs +++ b/src/Lumina/Excel/RawSubrowExcelSheet.cs @@ -46,4 +46,11 @@ public ushort GetSubrowCount( uint rowId ) 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? GetRowOrDefault( uint rowId ) where T : struct, IExcelSubrow + { + ref readonly var lookup = ref GetRowLookupOrNullRef( rowId ); + return Unsafe.IsNullRef( in lookup ) ? null : new( this, in lookup ); + } } \ No newline at end of file diff --git a/src/Lumina/Excel/RowRef{T}.cs b/src/Lumina/Excel/RowRef{T}.cs index 119542d9..24d39134 100644 --- a/src/Lumina/Excel/RowRef{T}.cs +++ b/src/Lumina/Excel/RowRef{T}.cs @@ -12,12 +12,12 @@ namespace Lumina.Excel; /// The associated language of the referenced row. Leave to use 's default language. 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 @@ -52,7 +52,12 @@ private ExcelSheet< T >? Sheet { /// /// Attempts to get the referenced row value. Is if does not exist in the sheet. /// - public T? ValueNullable => Sheet?.GetRowOrDefault( rowId ); + public T? ValueNullable => Sheet?.GetRowOrDefault( 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 ); diff --git a/src/Lumina/Excel/SubrowCollection.cs b/src/Lumina/Excel/SubrowCollection.cs index ce032a0d..70a59d34 100644 --- a/src/Lumina/Excel/SubrowCollection.cs +++ b/src/Lumina/Excel/SubrowCollection.cs @@ -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 sheet, scoped ref readonly RawExcelSheet.RowOffsetLookup lookup ) + { + _sheet = sheet; + _lookup = lookup; + } + + private RawSubrowExcelSheet? _rawSheet { get; } + private SubrowExcelSheet? _sheet { get; } + + /// Gets the associated raw sheet. + public RawSubrowExcelSheet RawSheet => _rawSheet ?? _sheet!.RawSheet; + /// Gets the associated sheet. - public SubrowExcelSheet< T > Sheet { get; } + [Obsolete("Use RawSheet instead; Create an issue on GitHub if RawSheet does not fit your use case.")] + public SubrowExcelSheet Sheet => _sheet ?? new( RawSheet ); /// Gets the Row ID of the subrows contained within. public uint RowId => _lookup.RowId; @@ -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 ) ); } } @@ -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; } @@ -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 ) ); } /// @@ -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; } diff --git a/src/Lumina/Excel/SubrowExcelSheet.cs b/src/Lumina/Excel/SubrowExcelSheet.cs index 2629ac1a..bb89c658 100644 --- a/src/Lumina/Excel/SubrowExcelSheet.cs +++ b/src/Lumina/Excel/SubrowExcelSheet.cs @@ -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 ); } /// @@ -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; } @@ -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 ); } /// @@ -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 ) ); } /// @@ -179,7 +179,7 @@ public T GetSubrowAt( int rowIndex, ushort subrowId ) public bool HasRow( uint rowId ) => RawSheet.HasRow( rowId ); /// - 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 ); /// public void CopyTo( SubrowCollection< T >[] array, int arrayIndex ) @@ -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(); @@ -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; } diff --git a/src/Lumina/Excel/SubrowRef{T}.cs b/src/Lumina/Excel/SubrowRef{T}.cs index 489d100d..8fbddb51 100644 --- a/src/Lumina/Excel/SubrowRef{T}.cs +++ b/src/Lumina/Excel/SubrowRef{T}.cs @@ -12,12 +12,12 @@ namespace Lumina.Excel; /// The associated language of the referenced row. Leave to use 's default language. public struct SubrowRef< T >( ExcelModule? module, uint rowId, Language? language = null ) where T : struct, IExcelSubrow< T > { - private SubrowExcelSheet< T >? _sheet = null; - private SubrowExcelSheet< T >? Sheet { + private RawSubrowExcelSheet? _sheet = null; + private RawSubrowExcelSheet? Sheet { get { if( module == null ) return null; - return _sheet ??= module.GetSubrowSheet< T >( + return _sheet ??= module.GetRawSubrowSheet< T >( language == Data.Language.None ? null : // Use default language if null (or fall back to None) language @@ -54,6 +54,11 @@ private SubrowExcelSheet< T >? Sheet { /// public SubrowCollection< T >? ValueNullable => Sheet?.GetRowOrDefault( rowId ); + public SubrowRef( RawSubrowExcelSheet sheet, uint rowId ) : this(sheet.Module, rowId, sheet.Language) + { + _sheet = sheet; + } + private readonly RowRef ToGeneric() => RowRef.CreateSubrow< T >( module, rowId ); /// diff --git a/src/Lumina/Extensions/ExcelExtensions.cs b/src/Lumina/Extensions/ExcelExtensions.cs new file mode 100644 index 00000000..aa202a8e --- /dev/null +++ b/src/Lumina/Extensions/ExcelExtensions.cs @@ -0,0 +1,65 @@ +using Lumina.Excel; + +namespace Lumina.Extensions; + +/// +/// Extensions for , , and . +/// +public static class RowExcelExtensions +{ + public static RowRef GetRowRef(this ExcelSheet sheet, uint rowId) where T : struct, IExcelRow + { + return new RowRef( sheet.RawSheet, rowId ); + } + + public static RowRef GetGenericRowRef( this ExcelSheet sheet, uint rowId ) where T : struct, IExcelRow + { + return RowRef.Create( sheet.Module, rowId, sheet.Language ); + } + + public static RowRef AsRowRef( this T row ) where T : struct, IExcelRow, IExtendedExcelRow + { + return new RowRef( row.Page.Sheet, row.RowId ); + } + + public static RowRef AsGenericRowRef( this T row ) where T : struct, IExcelRow, IExtendedExcelRow + { + return RowRef.Create( row.Page.Module, row.RowId, row.Page.Language ); + } + + public static RawRow AsRawRow( this T row ) where T : struct, IExcelRow, IExtendedExcelRow + { + return new RawRow( row.Page, row.Offset, row.RowId ); + } +} + +/// +/// Extensions for , , and . +/// +public static class SubrowExcelExtensions +{ + public static SubrowRef GetRowRef( this SubrowExcelSheet sheet, uint rowId ) where T : struct, IExcelSubrow + { + return new SubrowRef( sheet.RawSheet, rowId ); + } + + public static RowRef GetGenericRowRef( this SubrowExcelSheet sheet, uint rowId ) where T : struct, IExcelSubrow + { + return RowRef.CreateSubrow( sheet.Module, rowId, sheet.Language ); + } + + public static SubrowRef AsRowRef( this T row ) where T : struct, IExcelSubrow, IExtendedExcelSubrow + { + return new SubrowRef( (RawSubrowExcelSheet)row.Page.Sheet, row.RowId ); + } + + public static RowRef AsGenericRowRef( this T row ) where T : struct, IExcelSubrow, IExtendedExcelSubrow + { + return RowRef.CreateSubrow( row.Page.Module, row.RowId, row.Page.Language ); + } + + public static RawSubrow AsRawRow( this T row ) where T : struct, IExcelSubrow, IExtendedExcelSubrow + { + return new RawSubrow( row.Page, row.Offset, row.RowId, row.SubrowId ); + } +}