basically function but needs a lot of refinement

This commit is contained in:
2024-04-23 03:24:23 -05:00
parent 3ac7bf33c4
commit d3beca8014
26 changed files with 985 additions and 345 deletions

View File

@@ -1,18 +1,20 @@
using Godot;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
public partial class CommandHandler : Node
{
private Settings Settings { get; set; }
private game Game { get; set; }
private readonly List<Task> TaskQueue = new();
private readonly ConcurrentQueue<Action> ActionQueue = new();
private System.Net.Http.HttpClient _Client = null;
private System.Net.Http.HttpClient Client
{ get
@@ -21,34 +23,42 @@ public partial class CommandHandler : Node
return _Client;
}
}
private defer_manager _Deferer = null;
private defer_manager Deferer
{ get
{
_Deferer ??= GetNode<defer_manager>("/root/DeferManager");
return _Deferer;
}
}
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
Settings = GetNode<Settings>("/root/Settings");
Game = GetNode<game>("/root/Game");
GetNode<TwitchChatWatcher>("/root/TwitchChatWatcher").IncomingCommand
+= IncomingCommand;
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
//delete/cleanup one finished task per frame
//currently just logs errors
var t = TaskQueue.FirstOrDefault(t => t.IsCompleted, null);
if (t is not null)
while (ActionQueue.TryDequeue(out Action t))
{
if (t.Exception is not null)
GD.PrintErr(t.Exception);
TaskQueue.Remove(t);
t?.Invoke();
}
}
private void IncomingCommand(Command command)
{
GD.Print("Received command");
if (!Settings.IsUserAuthorized(command.User, command.IsStreamer,
command.IsModerator))
return;
GD.Print($"User {command.User} is authorized");
var baseArgs = command.GetArgs();
var type = CommandTypeHelper.ParseCommand(baseArgs.Pop());
GD.Print($"Command type: {type}");
var args = baseArgs.DuplicateAtState();
switch (type)
{
@@ -65,7 +75,9 @@ public partial class CommandHandler : Node
DeleteRow(args);
break;
case CommandType.CreateCard:
TaskQueue.Add(Task.Run(() => CreateCard(args)));
Task.Run(async () => await CreateCard(args))
.ContinueWith(t => GD.PrintErr(t.Exception.StackTrace),
TaskContinuationOptions.OnlyOnFaulted);
break;
case CommandType.CreateRow:
CreateRow(args);
@@ -80,10 +92,12 @@ public partial class CommandHandler : Node
RecolorRow(args);
break;
case CommandType.ChangeCardImage:
TaskQueue.Add(Task.Run(() => ChangeCardImage(args)));
Task.Run(async () => await ChangeCardImage(args))
.ContinueWith(t => GD.PushError(t.Exception.InnerException.StackTrace),
TaskContinuationOptions.OnlyOnFaulted);
break;
default:
throw new Exception();
throw new Exception("invalid command type");
}
}
@@ -120,10 +134,14 @@ public partial class CommandHandler : Node
}
private async Task CreateCard(CommandArguments args)
{
var title = args.Pop();
var url = args.Pop();
Image img = await ImageFromUrl(url);
Game.CreateCard(title, img);
var title = args.Remaining();
ImageWithMetadata img;
if (!string.IsNullOrWhiteSpace(url) && url != "_")
img = await ImageFromUrl(url);
else
img = null;
await Deferer.DeferAsync(() => Game.CreateCard(title, img));
}
private void CreateRow(CommandArguments args)
{
@@ -152,7 +170,8 @@ public partial class CommandHandler : Node
var rowId = args.Pop();
var colorStr = args.Pop();
var color = Color.FromString(colorStr, new Color(0, 0, 0, 0));
if (color == new Color(0, 0, 0, 0))
GD.Print($"Recoloring row to {color}");
if (color.IsEqualApprox(new Color(0, 0, 0, 0)))
throw new Exception($"invalid color {colorStr}");
Game.RecolorRow(rowId, color);
}
@@ -160,18 +179,38 @@ public partial class CommandHandler : Node
{
var cardId = args.Pop();
var img = await ImageFromUrl(args.Pop());
Game.ChangeCardImage(cardId, img);
await Deferer.DeferAsync(() => Game.ChangeCardImage(cardId, img));
// Game.ChangeCardImage(cardId, img);
}
private async Task<Image> ImageFromUrl(string url)
private async Task<ImageWithMetadata> ImageFromUrl(string url)
{
StretchMode mode = StretchMode.Unspecified;
if (url.Contains('|'))
{
var spl = url.Split('|', 2);
url = spl[0];
mode = spl[1].ToLower() switch
{
"unspecified" => StretchMode.Unspecified,
"fit" => StretchMode.Fit,
"stretch" => StretchMode.Stretch,
"crop" => StretchMode.Crop,
_ => throw new Exception($"Unrecognized {nameof(StretchMode)}"),
};
}
GD.Print($"Stretch mode: {mode}");
var uri = new Uri(url);
GD.Print("Starting image download");
var resp = await Client.GetAsync(uri);
if (!resp.IsSuccessStatusCode)
return null;
throw new Exception("Failed to download image");
GD.Print("Downloaded image successfully");
Image img = new();
var arr = await resp.Content.ReadAsByteArrayAsync();
var ext = Path.GetExtension(uri.AbsolutePath).TrimStart('.').ToLower();
GD.Print($"Image extension: {ext}");
//TODO detect images by header rather than extension
switch (Path.GetExtension(uri.AbsolutePath).ToLower())
switch (ext)
{
case "png":
img.LoadPngFromBuffer(arr);
@@ -189,7 +228,8 @@ public partial class CommandHandler : Node
default:
throw new Exception("unrecognized filetype");
}
return img;
GD.Print($"Loaded picture {img}");
return new ImageWithMetadata(img, mode);
}
protected override void Dispose(bool disposing)
{