4901 字
13 分钟
RTune Async Tick 深度调试报告

1. 文档信息#

  • 文档类型:架构审计 + 缺陷复盘 + 修复说明
  • 文档目标:为博客发布、团队同步、后续重构提供统一依据
  • 调试范围:
    • Source/ThirdParty/AsyncTickPhysics
    • Source/RTune/Private/Vehicle/RTuneVehicle.cpp
    • Source/RTune/Private/Vehicle/SuspensionComponent.cpp
  • 调试重点:Async Tick 链路、物理线程边界、复制模式下的车辆状态一致性、悬挂状态更新
  • 当前结论:
    • 该插件的核心能力是可用的,但原始实现存在明显的线程边界设计缺口
    • 最危险的问题不是单一公式错误,而是“物理线程直接执行 UObject / Blueprint / World 查询逻辑”
    • 本轮已完成一批高收益、低侵入的修复
    • 仍有两类高优先级架构风险需要在后续版本继续治理

2. 执行摘要#

这次排查的核心结论可以概括为一句话:

RTuneVehicle 的问题不是“某一个 Async Tick 函数写错了”,而是“整个 Async Tick 设计把过多的游戏线程语义直接带进了物理线程”。

具体表现为:

  1. 第三方 AsyncTickPhysics 原本会在物理线程中直接调用 BlueprintImplementableEvent AsyncTick
  2. RTuneVehicleSuspensionComponent 在物理线程内直接读写大量 UObject 状态。
  3. SuspensionComponent 在物理线程内直接调用 UWorld::SweepSingleByChannel / LineTraceSingleByChannel
  4. ERT_Full 复制模式下,非权威端此前会过早返回,导致远端车辆悬挂状态根本不刷新。
  5. 另有两个明确的局部逻辑 bug:
    • 刹车俯仰力矩条件判断错误
    • OnRep_Steering 的旋转分支与权威端不一致

本轮已经完成的修复包括:

  1. 补齐 AsyncTickPhysics 生命周期注销逻辑,避免回调悬挂。
  2. 将 Blueprint AsyncTick 从物理线程改为回派到游戏线程执行。
  3. 修复 ERT_Full 下远端车辆悬挂状态不更新的问题,同时补充同步 CurrentTorqueHandbrakeInput
  4. 修复刹车俯仰力矩门控错误。
  5. 修复悬挂转向复制分支错误。

本轮尚未彻底解决,但应被视为下一阶段重点工作的风险包括:

  1. 物理线程内通过 UWorld 做场景查询。
  2. 游戏线程和物理线程共享成员变量的直接读写,存在数据竞争风险。

3. 调试背景与验证边界#

3.1 为什么重点看 Async Tick#

车辆物理系统是高频、多状态、强耦合模块。只要引入异步物理回调,问题就不再局限于“公式对不对”,而会升级为:

  1. 回调在哪个线程运行。
  2. 这个线程能不能安全访问 UObject。
  3. 复制状态是在什么线程生成和消费。
  4. 游戏线程的动画、轮子、内部仪表、反重力系统是否读取了跨线程共享状态。

换句话说,Async Tick 的问题天然具有“隐性、偶发、难复现”的特征:

  1. 有的机器完全正常。
  2. 有的机器在高车流、高帧率或多人联机时才暴露。
  3. 有的问题不是立刻崩溃,而是轮子抖动、悬挂错位、远端车辆状态不对、偶发编辑器断言。

3.2 本轮验证方式#

本轮采用的是“源码审计 + 定点修复”的方式,尚未完成完整编译验证。

原因如下:

  1. 项目入口已确认存在:C:\Users\KawaiRina\Desktop\RTune_V2\VehiclePhysics.uproject
  2. uproject 指向 EngineAssociation: 5.7
  3. 当前本机标准安装路径下未直接找到 UE 5.7 编辑器/构建工具,因此本轮没有跑完整编译

这意味着:

  1. 文档中的问题分类、风险评估和修复思路是可靠的
  2. 已落地补丁需要在真实 UE 5.7 环境中再做一次编译与运行验证

4. 现有架构总览#

4.1 模块启动与管理器创建#

AsyncTickPhysics 在模块启动时向物理场景生命周期注册委托,并在物理场景初始化时创建 FAsyncTickManager

关键代码位置:

  • Source/ThirdParty/AsyncTickPhysics/Source/AsyncTickPhysics/Private/AsyncTickPhysics.cpp:12
  • Source/ThirdParty/AsyncTickPhysics/Source/AsyncTickPhysics/Private/AsyncTickPhysics.cpp:25
  • Source/ThirdParty/AsyncTickPhysics/Source/AsyncTickPhysics/Private/AsyncTickPhysics.cpp:31

链路如下:

StartupModule
-> OnPhysSceneInit
-> new FAsyncTickManager(Scene)
-> OnPhysSceneTerm
-> delete FAsyncTickManager(Scene)

4.2 AsyncTickManager 的职责#

FAsyncTickManager 负责:

  1. 记录当前 PhysScene 对应的管理器实例
  2. 维护参与 Async Tick 的 Pawn / ActorComponent 列表
  3. ScenePreTick 中把这些对象复制到 sim callback 输入缓冲
  4. 在 Chaos callback 中驱动这些对象执行 NativeAsyncTick

关键代码位置:

  • AsyncTickManager.cpp:13
  • AsyncTickManager.cpp:45
  • AsyncTickManager.cpp:55
  • AsyncTickManager.cpp:153

4.3 Async 回调执行链#

当前完整调用链如下:

FPhysicsDelegates::OnPhysSceneInit
-> FAsyncTickManager
-> Scene.OnPhysScenePreTick
-> ScenePreTick
-> 填充 FAsyncPhysicsInput
-> FAsyncPhysicsCallback::OnPreSimulate_Internal
-> Pawn->NativeAsyncTick(DeltaTime)
-> ActorComponent->NativeAsyncTick(DeltaTime)

关键代码位置:

  • AsyncTickManager.cpp:153
  • AsyncTickCallback.cpp:3
  • AsyncTickCallback.cpp:20
  • AsyncTickCallback.cpp:29

4.4 RTuneVehicle 在链路中的位置#

ARTuneVehicle 继承自 AAsyncTickPawn,因此会进入上述 Async 回调。

它在 Async Tick 中主要做四类事:

  1. 读取当前刚体姿态和速度
  2. 计算动力总成、空气力、手刹、反重力等物理状态
  3. 调用 SuspensionComponent::UpdatePhysicsWithState
  4. 同步服务端 / 客户端复制状态

关键代码位置:

  • RTuneVehicle.cpp:442
  • RTuneVehicle.cpp:592
  • RTuneVehicle.cpp:623
  • RTuneVehicle.cpp:1162

4.5 SuspensionComponent 的职责#

USuspensionComponent 同时承担了两个职能:

  1. 物理线程中的接地检测、悬挂压缩、轮胎侧向力、阻力计算
  2. 游戏线程中的轮胎模型可视化、转向几何、Debug 绘制

这意味着它天然处于 GT/PT 双线程交界处,是本次排查中最敏感的模块。

关键代码位置:

  • SuspensionComponent.cpp:311
  • SuspensionComponent.cpp:501

5. 问题分级总表#

编号问题级别状态
ATP-01Blueprint AsyncTick 在物理线程执行P0已修复
ATP-02Async callback 生命周期注销不闭合P0已修复
ATP-03ERT_Full 非权威端悬挂状态不刷新P1已修复
ATP-04刹车俯仰力矩门控变量错误P1已修复
ATP-05OnRep_Steering 旋转分支与权威端不一致P1已修复
ATP-06物理线程中直接使用 UWorld 场景查询P0未彻底修复
ATP-07GT/PT 共享成员变量直接读写P0未彻底修复
ATP-08复制状态仍非完整快照模型P2部分缓解

说明:
P0 = 可能导致崩溃、断言、不可预测线程问题
P1 = 明确错误行为、多人不同步、物理表现明显异常
P2 = 可运行但设计不够稳定,后续容易扩散为 P0/P1


6. 详细问题分析#

6.1 ATP-01:Blueprint AsyncTick 在物理线程执行#

现象#

原始实现中:

  • AAsyncTickPawn::NativeAsyncTick 直接调用 AsyncTick(DeltaTime)
  • UAsyncTickActorComponent::NativeAsyncTick 直接调用 AsyncTick(DeltaTime)

而这两个 AsyncTick 都是 BlueprintImplementableEvent

关键代码位置:

  • AsyncTickPawn.cpp 原始逻辑位于 NativeAsyncTick
  • AsyncTickActorComponent.cpp 原始逻辑位于 NativeAsyncTick
  • 调用入口位于 AsyncTickCallback.cpp:25AsyncTickCallback.cpp:34

风险#

这是本轮最危险的问题之一。

原因很直接:

  1. Blueprint VM 默认不是为物理线程任意执行设计的
  2. 蓝图图表中很容易访问 ActorComponentWorldTimelineNiagaraUIAnimation 等游戏线程对象
  3. 即使某个蓝图当前“碰巧没崩”,也不等于它是线程安全的

可能出现的问题包括:

  1. 偶发崩溃
  2. PIE 下难以复现的断言
  3. 编辑器模式下奇怪的 UObject 生命周期问题
  4. 蓝图逻辑表现不稳定

本轮修复#

本轮改为:

  1. NativeAsyncTick 仍然保留,用于真正的 C++ 异步逻辑
  2. Blueprint AsyncTick 不再直接在物理线程执行
  3. 如果检测到蓝图确实实现了 AsyncTick,则通过 AsyncTask(ENamedThreads::GameThread, ...) 回派到游戏线程执行
  4. 如果没有蓝图实现,则不创建额外任务

修复后关键代码位置:

  • AsyncTickPawn.cpp:6
  • AsyncTickPawn.cpp:11
  • AsyncTickPawn.cpp:38
  • AsyncTickActorComponent.cpp:6
  • AsyncTickActorComponent.cpp:11
  • AsyncTickActorComponent.cpp:38

价值#

这个修复的价值不在于“性能优化”,而在于把最危险的线程越界行为先切断。

它解决的是“架构级安全性问题”。


6.2 ATP-02:Async callback 生命周期注销不闭合#

原问题#

模块销毁物理场景时,旧代码会直接:

  1. SceneToPhysicsManagerMap 中移除
  2. delete PhysManager

但如果某些析构顺序或世界清理顺序与预期不同,OnPhysScenePreTick 里的 Raw 委托和 AsyncObject 可能没有被明确注销。

风险#

这类问题的危险点在于:

  1. 代码在多数时候看起来正常
  2. 真正出问题时往往发生在世界切换、PIE 结束、编辑器关闭、重载模块等“边界时刻”
  3. 一旦留下悬挂委托,后果通常是悬空指针或回调打到已析构对象

本轮修复#

本轮做了三件事:

  1. FAsyncTickManager::~FAsyncTickManager 中显式调用 UnregisterCallbacks()
  2. RegisterCallbacks / UnregisterCallbacks 增加幂等保护
  3. PhysScene_OnPhysSceneTerm 中删除管理器前先显式反注册

关键代码位置:

  • AsyncTickManager.cpp:31
  • AsyncTickManager.cpp:114
  • AsyncTickManager.cpp:131
  • AsyncTickPhysics.cpp:31

价值#

这属于典型的“生命周期闭环”修复。
它不会让单帧性能明显提升,但能显著降低世界退出、切图、PIE 结束时的偶发崩溃概率。


6.3 ATP-03:ERT_Full 非权威端悬挂状态不刷新#

原问题#

旧逻辑中:

  1. 如果 ReplicationMethod == ERT_Full
  2. 且当前实例不是权威端
  3. ClientStateSync(true) 后直接 return

关键代码位置在原 RTuneVehicle::NativeAsyncTick 开头。

这意味着远端车辆虽然会拿到基础复制状态,但:

  1. 悬挂压缩不会更新
  2. 轮胎接地状态不会更新
  3. 某些动画状态不会更新
  4. 手刹 / 轮胎附着相关表现会不完整

本轮修复#

本轮将该逻辑调整为:

  1. ClientStateSync(true)
  2. 再读取当前刚体变换和速度
  3. 继续执行 SuspensionComponent::UpdatePhysicsWithState
  4. 但把 bApplyForces 设为 false

也就是说:

  1. 远端客户端仍然刷新轮子状态
  2. 但不会把本地算出来的悬挂力重新写回刚体

关键代码位置:

  • RTuneVehicle.cpp:447
  • RTuneVehicle.cpp:459
  • SuspensionComponent.h:49
  • SuspensionComponent.cpp:501

同时补充的同步字段#

为了让远端悬挂表现更完整,本轮额外将以下状态加入 ServerState

  1. CurrentTorque
  2. HandbrakeInput

关键代码位置:

  • RTuneVehicle.h:72
  • RTuneVehicle.cpp:1162
  • RTuneVehicle.cpp:1185

价值#

这是本轮最重要的“行为正确性修复”之一。
它不只是防崩,而是直接改善远端车辆的肉眼可见表现。


6.4 ATP-04:刹车俯仰力矩门控变量错误#

原问题#

RTuneVehicle::UpdatePhysicsCore 中,刹车俯仰力矩原本写成:

  1. 条件判断使用 DynamicPitchMomentMultiplier
  2. 实际施加的却是 DynamicBrakeMomentMultiplier

这意味着:

  1. 如果用户只配置了刹车俯仰系数
  2. 但加速俯仰系数为 0
  3. 那么刹车俯仰逻辑根本不会触发

本轮修复#

门控条件已改为正确检查 DynamicBrakeMomentMultiplier

关键代码位置:

  • RTuneVehicle.cpp 对应刹车力矩分支

影响#

这是典型“看起来像调参问题,实则是代码条件错了”的 bug。

如果不修,用户会误以为:

  1. 参数没生效
  2. 车辆重心不对
  3. 刹车姿态系统设计有问题

实际上是判断条件写错。


6.5 ATP-05:OnRep_Steering 旋转分支与权威端不一致#

原问题#

UpdateSteeringGeometry 中权威端与客户端插值分支对 bAllowFullRotation 的解释是一致的:

  1. true 时保留 Pitch / Roll
  2. false 时只更新 Yaw

OnRep_Steering 中这一分支写反了。

关键代码位置:

  • 权威端逻辑:SuspensionComponent.cpp:207
  • 复制响应逻辑:SuspensionComponent.cpp:251

风险#

会导致:

  1. 客户端悬挂轮子朝向与服务端不一致
  2. 车辆转向几何在复制场景下表现异常
  3. 问题容易在多人环境中被误判为网络延迟或插值错误

本轮修复#

OnRep_Steering 现在与权威端分支保持一致。

关键代码位置:

  • SuspensionComponent.cpp:261

6.6 ATP-06:物理线程中直接使用 UWorld 场景查询#

问题描述#

USuspensionComponent::UpdatePhysicsWithState 目前仍在物理线程路径中执行以下逻辑:

  1. GetWorld()
  2. GetRelativeTransform()
  3. World->SweepSingleByChannel(...)
  4. World->LineTraceSingleByChannel(...)

关键代码位置:

  • SuspensionComponent.cpp:507
  • SuspensionComponent.cpp:544
  • SuspensionComponent.cpp:569
  • SuspensionComponent.cpp:593

为什么这是高风险问题#

这是当前代码里最大的“尚未彻底解决”的结构性风险。

原因不是这些 API 一定会立刻崩,而是它们带有明显的游戏线程 / UObject 世界语义:

  1. UWorld 本身并不是一个天然的物理线程专用查询对象
  2. Component 的相对变换、Owner、层级关系都属于 UObject 世界
  3. 这些 API 的线程安全边界很难靠“经验运行正常”来证明

典型症状#

如果这个问题在项目中被放大,常见表现包括:

  1. 多车压力下偶发 trace 结果不稳定
  2. PIE / 关卡切换时偶发断言
  3. 编辑器和打包版行为不一致
  4. 某些平台表现稳定,某些平台更容易出问题

为什么本轮没有彻底改掉#

因为要真正解决它,不能只改一两行:

  1. 要么引入物理线程可用的查询接口
  2. 要么改成“游戏线程预采样 + 物理线程只消费快照”
  3. 要么重做悬挂接地检测的数据来源

这已经是“重构级工作”,不适合在一轮热修里强行塞进去。

建议方向#

建议下一阶段选其中一种:

  1. 方案 A:建立 Physics Query Adapter,所有异步 trace 统一走受控接口
  2. 方案 B:GT 做环境采样,PT 只做受力计算
  3. 方案 C:将悬挂接地检测并回物理场景原生接口,不再通过 UWorld

6.7 ATP-07:GT/PT 共享成员变量直接读写#

问题描述#

当前 RTuneVehicleSuspensionComponent 都存在“物理线程写、游戏线程读”的共享状态。

典型例子:

RTuneVehicle#

游戏线程 Tick 会读取:

  1. Speed
  2. RPM
  3. SteeringInput
  4. LocationVelocity
  5. bHandbrakeInput

关键代码位置:

  • RTuneVehicle.cpp:161
  • RTuneVehicle.cpp:173
  • RTuneVehicle.cpp:202
  • RTuneVehicle.cpp:262

而物理线程 NativeAsyncTick / UpdatePhysicsCore 会写入:

  1. AngularVelocity
  2. AngularAcceleration
  3. Speed
  4. Acceleration
  5. CurrentTorque
  6. RPM

关键代码位置:

  • RTuneVehicle.cpp:442
  • RTuneVehicle.cpp:520
  • RTuneVehicle.cpp:596

SuspensionComponent#

游戏线程 UpdateTick 会读取:

  1. CurrentLength
  2. LinearVelocity
  3. BurnoutRotation
  4. bSingleRaycast
  5. Debug* 系列状态

关键代码位置:

  • SuspensionComponent.cpp:311
  • SuspensionComponent.cpp:327
  • SuspensionComponent.cpp:348
  • SuspensionComponent.cpp:385

而物理线程 UpdatePhysicsWithState 会写入这些成员:

  • SuspensionComponent.cpp:501

风险#

这类问题的危险点在于:

  1. 代码逻辑上“看起来没问题”
  2. 但内存模型层面属于无同步共享访问
  3. 表现可能是偶发抖动、插值跳变、低概率错误

为什么本轮没有直接用锁全包#

因为“到处加锁”不一定是正确解:

  1. 会拉高 PT/GT 竞争
  2. 会掩盖真正的架构问题
  3. 锁范围一旦过大,反而可能拖慢 physics tick

建议方向#

推荐后续升级到显式快照模型:

  1. PT 只写 AsyncStateSnapshot
  2. GT 每帧安全拷贝一次
  3. 所有 UI / 动画 / 调试 / 非权威显示逻辑只读 GT 快照

这是标准、长期可维护的方向。


6.8 ATP-08:复制状态仍不是完整快照模型#

虽然本轮已经补充了:

  1. CurrentTorque
  2. HandbrakeInput
  3. 非权威端悬挂状态刷新

但严格来说,ServerState 仍然不是一个完整的“车辆状态快照”。

例如:

  1. 部分悬挂派生状态仍是客户端自行再计算
  2. 部分可视化状态仍依赖客户端本地读数
  3. 反重力、Debug、轮胎局部状态并未统一复制

这不一定是 bug,但它说明当前系统仍然是“半快照、半本地推导”的混合架构。

混合架构的优点是轻量,缺点是:

  1. 维护成本高
  2. 问题定位困难
  3. 容易在新功能加入后变得更脆弱

7. 本轮已落地修复清单#

本轮实际修改点如下。

7.1 AsyncTickPhysics 层#

  1. FAsyncTickManager 析构时显式 UnregisterCallbacks
  2. RegisterCallbacks 增加幂等保护
  3. UnregisterCallbacks 增加幂等保护
  4. PhysScene_OnPhysSceneTerm 删除管理器前先反注册
  5. FAsyncPhysicsInput::Reset 同时重置 DebugMessages

7.2 AsyncTick Blueprint 边界#

  1. AAsyncTickPawn 增加 DispatchBlueprintAsyncTick
  2. UAsyncTickActorComponent 增加 DispatchBlueprintAsyncTick
  3. BeginPlay 中检测蓝图是否真的实现了 AsyncTick
  4. 仅当蓝图实现存在时,才派发回 GT

7.3 RTuneVehicle 层#

  1. 车辆 async 路径改为调用 DispatchBlueprintAsyncTick
  2. ERT_Full 非权威端仍然刷新悬挂状态,但不施力
  3. ServerState 新增 CurrentTorque
  4. ServerState 新增 HandbrakeInput
  5. ClientStateSync / ServerStateSync 同步上述字段
  6. 修复刹车俯仰门控变量

7.4 SuspensionComponent 层#

  1. UpdatePhysicsWithState 增加 bApplyForces
  2. 非权威端可更新悬挂状态但不回写刚体力
  3. 修复 OnRep_Steering 的旋转分支

8. 对项目表现的实际影响评估#

8.1 稳定性#

本轮修复后,最直接受益的是稳定性:

  1. Blueprint 不再直接跑在物理线程
  2. 世界退出 / 场景析构时的异步回调风险降低
  3. 多人环境下远端车辆状态不完整的问题得到明显缓解

8.2 行为一致性#

多人模式下预计会改善:

  1. 远端轮胎压缩 / 接地状态
  2. 远端手刹视觉反馈
  3. 远端驱动轮相关表现
  4. 复制转向几何一致性

8.3 性能#

本轮改动对性能的影响整体可控:

  1. Blueprint AsyncTick 回派 GT 会增加一些任务调度成本
  2. 但只在蓝图真正实现该事件时才会触发
  3. 与其接受不安全的 PT Blueprint 执行,这点成本是合理的

9. 推荐的下一阶段治理路线#

如果要把这套插件从“能用”升级到“长期稳定、可规模化使用”,建议按以下顺序推进。

阶段一:线程边界收口#

目标:不再让物理线程直接碰高层 UObject 世界语义。

建议动作:

  1. 梳理所有 PT 路径中的 GetWorld / GetOwner / GetRelativeTransform / SetActor...
  2. 建立白名单接口
  3. 所有非白名单 API 一律退出 PT 路径

阶段二:快照化#

目标:GT/PT 不再共享可变成员变量。

建议动作:

  1. RTuneVehicleFVehicleAsyncState
  2. SuspensionComponentFSuspensionAsyncState
  3. PT 写快照,GT 读快照

阶段三:场景查询重构#

目标:悬挂 trace 不再依赖 UWorld 查询。

建议动作:

  1. 设计统一的异步可用查询接口
  2. 或改成 GT 采样 + PT 受力计算
  3. 明确查询的线程模型和数据时序

阶段四:复制模型统一#

目标:复制模式行为一致,避免“某种模式正常,另一种模式表现破碎”。

建议动作:

  1. 定义最小必需状态快照
  2. ERT_Full / ERT_DataOnly / ERT_None 分别写出行为契约
  3. 明确哪些状态由服务端驱动,哪些状态允许客户端推导

10. 建议的验证清单#

在 UE 5.7 环境中,建议至少执行以下验证。

10.1 单机验证#

  1. 单车直线加速、刹车、倒车
  2. 手刹漂移、连续大角度转向
  3. 空中姿态控制
  4. 反重力启用 / 关闭
  5. 动画模式开启 / 关闭

10.2 多车压力测试#

  1. 同场景 10 辆车
  2. 同场景 20 辆车
  3. 频繁生成 / 销毁车辆
  4. PIE 启停多次

10.3 多人验证#

对以下模式分别验证:

  1. ERT_Full
  2. ERT_DataOnly
  3. ERT_None

重点观察:

  1. 远端悬挂压缩
  2. 远端转向几何
  3. 远端手刹与轮胎表现
  4. 服务器与客户端是否出现明显分歧

10.4 生命周期验证#

  1. 切换关卡
  2. 退出 PIE
  3. 关闭编辑器
  4. 热重载或插件重启

重点看是否出现:

  1. 委托未注销报错
  2. 异步回调打到已销毁对象
  3. 编辑器退出时的偶发崩溃

11. 结论#

这套插件的异步车辆物理链路并不是“完全不可用”,但它在设计上明显混用了:

  1. 物理线程逻辑
  2. Blueprint 事件逻辑
  3. UObject 世界逻辑
  4. 复制状态逻辑
  5. 游戏线程动画逻辑

这种混用在早期开发阶段很常见,因为它能快速做出结果。
但一旦项目进入多人、压力场景、复杂蓝图扩展、频繁切图的阶段,问题就会集中暴露。

本轮修复解决了最危险的一批问题:

  1. 切断 Blueprint 直接跑在物理线程的路径
  2. 补齐 async callback 生命周期闭环
  3. 修复多人 ERT_Full 下远端悬挂状态缺失
  4. 修掉两个明确的局部逻辑 bug

从“能跑”到“稳定可靠”,后续真正要做的不是继续堆分支判断,而是把线程边界、状态快照和场景查询模型彻底理顺。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

RTune Async Tick 深度调试报告
https://rina.resonera.cn/posts/svptick/
作者
璃奈
发布于
2026-03-15
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时