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

Add SeStringBuilder.GetViewAsMemory/Span #100

Merged
merged 2 commits into from
Nov 20, 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
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
Loading