Skip to content

Commit

Permalink
Merge pull request #100 from Soreepeong/feature/ssb-getview
Browse files Browse the repository at this point in the history
Add SeStringBuilder.GetViewAsMemory/Span
  • Loading branch information
NotAdam authored Nov 20, 2024
2 parents b99601a + 3675b80 commit db7deb5
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 6 deletions.
21 changes: 19 additions & 2 deletions src/Lumina.Tests/SeStringBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,24 @@ public void AddonIsParsedCorrectly()
}
}

[Fact]
public unsafe void SpanViewNullTerminationTest()
{
var test = new SeStringBuilder()
.AppendBold( "Test" )
.Append( "Asdf" )
.AppendItalicized( "Aaaaa" );
var expected =
"\x02\x19\x02\x02\x03"u8 + "Test"u8 + "\x02\x19\x02\x01\x03"u8 +
"Asdf"u8 +
"\x02\x1A\x02\x02\x03"u8 + "Aaaaa"u8 + "\x02\x1A\x02\x01\x03"u8;

var span = test.GetViewAsSpan();
Assert.True( span.SequenceEqual( expected ) );
fixed( byte* p = span )
Assert.Equal( 0 , p[ span.Length ]);
}

[Fact]
public unsafe void InterpolationHandlerTest1()
{
Expand Down Expand Up @@ -600,8 +618,7 @@ public void AllSheetsTextColumnCodec()
var languages = header?.Languages ?? [Language.None];
foreach( var language in languages )
{
if( gameData.Excel.GetSheet<RawRow>( language, sheetName ) is not { } sheet )
continue;
var sheet = gameData.Excel.GetSheet< RawRow >( language, sheetName );

var stringColumns = sheet.Columns.Where( c => c.Type == ExcelColumnDataType.String ).Select( c => c.Offset ).ToArray();
foreach( var row in sheet )
Expand Down
34 changes: 30 additions & 4 deletions src/Lumina/Text/SeStringBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,47 @@ public SeStringBuilder Clear( bool zeroBuffer = false )
return this;
}

/// <summary>Gets the SeString as a new byte array.</summary>
/// <returns>A new byte array.</returns>
public byte[] ToArray()
/// <summary>Gets the view of SeString being built.</summary>
/// <returns>View of the SeString being built.</returns>
/// <remarks>
/// <p>Returned view is invalidated upon any mutation to this builder, including <see cref="Clear"/> and <see cref="Append(string)"/>. If
/// <see cref="SharedPool"/> is being used, then returning to the pool also will invalidate the returned view.</p>
/// <p>After the last element (right after the end of the returned memory/span), <c>NUL</c> is present. You can pin the returned value and use the pointer
/// to the first element as a pointer to null-terminated string.</p>
/// </remarks>
public ReadOnlyMemory< byte > GetViewAsMemory()
{
if( _mss.Count != 1 )
throw new InvalidOperationException( "The string is incomplete, due to non-empty stack." );
return _mss[ 0 ].Stream.ToArray();

var stream = _mss[ 0 ].Stream;

// Force null termination.
stream.Capacity = Math.Max( stream.Capacity, (int) stream.Length + 1 );
stream.GetBuffer()[ stream.Length ] = 0;

return stream.GetBuffer().AsMemory( 0, (int) stream.Length );
}

/// <inheritdoc cref="GetViewAsMemory"/>
public ReadOnlySpan< byte > GetViewAsSpan() => GetViewAsMemory().Span;

/// <summary>Gets the SeString as a new byte array.</summary>
/// <returns>A new byte array.</returns>
/// <remarks>If the created value does not escape the code scope this function is being called, consider using <see cref="GetViewAsMemory"/> or
/// <see cref="GetViewAsSpan"/>.</remarks>
public byte[] ToArray() => GetViewAsMemory().ToArray();

/// <summary>Gets the SeString as a new instance of <see cref="SeString"/>.</summary>
/// <returns>A new instance of <see cref="SeString"/>.</returns>
/// <remarks>If the created value does not escape the code scope this function is being called, consider using <see cref="GetViewAsMemory"/> or
/// <see cref="GetViewAsSpan"/>.</remarks>
public SeString ToSeString() => new( ToArray() );

/// <summary>Gets the SeString as a new instance of <see cref="ReadOnlySeString"/>.</summary>
/// <returns>A new instance of <see cref="ReadOnlySeString"/>.</returns>
/// <remarks>If the created value does not escape the code scope this function is being called, consider using <see cref="GetViewAsMemory"/> or
/// <see cref="GetViewAsSpan"/>.</remarks>
public ReadOnlySeString ToReadOnlySeString() => ToArray();

/// <inheritdoc/>
Expand Down

0 comments on commit db7deb5

Please sign in to comment.