wip new display module
This commit is contained in:
parent
7e767d6dcb
commit
38463ac109
25
DisplayCommands/ByteGrid.cs
Normal file
25
DisplayCommands/ByteGrid.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using System.Diagnostics;
|
||||
|
||||
namespace DisplayCommands;
|
||||
|
||||
public class ByteGrid(ushort width, ushort height)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
private int GetIndex(ushort x, ushort y)
|
||||
{
|
||||
Debug.Assert(x < Width);
|
||||
Debug.Assert(y < Height);
|
||||
return x + y * Width;
|
||||
}
|
||||
}
|
59
DisplayCommands/Cp437Grid.cs
Normal file
59
DisplayCommands/Cp437Grid.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace DisplayCommands;
|
||||
|
||||
public sealed class Cp437Grid(ushort width, ushort height)
|
||||
{
|
||||
private readonly ByteGrid _byteGrid = new(width, height);
|
||||
|
||||
public ushort Height { get; } = height;
|
||||
public ushort Width { get; } = width;
|
||||
|
||||
internal Memory<byte> Data => _byteGrid.Data;
|
||||
|
||||
private readonly Encoding _encoding = Encoding.GetEncoding(437);
|
||||
|
||||
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];
|
||||
_encoding.GetChars(valueBytes, resultStr);
|
||||
return resultStr[0];
|
||||
}
|
||||
}
|
27
DisplayCommands/DisplayCommands.csproj
Normal file
27
DisplayCommands/DisplayCommands.csproj
Normal file
|
@ -0,0 +1,27 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<AnalysisMode>Recommended</AnalysisMode>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NoWarn>CA1805,CA1848</NoWarn>
|
||||
</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" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
8
DisplayCommands/DisplayConfiguration.cs
Normal file
8
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
DisplayCommands/DisplayExtensions.cs
Normal file
25
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;
|
||||
}
|
||||
}
|
4
DisplayCommands/GlobalUsings.cs
Normal file
4
DisplayCommands/GlobalUsings.cs
Normal file
|
@ -0,0 +1,4 @@
|
|||
// Global using directives
|
||||
|
||||
global using System;
|
||||
global using System.Threading.Tasks;
|
13
DisplayCommands/IDisplayConnection.cs
Normal file
13
DisplayCommands/IDisplayConnection.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace DisplayCommands;
|
||||
|
||||
public interface IDisplayConnection
|
||||
{
|
||||
ValueTask SendClearAsync();
|
||||
|
||||
ValueTask SendCp437DataAsync(ushort x, ushort y, Cp437Grid grid);
|
||||
|
||||
ValueTask SendCharBrightnessAsync(ushort x, ushort y, ByteGrid luma);
|
||||
ValueTask SendBrightnessAsync(byte brightness);
|
||||
ValueTask SendHardResetAsync();
|
||||
ValueTask SendFadeOutAsync(byte loops);
|
||||
}
|
17
DisplayCommands/Internals/DisplayCommand.cs
Normal file
17
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,
|
||||
BitmapLegacy = 0x0010,
|
||||
BitmapLinear = 0x0012,
|
||||
BitmapLinearWin = 0x0013,
|
||||
BitmapLinearAnd = 0x0014,
|
||||
BitmapLinearOr = 0x0015,
|
||||
BitmapLinearXor = 0x0016,
|
||||
}
|
104
DisplayCommands/Internals/DisplayConnection.cs
Normal file
104
DisplayCommands/Internals/DisplayConnection.cs
Normal file
|
@ -0,0 +1,104 @@
|
|||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
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 UdpClient _udpClient = new(options.Value.Hostname, options.Value.Port);
|
||||
private readonly ArrayPool<byte> _arrayPool = ArrayPool<byte>.Shared;
|
||||
|
||||
public ValueTask SendClearAsync()
|
||||
{
|
||||
var header = new HeaderWindow { Command = DisplayCommand.Clear };
|
||||
return SendAsync(header, Memory<byte>.Empty);
|
||||
}
|
||||
|
||||
public ValueTask SendCp437DataAsync(ushort x, ushort y, Cp437Grid grid)
|
||||
{
|
||||
var header = new HeaderWindow
|
||||
{
|
||||
Command = 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 = 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 = 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 = DisplayCommand.HardReset };
|
||||
return SendAsync(header, Memory<byte>.Empty);
|
||||
}
|
||||
|
||||
public async ValueTask SendFadeOutAsync(byte loops)
|
||||
{
|
||||
var header = new HeaderWindow { Command = DisplayCommand.FadeOut };
|
||||
|
||||
var payloadBuffer = _arrayPool.Rent(1);
|
||||
var payload = payloadBuffer.AsMemory(0, 1);
|
||||
payload.Span[0] = loops;
|
||||
|
||||
await SendAsync(header, payload);
|
||||
_arrayPool.Return(payloadBuffer);
|
||||
}
|
||||
|
||||
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
DisplayCommands/Internals/DisplaySubCommand.cs
Normal file
10
DisplayCommands/Internals/DisplaySubCommand.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace DisplayCommands.Internals;
|
||||
|
||||
internal enum DisplaySubCommand
|
||||
{
|
||||
BitmapNormal = 0x0,
|
||||
BitmapCompressZ = 0x677a,
|
||||
BitmapCompressBz = 0x627a,
|
||||
BitmapCompressLz = 0x6c7a,
|
||||
BitmapCompressZs = 0x7a73,
|
||||
}
|
17
DisplayCommands/Internals/HeaderWindow.cs
Normal file
17
DisplayCommands/Internals/HeaderWindow.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace DisplayCommands.Internals;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 16, Size = 10)]
|
||||
internal struct HeaderWindow
|
||||
{
|
||||
public DisplayCommand Command;
|
||||
|
||||
public ushort PosX;
|
||||
|
||||
public ushort PosY;
|
||||
|
||||
public ushort Width;
|
||||
|
||||
public ushort Height;
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TanksServer", "TanksServer\TanksServer.csproj", "{D88BF376-47A4-4C72-ADD1-983F9285C351}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DisplayCommands", "DisplayCommands\DisplayCommands.csproj", "{B4B43561-7A2C-486B-99F7-E58A67BC370A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -12,5 +14,9 @@ Global
|
|||
{D88BF376-47A4-4C72-ADD1-983F9285C351}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D88BF376-47A4-4C72-ADD1-983F9285C351}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D88BF376-47A4-4C72-ADD1-983F9285C351}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B4B43561-7A2C-486B-99F7-E58A67BC370A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B4B43561-7A2C-486B-99F7-E58A67BC370A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B4B43561-7A2C-486B-99F7-E58A67BC370A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B4B43561-7A2C-486B-99F7-E58A67BC370A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
using System.IO;
|
||||
using System.Xml;
|
||||
using DisplayCommands;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -115,6 +115,7 @@ public static class Program
|
|||
builder.Configuration.GetSection("Tanks"));
|
||||
builder.Services.Configure<PlayersConfiguration>(
|
||||
builder.Configuration.GetSection("Players"));
|
||||
builder.Services.AddDisplay(builder.Configuration.GetSection("ServicePointDisplay"));
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
|
|
@ -42,4 +42,8 @@
|
|||
<Content Include="../Makefile" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DisplayCommands\DisplayCommands.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
Loading…
Reference in a new issue