Using static NamedPipeClient? PipeClient { get; set; } in Vector3DebuggerVisualizerProvider works.

new VisualizerTargetType("Frame Array Visualizer", "Num.Roto.Visualization.Math.Geometry.Frame[]
This commit is contained in:
Matthias Heil
2026-04-01 15:47:54 +02:00
parent cce0740f72
commit 406a0f4067
12 changed files with 111 additions and 41 deletions

View File

@@ -9,7 +9,7 @@ namespace NamedPipes;
public class NamedPipeClient (string pipeName,string? serverLocation = null, string? serverName = null,Action<string>? logger = null): IDebugVisualizer, IDisposable
{
private bool IsConnected => PipeClient.State == PipeState.Connected;
private PipeClient<IDebugVisualizer> PipeClient { get; }= new(new PipeSerializer(), pipeName);
private PipeClient<IDebugVisualizer> PipeClient { get; } = new(new PipeSerializer(), pipeName);
private string ServerLocation { get; } = serverLocation ?? Environment.GetEnvironmentVariable("USERPROFILE") + @"\Documents\Visual Studio 2026\Visualizers\Server\NrxVisualizer\";
private string ServerName { get; } = serverName ?? "Num.Roto.Nrx.VisualizerServer";
private Action<string> Logger { get;} = logger ??((m) => Trace.WriteLine(m));
@@ -19,29 +19,40 @@ public class NamedPipeClient (string pipeName,string? serverLocation = null, st
if (IsConnected) return;
PipeClient.SetLogger((m) => Trace.WriteLine(m));
var serverAvailable = Process.GetProcessesByName(ServerName).Length > 0;
Logger($"Server available: {serverAvailable}");
if (!serverAvailable)
{
var serverPath = ServerLocation + ServerName + ".exe";
if (!File.Exists(serverPath)) throw new FileNotFoundException(serverPath);
if (!File.Exists(serverPath)) throw new FileNotFoundException($"{nameof(StartServerAsync)}, Server does not exist, Path = {serverPath}");
var server = new Process { StartInfo = { FileName = serverPath } };
server.Start();
}
await PipeClient.ConnectAsync();
}
try
{
await PipeClient.ConnectAsync().ConfigureAwait(true);
Logger("NamedPipeClient.StartServerAsync succeded.");
return;
}
catch(Exception e)
{
Logger(e.ToString());
throw;
}
}
public async Task<bool> SetDebugObjectAsync(DebugObject debugObject)
{
await StartServerAsync();
var result = PipeClient.InvokeAsync(server => server.SetDebugObjectAsync(debugObject)).Result;
Logger("NamedPipeClient.SetDebugObject: result = " + result);
await StartServerAsync().ConfigureAwait(true);
var result = await PipeClient.InvokeAsync(server => server.SetDebugObjectAsync(debugObject));
Logger("NamedPipeClient.SetDebugObjectAsync: result = " + result);
return result;
}
public async Task<bool> SetMessageAsync(string message)
{
await StartServerAsync();
var result = PipeClient.InvokeAsync(server => server.SetMessageAsync(message)).Result;
Logger("NamedPipeClient.SetMessage: result = " + result);
await StartServerAsync().ConfigureAwait(true);
var result = await PipeClient.InvokeAsync(server => server.SetMessageAsync(message));
Logger("NamedPipeClient.SetMessageAsync: result = " + result);
return result;
}

View File

@@ -7,6 +7,11 @@ using PipeMethodCalls;
namespace NamedPipes;
public partial class NamedPipesServer(string pipeName,Action<string>? logger = null) : ObservableObject,IDebugVisualizer, IDisposable
{
~NamedPipesServer()
{
Logger(@"~NamedPipesServer called!");
}
public bool IsConnected => PipeServer is { State: PipeState.Connected };
private PipeServer<IDebugVisualizer>? PipeServer {get; set;}
private string PipeName { get; } = pipeName;
@@ -15,15 +20,28 @@ public partial class NamedPipesServer(string pipeName,Action<string>? logger = n
public async Task WaitForConnectionAsync()
{
if(IsConnected) return;
if (PipeServer == null || PipeServer.State == PipeState.Closed)
if (PipeServer == null || PipeServer.State != PipeState.Connected)
{
PipeServer?.Dispose();
PipeServer = new PipeServer<IDebugVisualizer>(new PipeSerializer(), PipeName, () => this);
PipeServer.SetLogger((m) => Trace.WriteLine(m));
}
await PipeServer.WaitForConnectionAsync();
}
Logger("Waiting for client connection...");
await PipeServer.WaitForConnectionAsync().ConfigureAwait(true);
}
public async Task RunAsync()
{
while(true)
{
await WaitForConnectionAsync().ConfigureAwait(true);
Logger("Client connected.");
while (IsConnected)
{
await Task.Delay(100).ConfigureAwait(true);
}
Logger("Client disconnected.");
}
}
[ObservableProperty]
public partial DebugObject? DebugObject { get; private set; }

View File

@@ -4,8 +4,8 @@ var count = 0;
var message = "Hello World!".ToCharArray();
while (true)
{
var pipeClient = new NamedPipeClient("testPipe",@".\","TestServer");
Thread.Sleep(100);
var pipeClient = new NamedPipeClient("testPipe",@".\","TestServer",logger:Console.WriteLine);
Thread.Sleep(1000);
pipeClient.SetMessageAsync($"Hello from TestClient " + count++).GetAwaiter().GetResult();
pipeClient.SetDebugObjectAsync(new DebugObject{Type = typeof(string).FullName,Data = Encoding.GetEncoding("UTF-8").GetBytes(message)}).GetAwaiter().GetResult();
pipeClient.Dispose();

View File

@@ -1,7 +1,5 @@
using System;
using System.Threading;
var pipeServer = new NamedPipes.NamedPipesServer("testPipe",logger:Console.WriteLine);
await pipeServer.RunAsync().ConfigureAwait(true);
var pipeServer = new NamedPipes.NamedPipesServer("testPipe",Console.WriteLine);
pipeServer.WaitForConnectionAsync().GetAwaiter().GetResult();
while (pipeServer.IsConnected) Thread.Sleep(100);

View File

@@ -1,11 +1,16 @@
using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Extensibility.DebuggerVisualizers;
using Microsoft.VisualStudio.RpcContracts.RemoteUI;
using NamedPipes;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Vector3VisualizerSource;
namespace Vector3Visualizer;
[VisualStudioContribution]
@@ -16,18 +21,30 @@ internal sealed class Vector3DebuggerVisualizerProvider : DebuggerVisualizerProv
[
new VisualizerTargetType("Vector3 Visualizer", "System.Numerics.Vector3, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"),
new VisualizerTargetType("Vector3[] Visualizer", typeof(Vector3[])),
new VisualizerTargetType("List<Vector3> Visualizer", typeof(List<>)),
new VisualizerTargetType("Quaternion Visualizer", typeof(Quaternion))
new VisualizerTargetType("Quaternion Visualizer", typeof(Quaternion)),
new VisualizerTargetType("Frame Visualizer", "Num.Roto.Visualization.Math.Geometry.Frame, Num.Roto.Visualization.Math, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"),
new VisualizerTargetType("Frame Array Visualizer", "Num.Roto.Visualization.Math.Geometry.Frame[], Num.Roto.Visualization.Math, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"),
//not working
new VisualizerTargetType("Frame List Visualizer", "System.Collections.Generic.List<Num.Roto.Visualization.Math.Geometry.Frame>, Num.Roto.Visualization.Math, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"),
])
{
VisualizerObjectSourceType = new VisualizerObjectSourceType(typeof(Vector3ObjectSource)),
Style = VisualizerStyle.ToolWindow,
};
private static NamedPipeClient? PipeClient { get; set; }
private static void SendToVisualizer(Vector3Model? vector3Model)
{
if (vector3Model is null) return;
PipeClient ??= new NamedPipeClient("testPipe", serverName: @"TestServer", logger: (m) => Console.WriteLine(m));
_ = PipeClient.SetMessageAsync($"Hello from {nameof(CreateVisualizerAsync)},vector3Model = {vector3Model.Vector3},{vector3Model.Quaternion}");
}
public override async Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
{
Vector3Model? model = await visualizerTarget.ObjectSource.RequestDataAsync<Vector3Model?>(jsonSerializer: null, CancellationToken.None);
return await Task.FromResult<IRemoteUserControl>(new Vector3VisualizerUserControl(model));
Vector3Model? model = await visualizerTarget.ObjectSource.RequestDataAsync<Vector3Model?>(jsonSerializer: null, cancellationToken);
SendToVisualizer(model);
var control = new Vector3VisualizerUserControl(dataContext: model);
return await Task.FromResult<IRemoteUserControl>(control);
//return await Task.FromResult<IRemoteUserControl>(new MyRemoteUserControl(model));
}
}

View File

@@ -14,8 +14,12 @@
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Build" Version="17.14.40608" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<Content Include="$(MSBuildThisFileDirectory)..\bin\$(Configuration)\netstandard2.0\Vector3VisualizerSource.dll" Link="netstandard2.0\Vector3VisualizerSource.dll" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(MSBuildThisFileDirectory)..\bin\$(Configuration)\netstandard2.0\NamedPipes.dll" Link="netstandard2.0\NamedPipes.dll" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(MSBuildThisFileDirectory)..\bin\$(Configuration)\net8.0-windows8.0\Vector3VisualizerSource.dll" Link="netstandard2.0\Vector3VisualizerSource.dll" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(MSBuildThisFileDirectory)..\bin\$(Configuration)\net8.0-windows8.0\NamedPipes.dll" Link="netstandard2.0\NamedPipes.dll" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(MSBuildThisFileDirectory)..\bin\$(Configuration)\net8.0-windows8.0\PipeMethodCalls.dll" Link="netstandard2.0\PipeMethodCalls.dll" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(MSBuildThisFileDirectory)..\bin\$(Configuration)\net8.0-windows8.0\MessagePack.dll" Link="netstandard2.0\MessagePack.dll" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(MSBuildThisFileDirectory)..\bin\$(Configuration)\net8.0-windows8.0\MessagePack.Annotations.dll" Link="netstandard2.0\MessagePack.Annotations.dll" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(MSBuildThisFileDirectory)..\bin\$(Configuration)\net8.0-windows8.0\CommunityToolkit.Mvvm.dll" Link="netstandard2.0\CommunityToolkit.Mvvm.dll" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Vector3VisualizerUserControl.xaml" />

View File

@@ -1,12 +1,13 @@
using System.Runtime.Serialization;
using System.Numerics;
using System.Runtime.Serialization;
namespace Vector3VisualizerSource;
[DataContract]
public class Vector3Model
{
[DataMember]
public string Vector3 { get; set; } = "Null";
public Vector3? Vector3 { get; set; }
[DataMember]
public string Quaternion { get; set; } = "Null";
public Quaternion? Quaternion { get; set;}
}

View File

@@ -1,42 +1,35 @@
using Microsoft.VisualStudio.DebuggerVisualizers;
using NamedPipes;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Numerics;
namespace Vector3VisualizerSource;
public class Vector3ObjectSource : VisualizerObjectSource
{
private static NamedPipeClient? PipeClient { get; set; }
private static void Send(Vector3Model vector3Model)
{
PipeClient ??= new NamedPipeClient("testPipe");
PipeClient.SetMessageAsync($"Hello from {nameof(Vector3ObjectSource)}").GetAwaiter().GetResult();
}
public Vector3ObjectSource()
{
Debug.WriteLine("new Vector3ObjectSource");
}
public override void GetData(object target, Stream outgoingData)
{
Debug.WriteLine("GetData: objectType is: " + target.GetType().FullName);
Vector3Model vector3Model = new();
switch (target)
{
case Vector3 vector3:
vector3Model.Vector3 = vector3.ToString();
vector3Model.Vector3 = vector3;
break;
case IEnumerable<Vector3> vector3List:
vector3Model.Vector3 = vector3List.Last().ToString();
vector3Model.Vector3 = vector3List.Last();
break;
case Quaternion quaternion:
vector3Model.Quaternion = quaternion.ToString();
vector3Model.Quaternion = quaternion;
break;
}
Send(vector3Model);
SerializeAsJson(outgoingData, vector3Model);
}
}

View File

@@ -0,0 +1,12 @@
using System.Numerics;
var a = new Vector3(1,2,3);
var typeName = a.GetType().AssemblyQualifiedName;
var b = new Vector3(5, 6, 7);
Vector3[] v3Array = [a, b];
List<Vector3> v3List = v3Array.ToList();
var quaternion = new Quaternion(1,2,3,4);
return;

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,3 @@
<Solution>
<Project Path="Vector3VisualizerTest.csproj" />
</Solution>

View File

@@ -1,8 +1,11 @@
<Solution>
<Folder Name="/NamedPipes/">
<Project Path="NamedPipes/NamedPipes.csproj" Id="8d7b1151-8b57-4411-a361-47ed9c504a22" />
</Folder>
<Folder Name="/Test/">
<Project Path="TestClient/TestClient.csproj" />
<Project Path="TestServer/TestServer.csproj" Id="c81154ef-d854-4f1e-9d14-2cf674885291" />
<Project Path="Vector3VisualizerTest/Vector3VisualizerTest.csproj" />
</Folder>
<Project Path="Vector3Visualizer/Vector3Visualizer.csproj" />
<Project Path="Vector3VisualizerSource/Vector3VisualizerSource.csproj" />