move backend to subfolder

This commit is contained in:
Vinzenz Schroeter 2024-04-21 12:38:03 +02:00
parent d4d1f2f981
commit 8d09663eff
80 changed files with 98 additions and 88 deletions

View file

@ -0,0 +1,42 @@
using System.Diagnostics;
namespace DisplayCommands;
public sealed class ByteGrid(ushort width, ushort height) : IEquatable<ByteGrid>
{
public ushort Height { get; } = height;
public ushort Width { get; } = width;
internal Memory<byte> Data { get; } = new byte[width * height].AsMemory();
public byte this[ushort x, ushort y]
{
get => Data.Span[GetIndex(x, y)];
set => Data.Span[GetIndex(x, y)] = value;
}
public bool Equals(ByteGrid? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Height == other.Height && Width == other.Width && Data.Span.SequenceEqual(other.Data.Span);
}
private int GetIndex(ushort x, ushort y)
{
Debug.Assert(x < Width);
Debug.Assert(y < Height);
return x + y * Width;
}
public void Clear() => Data.Span.Clear();
public override bool Equals(object? obj) => ReferenceEquals(this, obj) || (obj is ByteGrid other && Equals(other));
public override int GetHashCode() => HashCode.Combine(Height, Width, Data);
public static bool operator ==(ByteGrid? left, ByteGrid? right) => Equals(left, right);
public static bool operator !=(ByteGrid? left, ByteGrid? right) => !Equals(left, right);
}

View file

@ -0,0 +1,71 @@
using System.Diagnostics;
using System.Text;
namespace DisplayCommands;
public sealed class Cp437Grid(ushort width, ushort height) : IEquatable<Cp437Grid>
{
private readonly ByteGrid _byteGrid = new(width, height);
private readonly Encoding _encoding = Encoding.GetEncoding(437);
public ushort Height { get; } = height;
public ushort Width { get; } = width;
internal Memory<byte> Data => _byteGrid.Data;
public char this[ushort x, ushort y]
{
get => ByteToChar(_byteGrid[x, y]);
set => _byteGrid[x, y] = CharToByte(value);
}
public string this[ushort row]
{
get
{
var rowStart = row * Width;
return _encoding.GetString(_byteGrid.Data[rowStart..(rowStart + Width)].Span);
}
set
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(row, Height, nameof(row));
ArgumentOutOfRangeException.ThrowIfGreaterThan(value.Length, Width, nameof(value));
ushort x = 0;
for (; x < value.Length; x++)
_byteGrid[x, row] = CharToByte(value[x]);
for (; x < Width; x++)
_byteGrid[x, row] = CharToByte(' ');
}
}
private byte CharToByte(char c)
{
ReadOnlySpan<char> valuesStr = stackalloc char[] { c };
Span<byte> convertedStr = stackalloc byte[1];
var consumed = _encoding.GetBytes(valuesStr, convertedStr);
Debug.Assert(consumed == 1);
return convertedStr[0];
}
private char ByteToChar(byte b)
{
ReadOnlySpan<byte> valueBytes = stackalloc byte[] { b };
Span<char> resultStr = stackalloc char[1];
var consumed = _encoding.GetChars(valueBytes, resultStr);
Debug.Assert(consumed == 1);
return resultStr[0];
}
public bool Equals(Cp437Grid? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Height == other.Height && Width == other.Width && _byteGrid.Equals(other._byteGrid);
}
public override bool Equals(object? obj) => ReferenceEquals(this, obj) || (obj is Cp437Grid other && Equals(other));
public override int GetHashCode() => HashCode.Combine(_byteGrid, Height, Width);
public static bool operator ==(Cp437Grid? left, Cp437Grid? right) => Equals(left, right);
public static bool operator !=(Cp437Grid? left, Cp437Grid? right) => !Equals(left, right);
}

View file

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../shared.props" />
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0"/>
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2"/>
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0"/>
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1"/>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1"/>
<ProjectReference Include="../EndiannessSourceGenerator/EndiannessSourceGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"/>
</ItemGroup>
</Project>

View file

@ -0,0 +1,8 @@
namespace DisplayCommands;
public class DisplayConfiguration
{
public string Hostname { get; set; } = "172.23.42.29";
public int Port { get; set; } = 2342;
}

View file

@ -0,0 +1,25 @@
using System.Text;
using DisplayCommands.Internals;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace DisplayCommands;
public static class DisplayExtensions
{
static DisplayExtensions()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
}
public static IServiceCollection AddDisplay(
this IServiceCollection services,
IConfigurationSection? configurationSection = null
)
{
services.AddSingleton<IDisplayConnection, DisplayConnection>();
if (configurationSection != null)
services.Configure<DisplayConfiguration>(configurationSection);
return services;
}
}

View file

@ -0,0 +1,2 @@
global using System;
global using System.Threading.Tasks;

View file

@ -0,0 +1,24 @@
namespace DisplayCommands;
public interface IDisplayConnection
{
ValueTask SendClearAsync();
ValueTask SendCp437DataAsync(ushort x, ushort y, Cp437Grid grid);
ValueTask SendBrightnessAsync(byte brightness);
ValueTask SendCharBrightnessAsync(ushort x, ushort y, ByteGrid luma);
ValueTask SendHardResetAsync();
ValueTask SendFadeOutAsync(byte loops);
public ValueTask SendBitmapLinearWindowAsync(ushort x, ushort y, PixelGrid pixels);
/// <summary>
/// Returns the IPv4 address that is associated with the interface with which the display is reachable.
/// </summary>
/// <returns>IPv4 as text</returns>
public string GetLocalIPv4();
}

View file

@ -0,0 +1,17 @@
namespace DisplayCommands.Internals;
internal enum DisplayCommand : ushort
{
Clear = 0x0002,
Cp437Data = 0x0003,
CharBrightness = 0x0005,
Brightness = 0x0007,
HardReset = 0x000b,
FadeOut = 0x000d,
[Obsolete("ignored by display code")] BitmapLegacy = 0x0010,
BitmapLinear = 0x0012,
BitmapLinearWin = 0x0013,
BitmapLinearAnd = 0x0014,
BitmapLinearOr = 0x0015,
BitmapLinearXor = 0x0016
}

View file

@ -0,0 +1,126 @@
using System.Buffers;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Options;
namespace DisplayCommands.Internals;
internal sealed class DisplayConnection(IOptions<DisplayConfiguration> options) : IDisplayConnection, IDisposable
{
private readonly ArrayPool<byte> _arrayPool = ArrayPool<byte>.Shared;
private readonly UdpClient _udpClient = new(options.Value.Hostname, options.Value.Port);
public ValueTask SendClearAsync()
{
var header = new HeaderWindow { Command = (ushort)DisplayCommand.Clear };
return SendAsync(header, Memory<byte>.Empty);
}
public ValueTask SendCp437DataAsync(ushort x, ushort y, Cp437Grid grid)
{
var header = new HeaderWindow
{
Command = (ushort)DisplayCommand.Cp437Data,
Height = grid.Height,
Width = grid.Width,
PosX = x,
PosY = y
};
return SendAsync(header, grid.Data);
}
public ValueTask SendCharBrightnessAsync(ushort x, ushort y, ByteGrid luma)
{
var header = new HeaderWindow
{
Command = (ushort)DisplayCommand.CharBrightness,
PosX = x,
PosY = y,
Height = luma.Height,
Width = luma.Width
};
return SendAsync(header, luma.Data);
}
public async ValueTask SendBrightnessAsync(byte brightness)
{
var header = new HeaderWindow { Command = (ushort)DisplayCommand.Brightness };
var payloadBuffer = _arrayPool.Rent(1);
var payload = payloadBuffer.AsMemory(0, 1);
payload.Span[0] = brightness;
await SendAsync(header, payload);
_arrayPool.Return(payloadBuffer);
}
public ValueTask SendHardResetAsync()
{
var header = new HeaderWindow { Command = (ushort)DisplayCommand.HardReset };
return SendAsync(header, Memory<byte>.Empty);
}
public async ValueTask SendFadeOutAsync(byte loops)
{
var header = new HeaderWindow { Command = (ushort)DisplayCommand.FadeOut };
var payloadBuffer = _arrayPool.Rent(1);
var payload = payloadBuffer.AsMemory(0, 1);
payload.Span[0] = loops;
await SendAsync(header, payload);
_arrayPool.Return(payloadBuffer);
}
public ValueTask SendBitmapLinearWindowAsync(ushort x, ushort y, PixelGrid pixels)
{
var header = new HeaderWindow
{
Command = (ushort)DisplayCommand.BitmapLinearWin,
PosX = x,
PosY = y,
Width = (ushort)(pixels.Width / 8),
Height = pixels.Height
};
return SendAsync(header, pixels.Data);
}
public string GetLocalIPv4()
{
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 0);
socket.Connect(options.Value.Hostname, options.Value.Port);
var endPoint = socket.LocalEndPoint as IPEndPoint ?? throw new NotSupportedException();
return endPoint.Address.ToString();
}
private async ValueTask SendAsync(HeaderWindow header, Memory<byte> payload)
{
int headerSize;
unsafe
{
// because we specified the struct layout, no platform-specific padding will be added and this is be safe.
headerSize = sizeof(HeaderWindow);
}
Debug.Assert(headerSize == 10);
var messageSize = headerSize + payload.Length;
var buffer = _arrayPool.Rent(messageSize);
var message = buffer.AsMemory(0, messageSize);
MemoryMarshal.Write(message.Span, header);
payload.CopyTo(message[headerSize..]);
await _udpClient.SendAsync(message);
_arrayPool.Return(buffer);
}
public void Dispose() => _udpClient.Dispose();
}

View file

@ -0,0 +1,10 @@
namespace DisplayCommands.Internals;
internal enum DisplaySubCommand : ushort
{
BitmapNormal = 0x0,
BitmapCompressZ = 0x677a,
BitmapCompressBz = 0x627a,
BitmapCompressLz = 0x6c7a,
BitmapCompressZs = 0x7a73
}

View file

@ -0,0 +1,19 @@
using System.Runtime.InteropServices;
using EndiannessSourceGenerator;
namespace DisplayCommands.Internals;
[StructEndianness(IsLittleEndian = false)]
[StructLayout(LayoutKind.Sequential, Pack = 16, Size = 10)]
internal partial struct HeaderBitmap
{
private ushort _command;
private ushort _offset;
private ushort _length;
private ushort _subCommand;
private ushort _reserved;
}

View file

@ -0,0 +1,19 @@
using System.Runtime.InteropServices;
using EndiannessSourceGenerator;
namespace DisplayCommands.Internals;
[StructEndianness(IsLittleEndian = false)]
[StructLayout(LayoutKind.Sequential, Pack = 16, Size = 10)]
internal partial struct HeaderWindow
{
private ushort _command;
private ushort _posX;
private ushort _posY;
private ushort _width;
private ushort _height;
}

View file

@ -0,0 +1,59 @@
using System.Diagnostics;
namespace DisplayCommands;
public sealed class PixelGrid(ushort width, ushort height) : IEquatable<PixelGrid>
{
private readonly ByteGrid _byteGrid = new((ushort)(width / 8u), height);
public ushort Width { get; } = width;
public ushort Height { get; } = height;
public Memory<byte> Data => _byteGrid.Data;
public bool this[ushort x, ushort y]
{
get
{
Debug.Assert(y < Height);
var (byteIndex, bitInByteMask) = GetIndexes(x);
var byteVal = _byteGrid[byteIndex, y];
return (byteVal & bitInByteMask) != 0;
}
set
{
Debug.Assert(y < Height);
var (byteIndex, bitInByteMask) = GetIndexes(x);
if (value)
_byteGrid[byteIndex, y] |= bitInByteMask;
else
_byteGrid[byteIndex, y] &= (byte)(ushort.MaxValue ^ bitInByteMask);
}
}
public void Clear() => _byteGrid.Clear();
public bool Equals(PixelGrid? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Width == other.Width && Height == other.Height && _byteGrid.Equals(other._byteGrid);
}
public override bool Equals(object? obj) => ReferenceEquals(this, obj) || (obj is PixelGrid other && Equals(other));
public override int GetHashCode() => HashCode.Combine(_byteGrid, Width, Height);
public static bool operator ==(PixelGrid? left, PixelGrid? right) => Equals(left, right);
public static bool operator !=(PixelGrid? left, PixelGrid? right) => !Equals(left, right);
private (ushort byteIndex, byte bitInByteMask) GetIndexes(int x)
{
Debug.Assert(x < Width);
var byteIndex = (ushort)(x / 8);
Debug.Assert(byteIndex < Width);
var bitInByteIndex = (byte)(7 - x % 8);
Debug.Assert(bitInByteIndex < 8);
var bitInByteMask = (byte)(1 << bitInByteIndex);
return (byteIndex, bitInByteMask);
}
}