merge preamble-output, socket-output

This commit is contained in:
Ben Adams 2015-11-20 21:27:59 +00:00
parent a3173c487a
commit feb40402de
9 changed files with 173 additions and 201 deletions

View File

@ -68,13 +68,29 @@ namespace Microsoft.AspNet.Server.Kestrel.Filter
public override void Write(byte[] buffer, int offset, int count)
{
var segment = new ArraySegment<byte>(buffer, offset, count);
ArraySegment<byte> segment;
if (buffer != null)
{
segment = new ArraySegment<byte>(buffer, offset, count);
}
else
{
segment = default(ArraySegment<byte>);
}
_output.Write(segment);
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken token)
{
var segment = new ArraySegment<byte>(buffer, offset, count);
ArraySegment<byte> segment;
if (buffer != null)
{
segment = new ArraySegment<byte>(buffer, offset, count);
}
else
{
segment = default(ArraySegment<byte>);
}
return _output.WriteAsync(segment, cancellationToken: token);
}

View File

@ -52,7 +52,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Filter
returnBlock.Pool?.Return(returnBlock);
}
_outputStream.Write(end.Block.Array, end.Block.Data.Offset, end.Index);
_outputStream.Write(end.Block.Array, end.Block.Data.Offset, end.Index - end.Block.Data.Offset);
end.Block.Pool?.Return(end.Block);
}
}

View File

@ -623,8 +623,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
bool appCompleted,
bool immediate)
{
var memoryBlock = Memory2.Lease();
var begin = memoryBlock.GetIterator();
var begin = SocketOutput.ProducingStart();
var count = 0;
var end = begin;
if (_keepAlive)
{
@ -673,48 +673,20 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
_responseHeaders.SetRawConnection("keep-alive", _bytesConnectionKeepAlive);
}
end.CopyFrom(_httpVersion == HttpVersionType.Http1_1 ? _bytesHttpVersion1_1 : _bytesHttpVersion1_0);
end.CopyFrom(statusBytes);
_responseHeaders.CopyTo(ref end);
end.CopyFrom(_bytesEndHeaders, 0, _bytesEndHeaders.Length);
count += end.CopyFrom(_httpVersion == HttpVersionType.Http1_1 ? _bytesHttpVersion1_1 : _bytesHttpVersion1_0);
count += end.CopyFrom(statusBytes);
count += _responseHeaders.CopyTo(ref end);
count += end.CopyFrom(_bytesEndHeaders, 0, _bytesEndHeaders.Length);
// TODO: change this to SocketOutput.ProduceStart/ProduceComplete once that change is made
var scan = begin.Block;
while (scan.Next != null)
{
if (scan.Start != scan.End)
{
SocketOutput.WriteAsync(
new ArraySegment<byte>(scan.Array, scan.Start, scan.End - scan.Start),
false);
}
var next = scan.Next;
Memory2.Return(scan);
scan = next;
}
var writeTask = SocketOutput.WriteAsync(
new ArraySegment<byte>(scan.Array, scan.Start, scan.End - scan.Start),
immediate);
SocketOutput.ProducingComplete(end, count);
if (writeTask.IsCompleted)
if (immediate)
{
Memory2.Return(scan);
return TaskUtilities.CompletedTask;
return SocketOutput.WriteAsync(default(ArraySegment<byte>), immediate: true);
}
else
{
return writeTask.ContinueWith(
(t, o) =>
{
var mb = (MemoryPoolBlock2)o;
mb.Pool.Return(mb);
if (t.IsFaulted)
{
throw t.Exception;
}
},
scan);
return TaskUtilities.CompletedTask;
}
}

View File

@ -7821,15 +7821,16 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
((ICollection<KeyValuePair<string, StringValues>>)MaybeUnknown)?.CopyTo(array, arrayIndex);
}
protected void CopyToFast(ref MemoryPoolIterator2 output)
protected int CopyToFast(ref MemoryPoolIterator2 output)
{
var count = 0;
if (((_bits & 1L) != 0))
{
foreach(var value in _CacheControl)
{
output.CopyFrom(_headerBytes, 0, 17);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 0, 17);
count += output.CopyFromAscii(value);
}
}
@ -7837,12 +7838,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
if (_rawConnection != null)
{
output.CopyFrom(_rawConnection, 0, _rawConnection.Length);
count += output.CopyFrom(_rawConnection, 0, _rawConnection.Length);
} else
foreach(var value in _Connection)
{
output.CopyFrom(_headerBytes, 17, 14);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 17, 14);
count += output.CopyFromAscii(value);
}
}
@ -7850,12 +7851,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
if (_rawDate != null)
{
output.CopyFrom(_rawDate, 0, _rawDate.Length);
count += output.CopyFrom(_rawDate, 0, _rawDate.Length);
} else
foreach(var value in _Date)
{
output.CopyFrom(_headerBytes, 31, 8);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 31, 8);
count += output.CopyFromAscii(value);
}
}
@ -7863,8 +7864,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _KeepAlive)
{
output.CopyFrom(_headerBytes, 39, 14);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 39, 14);
count += output.CopyFromAscii(value);
}
}
@ -7872,8 +7873,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _Pragma)
{
output.CopyFrom(_headerBytes, 53, 10);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 53, 10);
count += output.CopyFromAscii(value);
}
}
@ -7881,8 +7882,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _Trailer)
{
output.CopyFrom(_headerBytes, 63, 11);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 63, 11);
count += output.CopyFromAscii(value);
}
}
@ -7890,12 +7891,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
if (_rawTransferEncoding != null)
{
output.CopyFrom(_rawTransferEncoding, 0, _rawTransferEncoding.Length);
count += output.CopyFrom(_rawTransferEncoding, 0, _rawTransferEncoding.Length);
} else
foreach(var value in _TransferEncoding)
{
output.CopyFrom(_headerBytes, 74, 21);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 74, 21);
count += output.CopyFromAscii(value);
}
}
@ -7903,8 +7904,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _Upgrade)
{
output.CopyFrom(_headerBytes, 95, 11);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 95, 11);
count += output.CopyFromAscii(value);
}
}
@ -7912,8 +7913,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _Via)
{
output.CopyFrom(_headerBytes, 106, 7);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 106, 7);
count += output.CopyFromAscii(value);
}
}
@ -7921,8 +7922,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _Warning)
{
output.CopyFrom(_headerBytes, 113, 11);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 113, 11);
count += output.CopyFromAscii(value);
}
}
@ -7930,8 +7931,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _Allow)
{
output.CopyFrom(_headerBytes, 124, 9);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 124, 9);
count += output.CopyFromAscii(value);
}
}
@ -7939,12 +7940,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
if (_rawContentLength != null)
{
output.CopyFrom(_rawContentLength, 0, _rawContentLength.Length);
count += output.CopyFrom(_rawContentLength, 0, _rawContentLength.Length);
} else
foreach(var value in _ContentLength)
{
output.CopyFrom(_headerBytes, 133, 18);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 133, 18);
count += output.CopyFromAscii(value);
}
}
@ -7952,8 +7953,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _ContentType)
{
output.CopyFrom(_headerBytes, 151, 16);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 151, 16);
count += output.CopyFromAscii(value);
}
}
@ -7961,8 +7962,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _ContentEncoding)
{
output.CopyFrom(_headerBytes, 167, 20);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 167, 20);
count += output.CopyFromAscii(value);
}
}
@ -7970,8 +7971,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _ContentLanguage)
{
output.CopyFrom(_headerBytes, 187, 20);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 187, 20);
count += output.CopyFromAscii(value);
}
}
@ -7979,8 +7980,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _ContentLocation)
{
output.CopyFrom(_headerBytes, 207, 20);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 207, 20);
count += output.CopyFromAscii(value);
}
}
@ -7988,8 +7989,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _ContentMD5)
{
output.CopyFrom(_headerBytes, 227, 15);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 227, 15);
count += output.CopyFromAscii(value);
}
}
@ -7997,8 +7998,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _ContentRange)
{
output.CopyFrom(_headerBytes, 242, 17);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 242, 17);
count += output.CopyFromAscii(value);
}
}
@ -8006,8 +8007,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _Expires)
{
output.CopyFrom(_headerBytes, 259, 11);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 259, 11);
count += output.CopyFromAscii(value);
}
}
@ -8015,8 +8016,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _LastModified)
{
output.CopyFrom(_headerBytes, 270, 17);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 270, 17);
count += output.CopyFromAscii(value);
}
}
@ -8024,8 +8025,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _AcceptRanges)
{
output.CopyFrom(_headerBytes, 287, 17);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 287, 17);
count += output.CopyFromAscii(value);
}
}
@ -8033,8 +8034,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _Age)
{
output.CopyFrom(_headerBytes, 304, 7);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 304, 7);
count += output.CopyFromAscii(value);
}
}
@ -8042,8 +8043,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _ETag)
{
output.CopyFrom(_headerBytes, 311, 8);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 311, 8);
count += output.CopyFromAscii(value);
}
}
@ -8051,8 +8052,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _Location)
{
output.CopyFrom(_headerBytes, 319, 12);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 319, 12);
count += output.CopyFromAscii(value);
}
}
@ -8060,8 +8061,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _ProxyAutheticate)
{
output.CopyFrom(_headerBytes, 331, 21);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 331, 21);
count += output.CopyFromAscii(value);
}
}
@ -8069,8 +8070,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _RetryAfter)
{
output.CopyFrom(_headerBytes, 352, 15);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 352, 15);
count += output.CopyFromAscii(value);
}
}
@ -8078,12 +8079,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
if (_rawServer != null)
{
output.CopyFrom(_rawServer, 0, _rawServer.Length);
count += output.CopyFrom(_rawServer, 0, _rawServer.Length);
} else
foreach(var value in _Server)
{
output.CopyFrom(_headerBytes, 367, 10);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 367, 10);
count += output.CopyFromAscii(value);
}
}
@ -8091,8 +8092,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _SetCookie)
{
output.CopyFrom(_headerBytes, 377, 14);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 377, 14);
count += output.CopyFromAscii(value);
}
}
@ -8100,8 +8101,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _Vary)
{
output.CopyFrom(_headerBytes, 391, 8);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 391, 8);
count += output.CopyFromAscii(value);
}
}
@ -8109,11 +8110,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
foreach(var value in _WWWAuthenticate)
{
output.CopyFrom(_headerBytes, 399, 20);
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, 399, 20);
count += output.CopyFromAscii(value);
}
}
return count;
}
public unsafe void Append(byte[] keyBytes, int keyOffset, int keyLength, string value)
{

View File

@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
public partial class FrameResponseHeaders : FrameHeaders
{
private static byte[] _CRLF = new[] { (byte)'\r', (byte)'\n' };
private static byte[] _CrLf = new[] { (byte)'\r', (byte)'\n' };
private static byte[] _colonSpace = new[] { (byte)':', (byte)' ' };
public bool HasConnection => HeaderConnection.Count != 0;
@ -30,22 +30,23 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
return GetEnumerator();
}
public void CopyTo(ref MemoryPoolIterator2 output)
public int CopyTo(ref MemoryPoolIterator2 output)
{
CopyToFast(ref output);
var count = CopyToFast(ref output);
if (MaybeUnknown != null)
{
foreach (var kv in MaybeUnknown)
{
foreach (var value in kv.Value)
{
output.CopyFrom(_CRLF, 0, 2);
output.CopyFromAscii(kv.Key);
output.CopyFrom(_colonSpace, 0, 2);
output.CopyFromAscii(value);
count += output.CopyFrom(_CrLf, 0, 2);
count += output.CopyFromAscii(kv.Key);
count += output.CopyFrom(_colonSpace, 0, 2);
count += output.CopyFromAscii(value);
}
}
}
return count;
}
public partial struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>

View File

@ -70,11 +70,13 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
bool socketShutdownSend = false,
bool socketDisconnect = false)
{
var tail = ProducingStart();
tail = tail.CopyFrom(buffer);
// We do our own accounting below
ProducingComplete(tail, count: 0);
if (buffer.Count > 0)
{
var tail = ProducingStart();
tail.CopyFrom(buffer);
// We do our own accounting below
ProducingComplete(tail, count: 0);
}
TaskCompletionSource<object> tcs = null;
lock (_contextLock)

View File

@ -532,7 +532,17 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
}
}
public MemoryPoolIterator2 CopyFrom(ArraySegment<byte> buffer)
public int CopyFrom(byte[] data)
{
return CopyFrom(new ArraySegment<byte>(data));
}
public int CopyFrom(byte[] data, int offset, int count)
{
return CopyFrom(new ArraySegment<byte>(data, offset, count));
}
public int CopyFrom(ArraySegment<byte> buffer)
{
Debug.Assert(_block != null);
Debug.Assert(_block.Pool != null);
@ -545,11 +555,10 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
var bufferIndex = buffer.Offset;
var remaining = buffer.Count;
var bytesLeftInBlock = block.Data.Offset + block.Data.Count - blockIndex;
while (remaining > 0)
{
var bytesLeftInBlock = block.Data.Offset + block.Data.Count - blockIndex;
if (bytesLeftInBlock == 0)
{
var nextBlock = pool.Lease();
@ -560,93 +569,55 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
bytesLeftInBlock = block.Data.Count;
}
var bytesToCopy = Math.Min(remaining, bytesLeftInBlock);
var bytesToCopy = remaining < bytesLeftInBlock ? remaining : bytesLeftInBlock;
Buffer.BlockCopy(buffer.Array, bufferIndex, block.Array, blockIndex, bytesToCopy);
blockIndex += bytesToCopy;
bufferIndex += bytesToCopy;
remaining -= bytesToCopy;
bytesLeftInBlock -= bytesToCopy;
block.End = blockIndex;
}
return new MemoryPoolIterator2(block, blockIndex);
_block = block;
_index = blockIndex;
return buffer.Count;
}
public void CopyFrom(byte[] data)
{
CopyFrom(data, 0, data.Length);
}
public void CopyFrom(byte[] data, int offset, int count)
public unsafe int CopyFromAscii(string data)
{
Debug.Assert(_block != null);
Debug.Assert(_block.Pool != null);
Debug.Assert(_block.Next == null);
Debug.Assert(_block.End == _index);
var pool = _block.Pool;
var block = _block;
var blockIndex = _index;
var length = data.Length;
var sourceData = data;
var sourceStart = offset;
var sourceEnd = offset + count;
var targetData = block.Array;
var targetStart = block.End;
var targetEnd = block.Data.Offset + block.Data.Count;
while (true)
{
// actual count to copy is remaining data, or unused trailing space in the current block, whichever is smaller
var copyCount = Math.Min(sourceEnd - sourceStart, targetEnd - targetStart);
Buffer.BlockCopy(sourceData, sourceStart, targetData, targetStart, copyCount);
sourceStart += copyCount;
targetStart += copyCount;
// if this means all source data has been copied
if (sourceStart == sourceEnd)
{
// increase occupied space in the block, and adjust iterator at start of unused trailing space
block.End = targetStart;
_block = block;
_index = targetStart;
return;
}
// otherwise another block needs to be allocated to follow this one
block.Next = block.Pool.Lease();
block = block.Next;
targetData = block.Array;
targetStart = block.End;
targetEnd = block.Data.Offset + block.Data.Count;
}
}
public unsafe void CopyFromAscii(string data)
{
Debug.Assert(_block.Next == null);
Debug.Assert(_block.End == _index);
var block = _block;
var inputLength = data.Length;
var inputLengthMinusSpan = inputLength - 3;
var bytesLeftInBlock = block.Data.Offset + block.Data.Count - blockIndex;
var bytesLeftInBlockMinusSpan = bytesLeftInBlock - 3;
fixed (char* pData = data)
{
var input = pData;
var inputEnd = pData + data.Length;
var blockRemaining = block.Data.Offset + block.Data.Count - block.End;
var blockRemainingMinusSpan = blockRemaining - 3;
var inputEnd = pData + length;
var inputEndMinusSpan = inputEnd - 3;
while (input < inputEnd)
{
if (blockRemaining == 0)
if (bytesLeftInBlock == 0)
{
block.Next = block.Pool.Lease();
block = block.Next;
blockRemaining = block.Data.Count;
blockRemainingMinusSpan = blockRemaining - 3;
var nextBlock = pool.Lease();
block.Next = nextBlock;
block = nextBlock;
blockIndex = block.Data.Offset;
bytesLeftInBlock = block.Data.Count;
bytesLeftInBlockMinusSpan = bytesLeftInBlock - 3;
}
fixed (byte* pOutput = block.Data.Array)
@ -654,7 +625,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
var output = pOutput + block.End;
var copied = 0;
for (; copied < inputLengthMinusSpan && copied < blockRemainingMinusSpan; copied += 4)
for (; input < inputEndMinusSpan && copied < bytesLeftInBlockMinusSpan; copied += 4)
{
*(output) = (byte)*(input);
*(output + 1) = (byte)*(input + 1);
@ -662,19 +633,24 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
*(output + 3) = (byte)*(input + 3);
output += 4;
input += 4;
blockRemainingMinusSpan -= 4;
}
for (; copied < inputLength && copied < blockRemaining; copied++)
for (; input < inputEnd && copied < bytesLeftInBlock; copied++)
{
*(output++) = (byte)*(input++);
blockRemaining--;
}
block.End += copied;
_block = block;
_index = block.End;
blockIndex += copied;
bytesLeftInBlockMinusSpan -= copied;
bytesLeftInBlock -= copied;
}
block.End = blockIndex;
}
}
_block = block;
_index = blockIndex;
return length;
}
}
}

View File

@ -159,7 +159,8 @@ namespace Microsoft.AspNet.Server.KestrelTests
using (var pool = new MemoryPool2())
{
var block1 = pool.Lease(128);
var iterator = block1.GetIterator();
var start = block1.GetIterator();
var end = start;
var bufferSize = block1.Data.Count * 3;
var buffer = new byte[bufferSize];
@ -170,18 +171,18 @@ namespace Microsoft.AspNet.Server.KestrelTests
Assert.Null(block1.Next);
var end = iterator.CopyFrom(new ArraySegment<byte>(buffer));
end.CopyFrom(new ArraySegment<byte>(buffer));
Assert.NotNull(block1.Next);
for (int i = 0; i < bufferSize; i++)
{
Assert.Equal(i % 73, iterator.Take());
Assert.Equal(i % 73, start.Take());
}
Assert.Equal(-1, iterator.Take());
Assert.Equal(iterator.Block, end.Block);
Assert.Equal(iterator.Index, end.Index);
Assert.Equal(-1, start.Take());
Assert.Equal(start.Block, end.Block);
Assert.Equal(start.Index, end.Index);
}
}

View File

@ -380,22 +380,24 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
((ICollection<KeyValuePair<string, StringValues>>)MaybeUnknown)?.CopyTo(array, arrayIndex);
}}
{(loop.ClassName == "FrameResponseHeaders" ? $@"
protected void CopyToFast(ref MemoryPoolIterator2 output)
protected int CopyToFast(ref MemoryPoolIterator2 output)
{{
var count = 0;
{Each(loop.Headers, header => $@"
if ({header.TestBit()})
{{ {(header.EnhancedSetter == false ? "" : $@"
if (_raw{header.Identifier} != null)
{{
output.CopyFromAscii(_raw{header.Identifier}, 0, _raw{header.Identifier}.Length);
count += output.CopyFrom(_raw{header.Identifier}, 0, _raw{header.Identifier}.Length);
}} else ")}
foreach(var value in _{header.Identifier})
{{
output.CopyFromAscii(_headerBytes, {header.BytesOffset}, {header.BytesCount});
output.CopyFromAscii(value);
count += output.CopyFrom(_headerBytes, {header.BytesOffset}, {header.BytesCount});
count += output.CopyFromAscii(value);
}}
}}
")}
return count;
}}" : "")}
public unsafe void Append(byte[] keyBytes, int keyOffset, int keyLength, string value)
{{