diff --git a/DebugVisualizerExtension.slnx b/DebugVisualizerExtension.slnx index 3d880db..a4cd754 100644 --- a/DebugVisualizerExtension.slnx +++ b/DebugVisualizerExtension.slnx @@ -7,9 +7,15 @@ + + + + + + diff --git a/NamedPipes/NamedPipeServer.cs b/NamedPipes/NamedPipeServer.cs index d4621b0..07733f3 100644 --- a/NamedPipes/NamedPipeServer.cs +++ b/NamedPipes/NamedPipeServer.cs @@ -3,6 +3,7 @@ using PipeMethodCalls; using System; using System.Diagnostics; +using System.Threading; using System.Threading.Tasks; namespace NamedPipes; @@ -30,15 +31,15 @@ public partial class NamedPipesServer(string pipeName,Action? logger = n Logger("Waiting for client connection..."); await PipeServer.WaitForConnectionAsync().ConfigureAwait(true); } - public async Task RunAsync() + public async Task RunAsync(CancellationToken cancellationToken) { - while(true) + while(!cancellationToken.IsCancellationRequested) { await WaitForConnectionAsync().ConfigureAwait(true); Logger("Client connected."); while (IsConnected) { - await Task.Delay(100).ConfigureAwait(true); + await Task.Delay(100, cancellationToken).ConfigureAwait(true); } Logger("Client disconnected."); } diff --git a/NrxDebugVisualizer/App.axaml b/NrxDebugVisualizer/App.axaml new file mode 100644 index 0000000..cd9bb79 --- /dev/null +++ b/NrxDebugVisualizer/App.axaml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/NrxDebugVisualizer/App.axaml.cs b/NrxDebugVisualizer/App.axaml.cs new file mode 100644 index 0000000..52a5be2 --- /dev/null +++ b/NrxDebugVisualizer/App.axaml.cs @@ -0,0 +1,47 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Data.Core; +using Avalonia.Data.Core.Plugins; +using Avalonia.Markup.Xaml; +using NrxDebugVisualizer.ViewModels; +using NrxDebugVisualizer.Views; +using System.Linq; + +namespace NrxDebugVisualizer; + +public partial class App : Application +{ + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + // Avoid duplicate validations from both Avalonia and the CommunityToolkit. + // More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins + DisableAvaloniaDataAnnotationValidation(); + desktop.MainWindow = new MainWindow + { + DataContext = new MainWindowViewModel(), + }; + } + + base.OnFrameworkInitializationCompleted(); + } + + private static void DisableAvaloniaDataAnnotationValidation() + { + // Get an array of plugins to remove + var dataValidationPluginsToRemove = + BindingPlugins.DataValidators.OfType().ToArray(); + + // remove each entry found + foreach (var plugin in dataValidationPluginsToRemove) + { + BindingPlugins.DataValidators.Remove(plugin); + } + } +} \ No newline at end of file diff --git a/NrxDebugVisualizer/Assets/avalonia-logo.ico b/NrxDebugVisualizer/Assets/avalonia-logo.ico new file mode 100644 index 0000000..f7da8bb Binary files /dev/null and b/NrxDebugVisualizer/Assets/avalonia-logo.ico differ diff --git a/NrxDebugVisualizer/Models/PipeServer.cs b/NrxDebugVisualizer/Models/PipeServer.cs new file mode 100644 index 0000000..710e5a0 --- /dev/null +++ b/NrxDebugVisualizer/Models/PipeServer.cs @@ -0,0 +1,79 @@ +using NamedPipes; +using NrxDebugVisualizer.Scenes; +using Num.Roto.Visualization.Math.Geometry; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Threading; +using KontractFrame = System.ValueTuple; +namespace NrxDebugVisualizer.Models; + +internal sealed class PipeServer +{ + private string PipeName { get; } + private NamedPipesServer? NamedPipesServer { get; set; } + public PipeServer(string pipeName, DebugVisualizerScene debuggerScene,CancellationToken cancellationToken) + { + PipeName = pipeName; + DebugVisualizerScene = debuggerScene; + StartPipeServer(cancellationToken); + } + private void NamedPipesServer_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName != nameof(NamedPipesServer.VisualizerModel)) return; + var visualizerObject = NamedPipesServer?.VisualizerModel; + if (visualizerObject is null) return; + + SetPoint(visualizerObject.Point); + SetPointList(visualizerObject.PointArray); + SetFrame(visualizerObject.Frame); + SetFrameList(visualizerObject.FrameArray); + } + + #region NamedPipesServer + private async void StartPipeServer(CancellationToken cancellationToken) + { + NamedPipesServer = new NamedPipesServer(PipeName, logger: Console.WriteLine); + NamedPipesServer.PropertyChanged += NamedPipesServer_PropertyChanged; + await NamedPipesServer.RunAsync(cancellationToken); + } + #endregion + + #region DebugVisualizerScene + private DebugVisualizerScene DebugVisualizerScene { get; } + private static Frame ToFrame(KontractFrame kontractFrame) + { + var (translation, orientation, scale) = kontractFrame; + return new Frame(translation, scale, orientation); + } + private static Frame[] ToFrameArray(IEnumerable frameList) + { + return [.. frameList.Select(ToFrame)]; + } + + private void SetPoint(Vector3? point) + { + if (point is null) return; + DebugVisualizerScene.AddPointGeometry(point.Value, 20f); + } + + private void SetPointList(Vector3[]? pointList) + { + if (pointList == null || pointList.Length < 1) return; + DebugVisualizerScene.AddPointListGeometry(pointList, 5); + } + + private void SetFrame(KontractFrame? kontractFrame) + { + if (kontractFrame is null) return; + DebugVisualizerScene.AddFrameGeometry(ToFrame(kontractFrame.Value), 1); + } + + private void SetFrameList(KontractFrame[]? frameArray) + { + if (frameArray == null || frameArray.Length < 1) return; + DebugVisualizerScene.AddFrameListGeometry(ToFrameArray(frameArray), 1); + } + #endregion +} diff --git a/NrxDebugVisualizer/NrxDebugVisualizer.csproj b/NrxDebugVisualizer/NrxDebugVisualizer.csproj new file mode 100644 index 0000000..60ffa21 --- /dev/null +++ b/NrxDebugVisualizer/NrxDebugVisualizer.csproj @@ -0,0 +1,31 @@ + + + WinExe + net10.0 + enable + app.manifest + true + + + + + + + + + + + + + + None + All + + + + + + + + + diff --git a/NrxDebugVisualizer/Program.cs b/NrxDebugVisualizer/Program.cs new file mode 100644 index 0000000..cf8e29d --- /dev/null +++ b/NrxDebugVisualizer/Program.cs @@ -0,0 +1,21 @@ +using Avalonia; +using System; + +namespace NrxDebugVisualizer; + +internal sealed class Program +{ + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace(); +} diff --git a/NrxDebugVisualizer/Scenes/DebugVisualizerScene.cs b/NrxDebugVisualizer/Scenes/DebugVisualizerScene.cs new file mode 100644 index 0000000..ba28087 --- /dev/null +++ b/NrxDebugVisualizer/Scenes/DebugVisualizerScene.cs @@ -0,0 +1,89 @@ +using Num.Roto.Visualization.Math.Geometry; +using Num.Roto.Visualization.SceneGraph.Geometry; +using Num.Roto.Visualization.SceneGraph.Material; +using Num.Roto.Visualization.SceneGraph.Scene; +using Num.Roto.Visualization.SceneGraph.Scene.Nodes; +using Num.Roto.Visualization.Types; +using System.Numerics; + +namespace NrxDebugVisualizer.Scenes; + +internal sealed class DebugVisualizerScene : Num.Roto.Visualization.SceneGraph.Scenes.SceneBase +{ + private static uint Counter; + private static uint Uid => Counter++; + + private SceneNode Points { get; } + private SceneNode Frames { get; } + + + internal DebugVisualizerScene() : base("NrxDebuggerVisualizerScene") + { + Points = new SceneNode(nameof(Points), Objects); + Frames = new SceneNode(nameof(Frames), Objects); + } + public void AddPointGeometry(Vector3 position, float pointSize) + { + var point = new Points([position], "Point"); + _ = new SceneGeometry("Point" + $".{Uid}", point, new Material(Colors4.White, null), Points) + { + PointSize = pointSize, + ComputeFragColor = false + }; + } + public void AddPointListGeometry(Vector3[] positions, float pointSize) + { + var points = new Points(positions, "Points"); + var material = new Material(Colors4.White, null) + { + EmissiveColor = Colors4.White, + VertexColorBlendFactor = 1 + }; + _ = new SceneGeometry("Points" + $".{Uid}", points, material, Points) + { + PointSize = pointSize, + ComputeFragColor = false + }; + } + public void AddFrameGeometry(Frame frame, float length) + { + AddFrameListGeometry([frame], length); + } + public void AddFrameListGeometry(Frame[] frames, float length) + { + var material = new Material(Colors4.White, null) + { + EmissiveColor = Colors4.White, + VertexColorBlendFactor = 1 + }; + var name = frames.Length > 1 ? "FrameList" : "Frame"; + var firstFrame = frames[0]; + var frameContainer = new SceneNode(name + $".{Uid}", Frames) + { + Frame = firstFrame + }; + var inverse = firstFrame.Inverse(); + for (var i = 0; i < frames.Length; ++i) + { + frames[i] = inverse * frames[i]; + } + var frameGeometry = new SceneGeometry("Geometry", Crosses.DefaultCross(0.5f), material, frameContainer) + { + InstanceFrames = [.. frames], + Flags = 0, + LineWidth = 1 + }; + if (frames.Length > 1) + { + var size = 2 * (frames.Length - 1); + var path = new Vector3[size]; + path[0] = frames[0].Translation; + for (var i = 1; i < frames.Length - 1; ++i) + { + path[2 * i] = path[2 * i - 1] = frames[i].Translation; + } + path[^1] = frames[^1].Translation; + _ = new SceneGeometry("Path", new Lines(path), material, frameGeometry); + } + } +} diff --git a/NrxDebugVisualizer/ViewModels/MainWindowViewModel.cs b/NrxDebugVisualizer/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000..7535766 --- /dev/null +++ b/NrxDebugVisualizer/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,8 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace NrxDebugVisualizer.ViewModels; + +public partial class MainWindowViewModel : ObservableObject +{ + public string Greeting { get; } = "Welcome to Avalonia!"; +} diff --git a/NrxDebugVisualizer/Views/MainWindow.axaml b/NrxDebugVisualizer/Views/MainWindow.axaml new file mode 100644 index 0000000..c93a8eb --- /dev/null +++ b/NrxDebugVisualizer/Views/MainWindow.axaml @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/NrxDebugVisualizer/Views/MainWindow.axaml.cs b/NrxDebugVisualizer/Views/MainWindow.axaml.cs new file mode 100644 index 0000000..2f0f3a3 --- /dev/null +++ b/NrxDebugVisualizer/Views/MainWindow.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace NrxDebugVisualizer.Views; + +public partial class MainWindow : Window +{ + public MainWindow() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/NrxDebugVisualizer/app.manifest b/NrxDebugVisualizer/app.manifest new file mode 100644 index 0000000..a2ac8c1 --- /dev/null +++ b/NrxDebugVisualizer/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/TestClient/Program.cs b/TestClient/Program.cs index c60f9dc..183d309 100644 --- a/TestClient/Program.cs +++ b/TestClient/Program.cs @@ -1,8 +1,8 @@ -using System.Text; + using NamedPipes; var count = 0; var message = "Hello World!".ToCharArray(); -VisualizerModel visualizerModel = new VisualizerModel +VisualizerModel visualizerModel = new() { Content = "Hello from TestClient", Point = new System.Numerics.Vector3(1, 2, 3), @@ -13,7 +13,7 @@ VisualizerModel visualizerModel = new VisualizerModel }; while (true) { - var pipeClient = new NamedPipeClient("testPipe",@".\","TestServer",logger:Console.WriteLine); + var pipeClient = new NamedPipeClient("TestPipe", @".\","TestServer",logger:Console.WriteLine); Thread.Sleep(1000); await pipeClient.SetVisualizerModelAsync(visualizerModel); pipeClient.SetMessageAsync($"Hello from TestClient " + count++).GetAwaiter().GetResult(); diff --git a/TestServer/Program.cs b/TestServer/Program.cs index bd9d7cb..c115b97 100644 --- a/TestServer/Program.cs +++ b/TestServer/Program.cs @@ -1,5 +1,6 @@ using System; -var pipeServer = new NamedPipes.NamedPipesServer("testPipe",logger:Console.WriteLine); -await pipeServer.RunAsync().ConfigureAwait(true); +using System.Threading; +var pipeServer = new NamedPipes.NamedPipesServer("TestPipe", logger:Console.WriteLine); +await pipeServer.RunAsync(CancellationToken.None).ConfigureAwait(true);