Skip to content

Commit

Permalink
Use caching ConsoleStream for both iOS and Android (#58967)
Browse files Browse the repository at this point in the history
Expands on #56713 by moving the caching implementation to a separate internal class leaving only the interop calls in ConsolePal.iOS and ConsolePal.Android

Fixes #57304
  • Loading branch information
steveisok authored Sep 14, 2021
1 parent a56b732 commit edeb905
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 73 deletions.
1 change: 1 addition & 0 deletions src/libraries/System.Console/src/System.Console.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<Compile Include="System\ConsoleKeyInfo.cs" />
<Compile Include="System\ConsoleModifiers.cs" />
<Compile Include="System\IO\ConsoleStream.cs" />
<Compile Include="System\IO\CachedConsoleStream.cs" />
<Compile Include="System\IO\SyncTextReader.cs" />
<Compile Include="System\IO\Error.cs" />
<Compile Include="$(CommonPath)System\Text\ConsoleEncoding.cs"
Expand Down
14 changes: 6 additions & 8 deletions src/libraries/System.Console/src/System/ConsolePal.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@

namespace System
{
internal sealed unsafe class LogcatStream : ConsoleStream
internal sealed unsafe class LogcatStream : CachedConsoleStream
{
public LogcatStream() : base(FileAccess.Write) {}
public LogcatStream(Encoding encoding) : base(encoding) {}

public override int Read(Span<byte> buffer) => throw Error.GetReadNotSupported();

public override unsafe void Write(ReadOnlySpan<byte> buffer)
protected override void Print(ReadOnlySpan<char> line)
{
string log = ConsolePal.OutputEncoding.GetString(buffer);
string log = line.ToString();
Interop.Logcat.AndroidLogPrint(Interop.Logcat.LogLevel.Info, "DOTNET", log);
}
}
Expand All @@ -26,9 +24,9 @@ internal static void EnsureConsoleInitialized() { }

public static Stream OpenStandardInput() => throw new PlatformNotSupportedException();

public static Stream OpenStandardOutput() => new LogcatStream();
public static Stream OpenStandardOutput() => new LogcatStream(OutputEncoding);

public static Stream OpenStandardError() => new LogcatStream();
public static Stream OpenStandardError() => new LogcatStream(OutputEncoding);

public static Encoding InputEncoding => throw new PlatformNotSupportedException();

Expand Down
70 changes: 5 additions & 65 deletions src/libraries/System.Console/src/System/ConsolePal.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,75 +8,15 @@

namespace System
{
internal sealed class NSLogStream : ConsoleStream
internal sealed class NSLogStream : CachedConsoleStream
{
private readonly StringBuilder _buffer = new StringBuilder();
private readonly Encoding _encoding;
private readonly Decoder _decoder;
public NSLogStream(Encoding encoding) : base(encoding) {}

public NSLogStream(Encoding encoding) : base(FileAccess.Write)
protected override unsafe void Print(ReadOnlySpan<char> line)
{
_encoding = encoding;
_decoder = _encoding.GetDecoder();
}

public override int Read(Span<byte> buffer) => throw Error.GetReadNotSupported();

public override void Write(ReadOnlySpan<byte> buffer)
{
int maxCharCount = _encoding.GetMaxCharCount(buffer.Length);
char[]? pooledBuffer = null;
Span<char> charSpan = maxCharCount <= 512 ? stackalloc char[512] : (pooledBuffer = ArrayPool<char>.Shared.Rent(maxCharCount));
try
{
int count = _decoder.GetChars(buffer, charSpan, false);
if (count > 0)
{
WriteOrCache(_buffer, charSpan.Slice(0, count));
}
}
finally
{
if (pooledBuffer != null)
{
ArrayPool<char>.Shared.Return(pooledBuffer);
}
}
}

private static void WriteOrCache(StringBuilder cache, Span<char> charBuffer)
{
int lastNewLine = charBuffer.LastIndexOf('\n');
if (lastNewLine != -1)
{
Span<char> lineSpan = charBuffer.Slice(0, lastNewLine);
if (cache.Length > 0)
{
Print(cache.Append(lineSpan).ToString());
cache.Clear();
}
else
{
Print(lineSpan);
}

if (lastNewLine + 1 < charBuffer.Length)
{
cache.Append(charBuffer.Slice(lastNewLine + 1));
}

return;
}

// no newlines found, add the entire buffer to the cache
cache.Append(charBuffer);

static unsafe void Print(ReadOnlySpan<char> line)
fixed (char* ptr = line)
{
fixed (char* ptr = line)
{
Interop.Sys.Log((byte*)ptr, line.Length * 2);
}
Interop.Sys.Log((byte*)ptr, line.Length * 2);
}
}
}
Expand Down
77 changes: 77 additions & 0 deletions src/libraries/System.Console/src/System/IO/CachedConsoleStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace System.IO
{
internal abstract class CachedConsoleStream : ConsoleStream
{
private readonly StringBuilder _buffer = new StringBuilder();
private readonly Encoding _encoding;
private readonly Decoder _decoder;

public CachedConsoleStream(Encoding encoding) : base(FileAccess.Write)
{
_encoding = encoding;
_decoder = _encoding.GetDecoder();
}

public override int Read(Span<byte> buffer) => throw Error.GetReadNotSupported();

public override void Write(ReadOnlySpan<byte> buffer)
{
int maxCharCount = _encoding.GetMaxCharCount(buffer.Length);
char[]? pooledBuffer = null;
Span<char> charSpan = maxCharCount <= 512 ? stackalloc char[512] : (pooledBuffer = ArrayPool<char>.Shared.Rent(maxCharCount));
try
{
int count = _decoder.GetChars(buffer, charSpan, false);
if (count > 0)
{
WriteOrCache(this, _buffer, charSpan.Slice(0, count));
}
}
finally
{
if (pooledBuffer != null)
{
ArrayPool<char>.Shared.Return(pooledBuffer);
}
}
}

protected abstract void Print(ReadOnlySpan<char> line);

private static void WriteOrCache(CachedConsoleStream stream, StringBuilder cache, Span<char> charBuffer)
{
int lastNewLine = charBuffer.LastIndexOf('\n');
if (lastNewLine != -1)
{
Span<char> lineSpan = charBuffer.Slice(0, lastNewLine);
if (cache.Length > 0)
{
stream.Print(cache.Append(lineSpan).ToString());
cache.Clear();
}
else
{
stream.Print(lineSpan);
}

if (lastNewLine + 1 < charBuffer.Length)
{
cache.Append(charBuffer.Slice(lastNewLine + 1));
}

return;
}

// no newlines found, add the entire buffer to the cache
cache.Append(charBuffer);
}
}
}

0 comments on commit edeb905

Please sign in to comment.