实验性 Web Renderer
实验性 Web Renderer
Web Renderer 是独立内置插件 top.plfjy.bpsys.WebRenderer。它会把当前活动 .bpui v3 包的 Window-centric 布局发送给 Kestrel sidecar,并在浏览器中还原固定设计画布。它不替代现有 WPF 前台窗口;WPF 与网页会并行运行,比赛状态仍只由 WPF 的 ISharedDataService 权威维护。
插件本体不引用 ASP.NET Core;它会先检测 x64 的 Microsoft.AspNetCore.App 10.*,然后启动同目录 Host 下 framework-dependent 的 net10.0 sidecar。缺少 runtime、进程启动错误、端口占用或 IPC 断开只会记录日志和显示非模态提示,不会中止主 WPF 应用。请安装 ASP.NET Core Runtime 10 (x64) 后点击"重新检测"。
当 runtime 缺失时,管理页顶部会显示引导区域,支持一键下载并安装官方 ASP.NET Core Runtime 10 x64 installer:
- 版本发现:
WebRendererRuntimeReleaseFeed在线查询 Microsoft 官方 release metadata(https://builds.dotnet.microsoft.com/dotnet/release-metadata/10.0/releases.json),解析最新 10.0.x 版本号、win-x64 installer 直链与 SHA-512。网络失败时回退到插件内置常量WebRendererRuntimeReleaseFeed.KnownFallbackVersion(当前10.0.10)与稳定 CDN URL patternhttps://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/{version}/aspnetcore-runtime-{version}-win-x64.exe。发布新版本后应同步更新该常量。 - 下载:
WebRendererRuntimeSetupService通过宿主IFileDownloadService下载 installer 到%TEMP%\neo-bpsys-wpf_WebRenderer\,支持暂停、继续和取消后续传,并实时回传进度。 - 校验:若 release metadata 提供 SHA-512,下载后用
SHA512.HashDataAsync校验;hash 缺失则跳过校验并记录 warning。 - 安装:以
/quiet /norestart参数、Verb=runas(UAC 提升)唤起 installer 静默安装;用户拒绝 UAC 或 installer 退出码非 0 时回退为"打开官方下载页"手动安装引导。 - 重启:安装完成并重新检测通过后,设置
IGlobalRestartService.IsRestartRequired = true,应用标题栏出现"需要重启"按钮,用户点击后App.Current.Restart()重启主程序;重启后 sidecar 重新检测 runtime 并正常启动。安装完成与重启之间不自动关闭应用,避免丢失用户未保存的后台状态。
安装流程状态机:Idle → FetchingRelease → Downloading(可进入 Paused 并恢复)→ Verifying → Installing → AwaitingRestart(或 Failed)。任何阶段的错误都会回写到管理页引导区域,不会中止主 WPF 应用。
启动参数、运行依赖安装与部署命令见插件构建运行指南。默认仅 localhost;LAN 模式无访问认证,同网设备可读取实时展示数据,须由部署者限制网络访问。
资源 URL 是每次 bootstrap 创建的随机 token;浏览器不会获得物理路径。插件只授权当前活动包、local、内置 Resources/... 和已知应用字体,拒绝绝对路径、跨包引用及编码路径穿越。切换包或 Designer 保存布局时会重新发送 bootstrap,页面通过 WebSocket 刷新。未知内置控件与没有 Web adapter 的 plugin:* 控件会显示诊断占位;纯 Binding 文本显示绑定路径占位。
每次 Web client 构建都会写入 client build id(提交标识与构建时间)。该值同时出现在最终 index.html 的 meta 标签、浏览器启动日志和 /health 的 clientBuildId。sidecar 启动前会验证最终 Host/wwwroot/index.html、build id 以及它引用的每一个本地 script/link 文件;任何缺失或越界引用都会让 sidecar 以明确错误退出,而不会在运行期间持续请求不存在的 hash 文件。
/、/render/* 和 /index.html 始终返回 Cache-Control: no-store,确保入口页不会缓存旧 bundle 引用。带内容 hash 的 /assets/*.js 与 /assets/*.css 使用 immutable 缓存策略。
IPC 使用 version 8,bootstrap schema 使用 version 1,runtime value schema 使用 version 2,并采用显式会话状态:Stopped、StartingProcess、WaitingForPipe、PipeConnected、BuildingBootstrap、WaitingForBootstrapAck、Ready、Stopping、Faulted。主程序是状态权威;sidecar 必须先发送 sidecar.ready,主程序才发送 host.hello 和真实活动包的 bootstrap.replace。sidecar 会原子校验协议版本、schema、generation、窗口结构与本地资源表,成功后返回 bootstrap.applied;只有该确认到达,管理页、/health 与 /api/windows 才会显示 Ready 和已发布窗口。连接断开时 sidecar 保持 HTTP 进程并以封顶退避重连,重连后的第一组消息始终是完整 bootstrap、localization 和 runtime snapshot。
Web Renderer 的本地化唯一由主程序 WebRendererLocalizationBridge 负责。主程序使用显式 CultureInfo 调用 I18nHelper、MapNameDisplayHelper、GameProgressDisplayHelper 和窗口显示名 Helper,生成 WebLocalizationSnapshot;快照只包含稳定控件 ID 与最终显示文本,不包含 resx、字典名或浏览器查找规则。LocalizedText、MapNameText、MapV2Display 和 GameProgressText 的 runtime 投影分别提供 DisplayText、地图/阵营最终文本或 FullText/GameText/HalfText。React 只负责排版,未收到匹配 Revision 的 localization 和 runtime 前保持业务文本为空。
语言变化来自 ISettingsHostService.LanguageSettingChanged:后台构建新快照,按 latest-wins 丢弃过期 revision,通过 localization.replace 主动推送;sidecar 保存并广播完整快照,随后主程序发布带 LocalizationRevision 的 runtime snapshot。语言切换不重启 sidecar、刷新浏览器或重新下载图片。日志链为 language changed → localization snapshot built → localization.replace sent → sidecar applied revision → browser applied revision。缺失资源仅由主程序记录一次 LocalizationMissing:{dictionary}:{key}:{culture},FallbackText 只在真实资源缺失时使用。
/api/windows 和 /api/bootstrap/{encodedFullWindowType} 在 IPC 未连接时返回 503 IpcUnavailable,bootstrap 尚未确认时返回 503 BootstrapPending,构建或校验失败时返回结构化 503 错误。/render/{...} 在等待状态显示“正在等待主程序布局数据”;仅 Ready 后才区分 UnknownWindow 与 LayoutUnavailable。管理页仅把 sidecar 已确认的窗口作为可用 Web Runtime,不会用 registry 候选窗口伪装布局已发布。
Image 与 BorderedImage 按 WPF 的语义树分离外层布局、内容 viewport、Lock/PickingBorder overlay 和行为生成部件。动态图片值明确区分 resolved、pending、null、failed;runtime asset 同时携带自然 DIP、像素尺寸与 DPI。浏览器只在新图片 decode 成功后原子切换,pending/failed 保留同 generation 的上一稳定图片,业务 null 则清空。
在线队伍图片
在线图片存在两条实际路径,不能混为一条:
- Team.Logo / Member.Image 当前经 Core RemoteImageLoader 在后台下载、OnLoad 解码并冻结,再在 Dispatcher 发布。getter 首次返回 null;完成后 WebRuntimeAssetRegistry 将无 UriSource 的冻结位图按 FrozenBitmap 异步编码。WPF 模型本身会下载图片,不再是旧文档的“主进程完全不读图片字节”。
- 对仍携带 HTTP/HTTPS UriSource 的 BitmapImage,registry 分类为 RemoteHttp,发布规范化 URI 描述符,经 remoteAsset.fetch IPC 由 sidecar 的 RemoteAssetFetcher 下载;下列缓存、重定向与地址校验属于此路径,不能宣称已自动覆盖第一条 Core 下载链。
源码:RemoteImageLoader、WebRuntimeValue 的分类与注册。这两条路径的安全/诊断策略差异需要在相关代码任务中单独评估,这里分别描述两条实际边界。
没有选择角色时,PictureShown 使用 Member.Image;选择角色后使用 Character.HalfImage;清除角色后重新使用当前 Member 的在线定妆照。两个队伍 Logo、四名求生者与一名监管者都走同一套绑定和资源链路,不含控件特判。
sidecar 独立异步下载在线图片,并只允许 HTTP/HTTPS 的 PNG、JPEG、WebP 和 GIF。下载使用操作系统默认代理和路由设置,因此浏览器、WPF 与 Clash TUN/Fake-IP 模式共用同一网络路径;sidecar 不强制直连,也不要求额外的受信任主机配置。每次下载最多跟随 5 次重定向,连接超时 5 秒、总请求超时 20 秒、单项上限 10 MiB;相同资源的并发请求合并。成功结果进入 64 MiB 内存 LRU 与 512 MiB、最长 7 天的磁盘缓存,完整写入后才通过 /remote-assets/{token} 提供。失败不会持久缓存,绑定仍存在时会退避重试;URL 或 generation 改变时旧任务结果会被丢弃。
每次请求和重定向都会重新执行基本地址校验:拒绝 userinfo、非 HTTP/HTTPS、localhost、环回、链路本地、组播、未指定和 RFC1918 私有地址。Clash TUN 常用的 198.18.0.0/15 仅作为 Fake-IP 路由地址,允许交由操作系统代理/路由处理,不将其当作用户可访问的局域网图片服务器。日志只记录稳定诊断码、generation 和截短 token,不记录源 URL、查询参数或完整缓存路径。
字体分类
普通字体名称(例如 Arial、Segoe UI、Microsoft YaHei、Times New Roman、sans-serif、serif、monospace)直接交给浏览器解析,不进入 bootstrap resources、不生成 @font-face、不发起字体资源请求,也不会映射为 Noto Sans。只有 pack://...#Family、bpui://...#Family 和明确指向 .ttf、.otf、.woff、.woff2 的 Resources/... 引用才作为嵌入或包字体处理。
Web 文本将 WPF FontWeight 名称集中转换为 CSS 数值:Thin 100;ExtraLight/UltraLight 200;Light 300;Normal/Regular 400;Medium 500;DemiBold/SemiBold 600;Bold 700;ExtraBold/UltraBold 800;Black/Heavy 900;ExtraBlack 950。未知或空值不直接写入 CSS,由浏览器继承。
.bpui v3 的 Rectangle、Border、Image AnimationParts 会进入所属控件的局部 Above/Below overlay。Web 动画使用带单位的长度值,百分比 ClipInset 直接生成 clip-path: inset(...),Transform 分量由同一元素状态合成。Transition 的 transition.committed 携带 required generation/sequence;浏览器应用到该 runtime sequence 后才启动 EnterGraph,超时、断线或 generation 变化时 fail-open,WPF 始终是唯一业务 commit 所有者。
这些能力仍属于实验性 Web Renderer。BackgroundTintRectangle、BackgroundTintPolygon、MapV2Display、GameProgressText 以及 Image/BorderedImage 的 PickingBorder 已由 Web Runtime 实现;图片编码、runtime patch、IPC 和 Web 动画保持异步,WPF Renderer 的既有语义不变。
BackgroundTint 使用独立 Web Worker 和 OffscreenCanvas 对当前 Canvas 背景执行与 WPF 同类的像素染色,主线程不进行逐像素处理。MapV2Display 使用持久化的 InternalParts、RuntimeAsset 图片和 part:{BehaviorGuid}:PickingBorder 行为目标。GameProgressText 使用现有枚举的模式、语言、数字和竖排规则;尚未实现的控件仍会在管理页和浏览器诊断中明确标记为 unsupported。
管理与排查
插件启用并重启应用后会在后台导航中出现 Web Renderer 实验页面。页面可以启动、停止和重启 sidecar,显示与 /health 一致的生命周期状态、本机/局域网 URL、连接客户端、已确认活动包、已确认窗口和最近错误,也可以复制或在默认浏览器打开本机 URL。Host、Port、随应用启动、Transition fail-open timeout 与协议摘要日志保存在插件自己的 PluginConfigs/top.plfjy.bpsys.WebRenderer/Settings.json,不会修改主程序 Config.json。保存配置会重启 sidecar。
命令行参数优先于插件设置:--web-host(或 --host)、--web-port(或 --port)、--web-no-start 与 --web-log-protocol。协议日志只记录连接和消息摘要,不记录动画逐帧数据。
OBS 可添加 Browser Source 并填入本机 URL 或指定窗口的 /render/{encodedFullWindowType} URL;浏览器字体栅格化与 WPF 可能略有不同。plugin:* 控件只有提供受控 Web adapter 才能渲染,否则显示诊断占位。
构建与部署入口
运行依赖安装、启动选项、完整产物检查与真实 IPC 验收统一见插件 README。Debug 的可选省略与正式构建的失败规则以构建发布规范为准;构建 Web 客户端需要 pnpm 与 Node.js,运行 sidecar 不需要 Node.js。
停止、重启与保存后重启互斥:先请求 sidecar 退出并关闭 IPC,超时才终止本会话所属进程树。优先 Windows Job Object,parent PID + 启动时间监视为兜底。成功、失败、取消或超时后均恢复管理命令。WebTransitionGateway 保存 CancellationTokenRegistration,完成/取消时非阻塞注销,覆盖预先取消与回调重入;超时仍由调用方 Cancel/fail-open,不能让已完成会话被长寿命令牌持有。
