Files
MRCC/launcher/PCL-CE/PCL.Core/App/Essentials/RpcService.cs
T
xyou f70b061d1a
CI / Go Backend (push) Canceled after 0s
feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
2026-08-08 14:07:56 +08:00

277 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using PCL.Core.App.IoC;
using PCL.Core.IO;
namespace PCL.Core.App.Essentials;
/// <summary>
/// 用于终止 Pipe RPC 执行过程并返回错误信息的异常<br/>
/// 当抛出该异常时 RPC 服务端将会返回内容为 <c>Reason</c> 的 <c>ERR</c> 响应
/// </summary>
public class RpcException(string reason) : Exception
{
public string Reason => reason;
}
/// <summary>
/// 标记一个方法或属性,使其成为 RPC 函数/属性。<br/>
/// 方法签名需兼容 <see cref="RpcFunction"/> 委托,属性仅支持 <see langword="string"/> 类型。
/// </summary>
/// <param name="name">该 RPC 函数/属性的名称</param>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
[DependencyCollector<RpcFunction>("rpc-function", AttributeTargets.Method)]
[DependencyCollector<string>("rpc-property", AttributeTargets.Property)]
public sealed class RegisterRpc(string name) : Attribute;
/// <summary>
/// RPC 服务项
/// </summary>
[LifecycleService(LifecycleState.Loaded)]
[LifecycleScope("rpc", "远程执行服务")]
public sealed partial class RpcService
{
private NamedPipeServerStream? _pipe;
[LifecycleStart]
private void _Start()
{
_pipe = PipeComm.StartPipeServer("Echo", _EchoPipeName, _EchoPipeCallback);
}
[LifecycleStop]
private async Task _StopAsync()
{
if (_pipe is not null) await _pipe.DisposeAsync();
}
[LifecycleDependencyInjection("rpc-function", AttributeTargets.Method)]
private static void _CollectRpcFunctionRegistry(ImmutableList<(RpcFunction func, string name)> items)
{
foreach (var (func, name) in items)
{
AddFunction(name, func);
}
}
[LifecycleDependencyInjection("rpc-property", AttributeTargets.Property)]
private static void _CollectRpcPropertyRegistry(ImmutableList<(PropertyAccessor<string> prop, string name)> items)
{
foreach (var (prop, name) in items)
{
AddProperty(new RpcProperty(
name,
() => prop.Value,
value => prop.Value = value ?? "",
prop.CanSet
));
}
}
public const string PipePrefix = "PCLCE_RPC";
private static readonly string _EchoPipeName = $"{PipePrefix}@{Basics.CurrentProcess.Id}";
private static readonly string[] _RequestTypeArray = ["GET", "SET", "REQ"];
private static readonly HashSet<string> _RequestType = [.._RequestTypeArray];
#region Property
private static readonly Dictionary<string, RpcProperty> _PropertyMap = new();
/// <summary>
/// 添加一个新的 RPC 属性,若有多个使用 foreach 即可
/// </summary>
/// <param name="prop">要添加的属性</param>
/// <returns>是否成功添加(若已存在相同名称的属性则无法添加)</returns>
public static bool AddProperty(RpcProperty prop) => _PropertyMap.TryAdd(prop.Name, prop);
/// <summary>
/// 通过指定的名称删除已存在的 RPC 属性
/// </summary>
/// <param name="name">属性名称</param>
/// <returns>是否成功删除(若不存在该名称则无法删除)</returns>
public static bool RemoveProperty(string name)
{
return _PropertyMap.Remove(name);
}
/// <summary>
/// 删除已存在的 RPC 属性,实质上仍然是通过属性的名称删除,但会检查是否是同一个对象
/// </summary>
/// <param name="prop">要删除的属性</param>
/// <returns></returns>
public static bool RemoveProperty(RpcProperty prop)
{
var key = prop.Name;
var result = _PropertyMap.TryGetValue(key, out var value);
if (!result || value != prop) return false;
_PropertyMap.Remove(key);
return true;
}
#endregion
#region Function
private static readonly Dictionary<string, RpcFunction> _FunctionMap = new() {
["ping"] = ((_, _, _) => RpcResponse.EmptySuccess),
["activate"] = ((_, _, _) =>
{
if (Lifecycle.CurrentState >= LifecycleState.WindowCreated) ActivateMainWindow();
else Lifecycle.When(LifecycleState.WindowCreated, ActivateMainWindow);
return RpcResponse.EmptySuccess;
void ActivateMainWindow()
{
var app = Lifecycle.CurrentApplication;
app.Dispatcher.BeginInvoke(() =>
{
var window = app.MainWindow!;
if (window.WindowState == WindowState.Minimized) window.WindowState = WindowState.Normal;
if (!window.Topmost)
{
window.Topmost = true;
window.Topmost = false;
}
window.Activate();
});
}
})
};
/// <summary>
/// 添加一个新的 RPC 函数,若有多个使用 foreach 即可
/// </summary>
/// <param name="name">函数名称</param>
/// <param name="func">函数过程</param>
/// <returns>是否成功添加(若已存在相同名称的函数则无法添加)</returns>
public static bool AddFunction(string name, RpcFunction func) => _FunctionMap.TryAdd(name, func);
/// <summary>
/// 通过指定的名称删除已存在的 RPC 函数
/// </summary>
/// <param name="name">函数名称</param>
/// <returns>是否成功删除(若不存在该名称则无法删除)</returns>
public static bool RemoveFunction(string name)
{
return _FunctionMap.Remove(name);
}
#endregion
private static bool _EchoPipeCallback(StreamReader reader, StreamWriter writer, Process? client)
{
try
{
// GET/SET/REQ [target]
// [content]
var header = reader.ReadLine(); // 读入请求头
Context.Info($"客户端请求: {header}");
var args = header?.Split([' '], 2) ?? []; // 分离请求类型和参数
if (args.Length < 2 || args[1].Length == 0) throw new RpcException("请求参数过少");
var type = args[0].ToUpperInvariant();
if (!_RequestType.Contains(type)) throw new RpcException($"请求类型必须为 {string.Join("/", _RequestTypeArray)} 其中之一");
var target = args[1];
// 读入请求内容(可能没有)
var buffer = new StringBuilder();
var tmp = reader.Read();
while (tmp != PipeComm.PipeEndingChar)
{
buffer.Append((char)tmp);
tmp = reader.Read();
}
var content = buffer.Length == 0 ? null : buffer.ToString();
switch (type)
{
case "GET": case "SET": {
target = target.ToLowerInvariant();
var result = _PropertyMap.TryGetValue(target, out var prop);
if (!result) throw new RpcException($"不存在属性 {target}");
RpcResponse response;
if (type == "GET")
{
try
{
var value = prop!.Value;
response = new RpcResponse(RpcResponseStatus.Success, RpcResponseType.Text, value, target);
Context.Trace($"返回值: {value}");
}
catch (RpcPropertyOperationFailedException)
{
response = RpcResponse.EmptyFailure;
Context.Debug("设置失败: 只写属性或请求被拒绝");
}
}
else if (prop!.Settable)
{
try
{
prop.Value = content;
response = RpcResponse.EmptySuccess;
Context.Trace($"设置成功: {content}");
}
catch (RpcPropertyOperationFailedException)
{
response = RpcResponse.EmptyFailure;
Context.Debug("设置失败: 请求被拒绝");
}
}
else
{
response = RpcResponse.EmptyFailure;
Context.Debug("设置失败: 只读属性");
}
response.Response(writer);
break;
}
case "REQ": {
var targetArgs = target.Split([' '], 2); // 分离函数名和参数
var name = targetArgs[0].ToLowerInvariant();
var indent = false; // 检测缩进指示
if (name.EndsWith('$'))
{
indent = true;
name = name[..^1];
}
var result = _FunctionMap.TryGetValue(name, out var func);
if (!result) throw new RpcException($"不存在函数 {name}");
string? argument = null;
if (targetArgs.Length > 1)
argument = targetArgs[1];
Context.Trace($"正在调用函数 {name} {argument}");
var response = func!(argument, content, indent);
response.Response(writer);
Context.Trace($"函数已退出,返回状态 {response.Status}");
break;
}
}
}
catch (Exception ex)
{
if (ex is RpcException rpcEx)
{
var reason = rpcEx.Reason;
RpcResponse.Err(reason).Response(writer);
Context.Info($"出错: {reason}");
}
else
{
RpcResponse.Err(ex.ToString(), "stacktrace").Response(writer);
Context.Error("处理请求时发生异常", ex);
}
}
return true;
}
}