move backend to subfolder
This commit is contained in:
parent
d4d1f2f981
commit
8d09663eff
80 changed files with 98 additions and 88 deletions
42
tanks-backend/DisplayCommands/ByteGrid.cs
Normal file
42
tanks-backend/DisplayCommands/ByteGrid.cs
Normal 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);
|
||||
}
|
71
tanks-backend/DisplayCommands/Cp437Grid.cs
Normal file
71
tanks-backend/DisplayCommands/Cp437Grid.cs
Normal 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);
|
||||
}
|
23
tanks-backend/DisplayCommands/DisplayCommands.csproj
Normal file
23
tanks-backend/DisplayCommands/DisplayCommands.csproj
Normal 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>
|
8
tanks-backend/DisplayCommands/DisplayConfiguration.cs
Normal file
8
tanks-backend/DisplayCommands/DisplayConfiguration.cs
Normal 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;
|
||||
}
|
25
tanks-backend/DisplayCommands/DisplayExtensions.cs
Normal file
25
tanks-backend/DisplayCommands/DisplayExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
2
tanks-backend/DisplayCommands/GlobalUsings.cs
Normal file
2
tanks-backend/DisplayCommands/GlobalUsings.cs
Normal file
|
@ -0,0 +1,2 @@
|
|||
global using System;
|
||||
global using System.Threading.Tasks;
|
24
tanks-backend/DisplayCommands/IDisplayConnection.cs
Normal file
24
tanks-backend/DisplayCommands/IDisplayConnection.cs
Normal 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();
|
||||
}
|
17
tanks-backend/DisplayCommands/Internals/DisplayCommand.cs
Normal file
17
tanks-backend/DisplayCommands/Internals/DisplayCommand.cs
Normal 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
|
||||
}
|
126
tanks-backend/DisplayCommands/Internals/DisplayConnection.cs
Normal file
126
tanks-backend/DisplayCommands/Internals/DisplayConnection.cs
Normal 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();
|
||||
}
|
10
tanks-backend/DisplayCommands/Internals/DisplaySubCommand.cs
Normal file
10
tanks-backend/DisplayCommands/Internals/DisplaySubCommand.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace DisplayCommands.Internals;
|
||||
|
||||
internal enum DisplaySubCommand : ushort
|
||||
{
|
||||
BitmapNormal = 0x0,
|
||||
BitmapCompressZ = 0x677a,
|
||||
BitmapCompressBz = 0x627a,
|
||||
BitmapCompressLz = 0x6c7a,
|
||||
BitmapCompressZs = 0x7a73
|
||||
}
|
19
tanks-backend/DisplayCommands/Internals/HeaderBitmap.cs
Normal file
19
tanks-backend/DisplayCommands/Internals/HeaderBitmap.cs
Normal 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;
|
||||
}
|
19
tanks-backend/DisplayCommands/Internals/HeaderWindow.cs
Normal file
19
tanks-backend/DisplayCommands/Internals/HeaderWindow.cs
Normal 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;
|
||||
}
|
59
tanks-backend/DisplayCommands/PixelGrid.cs
Normal file
59
tanks-backend/DisplayCommands/PixelGrid.cs
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue