Skip to content

Commit db7deb5

Browse files
authored
Merge pull request #100 from Soreepeong/feature/ssb-getview
Add SeStringBuilder.GetViewAsMemory/Span
2 parents b99601a + 3675b80 commit db7deb5

File tree

2 files changed

+49
-6
lines changed

2 files changed

+49
-6
lines changed

src/Lumina.Tests/SeStringBuilderTests.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,24 @@ public void AddonIsParsedCorrectly()
443443
}
444444
}
445445

446+
[Fact]
447+
public unsafe void SpanViewNullTerminationTest()
448+
{
449+
var test = new SeStringBuilder()
450+
.AppendBold( "Test" )
451+
.Append( "Asdf" )
452+
.AppendItalicized( "Aaaaa" );
453+
var expected =
454+
"\x02\x19\x02\x02\x03"u8 + "Test"u8 + "\x02\x19\x02\x01\x03"u8 +
455+
"Asdf"u8 +
456+
"\x02\x1A\x02\x02\x03"u8 + "Aaaaa"u8 + "\x02\x1A\x02\x01\x03"u8;
457+
458+
var span = test.GetViewAsSpan();
459+
Assert.True( span.SequenceEqual( expected ) );
460+
fixed( byte* p = span )
461+
Assert.Equal( 0 , p[ span.Length ]);
462+
}
463+
446464
[Fact]
447465
public unsafe void InterpolationHandlerTest1()
448466
{
@@ -600,8 +618,7 @@ public void AllSheetsTextColumnCodec()
600618
var languages = header?.Languages ?? [Language.None];
601619
foreach( var language in languages )
602620
{
603-
if( gameData.Excel.GetSheet<RawRow>( language, sheetName ) is not { } sheet )
604-
continue;
621+
var sheet = gameData.Excel.GetSheet< RawRow >( language, sheetName );
605622

606623
var stringColumns = sheet.Columns.Where( c => c.Type == ExcelColumnDataType.String ).Select( c => c.Offset ).ToArray();
607624
foreach( var row in sheet )

src/Lumina/Text/SeStringBuilder.cs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,21 +94,47 @@ public SeStringBuilder Clear( bool zeroBuffer = false )
9494
return this;
9595
}
9696

97-
/// <summary>Gets the SeString as a new byte array.</summary>
98-
/// <returns>A new byte array.</returns>
99-
public byte[] ToArray()
97+
/// <summary>Gets the view of SeString being built.</summary>
98+
/// <returns>View of the SeString being built.</returns>
99+
/// <remarks>
100+
/// <p>Returned view is invalidated upon any mutation to this builder, including <see cref="Clear"/> and <see cref="Append(string)"/>. If
101+
/// <see cref="SharedPool"/> is being used, then returning to the pool also will invalidate the returned view.</p>
102+
/// <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
103+
/// to the first element as a pointer to null-terminated string.</p>
104+
/// </remarks>
105+
public ReadOnlyMemory< byte > GetViewAsMemory()
100106
{
101107
if( _mss.Count != 1 )
102108
throw new InvalidOperationException( "The string is incomplete, due to non-empty stack." );
103-
return _mss[ 0 ].Stream.ToArray();
109+
110+
var stream = _mss[ 0 ].Stream;
111+
112+
// Force null termination.
113+
stream.Capacity = Math.Max( stream.Capacity, (int) stream.Length + 1 );
114+
stream.GetBuffer()[ stream.Length ] = 0;
115+
116+
return stream.GetBuffer().AsMemory( 0, (int) stream.Length );
104117
}
105118

119+
/// <inheritdoc cref="GetViewAsMemory"/>
120+
public ReadOnlySpan< byte > GetViewAsSpan() => GetViewAsMemory().Span;
121+
122+
/// <summary>Gets the SeString as a new byte array.</summary>
123+
/// <returns>A new byte array.</returns>
124+
/// <remarks>If the created value does not escape the code scope this function is being called, consider using <see cref="GetViewAsMemory"/> or
125+
/// <see cref="GetViewAsSpan"/>.</remarks>
126+
public byte[] ToArray() => GetViewAsMemory().ToArray();
127+
106128
/// <summary>Gets the SeString as a new instance of <see cref="SeString"/>.</summary>
107129
/// <returns>A new instance of <see cref="SeString"/>.</returns>
130+
/// <remarks>If the created value does not escape the code scope this function is being called, consider using <see cref="GetViewAsMemory"/> or
131+
/// <see cref="GetViewAsSpan"/>.</remarks>
108132
public SeString ToSeString() => new( ToArray() );
109133

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

114140
/// <inheritdoc/>

0 commit comments

Comments
 (0)