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
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TanksServer", "TanksServer\TanksServer.csproj", "{D88BF376-47A4-4C72-ADD1-983F9285C351}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TanksServer", "TanksServer\TanksServer.csproj", "{D88BF376-47A4-4C72-ADD1-983F9285C351}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DisplayCommands", "DisplayCommands\DisplayCommands.csproj", "{B4B43561-7A2C-486B-99F7-E58A67BC370A}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{D88BF376-47A4-4C72-ADD1-983F9285C351}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Xml;
|
using DisplayCommands;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
@ -115,6 +115,7 @@ public static class Program
|
||||||
builder.Configuration.GetSection("Tanks"));
|
builder.Configuration.GetSection("Tanks"));
|
||||||
builder.Services.Configure<PlayersConfiguration>(
|
builder.Services.Configure<PlayersConfiguration>(
|
||||||
builder.Configuration.GetSection("Players"));
|
builder.Configuration.GetSection("Players"));
|
||||||
|
builder.Services.AddDisplay(builder.Configuration.GetSection("ServicePointDisplay"));
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|
|
@ -42,4 +42,8 @@
|
||||||
<Content Include="../Makefile" />
|
<Content Include="../Makefile" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DisplayCommands\DisplayCommands.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
Loading…
Reference in a new issue