惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cyberwarzone
Cyberwarzone
Google DeepMind News
Google DeepMind News
宝玉的分享
宝玉的分享
博客园_首页
量子位
博客园 - 司徒正美
罗磊的独立博客
腾讯CDC
IT之家
IT之家
S
Schneier on Security
Hugging Face - Blog
Hugging Face - Blog
L
Lohrmann on Cybersecurity
H
Hacker News: Front Page
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
J
Java Code Geeks
Know Your Adversary
Know Your Adversary
人人都是产品经理
人人都是产品经理
有赞技术团队
有赞技术团队
大猫的无限游戏
大猫的无限游戏
D
Darknet – Hacking Tools, Hacker News & Cyber Security
AWS News Blog
AWS News Blog
Spread Privacy
Spread Privacy
I
InfoQ
T
Threatpost
Simon Willison's Weblog
Simon Willison's Weblog
云风的 BLOG
云风的 BLOG
L
LINUX DO - 热门话题
A
About on SuperTechFans
Blog — PlanetScale
Blog — PlanetScale
Latest news
Latest news
Forbes - Security
Forbes - Security
Security Latest
Security Latest
NISL@THU
NISL@THU
The GitHub Blog
The GitHub Blog
P
Proofpoint News Feed
The Hacker News
The Hacker News
M
MIT News - Artificial intelligence
S
SegmentFault 最新的问题
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
B
Blog
A
Arctic Wolf
C
Check Point Blog
G
Google Developers Blog
S
Security @ Cisco Blogs
Google DeepMind News
Google DeepMind News
The Cloudflare Blog
L
LINUX DO - 最新话题
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
P
Privacy International News Feed
小众软件
小众软件

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
调试测试与性能优化
我才是银古 · 2026-06-20 · via 博客园 - 我才是银古

第十九章:调试测试与性能优化

19.1 测试框架

19.1.1 测试项目结构

test/
├── LightCAD.MathLib.Tests/      # 数学库单元测试
│   ├── LightCAD.MathLib.Tests.csproj
│   ├── Point2dTests.cs
│   ├── Vector2dTests.cs
│   ├── Matrix4dTests.cs
│   ├── IntersectionTests.cs
│   ├── BezierCurveTests.cs
│   └── NurbsCurveTests.cs
└── WinFormTest/                 # 窗体功能测试
    ├── WinFormTest.csproj
    └── ViewportTests.cs

19.1.2 测试框架选择

LightCAD使用MSTest作为单元测试框架:

<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.x" />
<PackageReference Include="MSTest.TestAdapter" Version="3.x" />
<PackageReference Include="MSTest.TestFramework" Version="3.x" />

19.2 单元测试实践

19.2.1 数学库测试

[TestClass]
public class Point2dTests
{
    [TestMethod]
    public void Distance_Origin_To_3_4_Should_Be_5()
    {
        var p1 = new Point2d(0, 0);
        var p2 = new Point2d(3, 4);
        Assert.AreEqual(5.0, p1.DistanceTo(p2), 1e-10);
    }

    [TestMethod]
    public void MidPoint_Should_Be_Average()
    {
        var p1 = new Point2d(0, 0);
        var p2 = new Point2d(100, 200);
        var mid = p1.MidPoint(p2);
        Assert.AreEqual(50.0, mid.X, 1e-10);
        Assert.AreEqual(100.0, mid.Y, 1e-10);
    }

    [TestMethod]
    public void IsEqualTo_WithinTolerance_Should_Return_True()
    {
        var p1 = new Point2d(1.0, 2.0);
        var p2 = new Point2d(1.0 + 1e-11, 2.0 - 1e-11);
        Assert.IsTrue(p1.IsEqualTo(p2));
    }

    [TestMethod]
    public void IsEqualTo_OutsideTolerance_Should_Return_False()
    {
        var p1 = new Point2d(1.0, 2.0);
        var p2 = new Point2d(1.1, 2.0);
        Assert.IsFalse(p1.IsEqualTo(p2));
    }
}

[TestClass]
public class Matrix4dTests
{
    [TestMethod]
    public void Identity_Transform_Should_Not_Change_Point()
    {
        var point = new Point3d(10, 20, 30);
        var result = Matrix4d.Identity.Transform(point);
        Assert.AreEqual(10, result.X, 1e-10);
        Assert.AreEqual(20, result.Y, 1e-10);
        Assert.AreEqual(30, result.Z, 1e-10);
    }

    [TestMethod]
    public void Translation_Should_Move_Point()
    {
        var point = new Point3d(0, 0, 0);
        var matrix = Matrix4d.CreateTranslation(10, 20, 30);
        var result = matrix.Transform(point);
        Assert.AreEqual(10, result.X, 1e-10);
        Assert.AreEqual(20, result.Y, 1e-10);
        Assert.AreEqual(30, result.Z, 1e-10);
    }

    [TestMethod]
    public void RotationZ_90Degrees_Should_Rotate_Correctly()
    {
        var point = new Point3d(1, 0, 0);
        var matrix = Matrix4d.CreateRotationZ(Math.PI / 2);
        var result = matrix.Transform(point);
        Assert.AreEqual(0, result.X, 1e-10);
        Assert.AreEqual(1, result.Y, 1e-10);
        Assert.AreEqual(0, result.Z, 1e-10);
    }

    [TestMethod]
    public void Inverse_Times_Original_Should_Be_Identity()
    {
        var matrix = Matrix4d.CreateTranslation(10, 20, 30)
            * Matrix4d.CreateRotationZ(Math.PI / 4)
            * Matrix4d.CreateScale(2, 3, 1);

        var inverse = matrix.Inverse();
        var product = matrix * inverse;

        // 验证结果接近单位矩阵
        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < 4; j++)
            {
                var expected = (i == j) ? 1.0 : 0.0;
                Assert.AreEqual(expected, product[i, j], 1e-10);
            }
        }
    }
}

[TestClass]
public class IntersectionTests
{
    [TestMethod]
    public void LineLineIntersect_Perpendicular()
    {
        var result = LineLineIntersect.Compute(
            new Point2d(0, 0), new Vector2d(1, 0),
            new Point2d(5, -5), new Vector2d(0, 1)
        );

        Assert.IsNotNull(result);
        Assert.AreEqual(5.0, result.Value.X, 1e-10);
        Assert.AreEqual(0.0, result.Value.Y, 1e-10);
    }

    [TestMethod]
    public void LineLineIntersect_Parallel_Returns_Null()
    {
        var result = LineLineIntersect.Compute(
            new Point2d(0, 0), new Vector2d(1, 0),
            new Point2d(0, 1), new Vector2d(1, 0)
        );

        Assert.IsNull(result);
    }

    [TestMethod]
    public void LineCircleIntersect_Two_Points()
    {
        var results = LineCircleIntersect.Compute(
            new Point2d(-10, 0), new Vector2d(1, 0),
            new Point2d(0, 0), 5
        );

        Assert.AreEqual(2, results.Count);
    }

    [TestMethod]
    public void LineCircleIntersect_Tangent()
    {
        var results = LineCircleIntersect.Compute(
            new Point2d(0, 5), new Vector2d(1, 0),
            new Point2d(0, 0), 5
        );

        Assert.AreEqual(1, results.Count);
        Assert.AreEqual(0, results[0].X, 1e-6);
        Assert.AreEqual(5, results[0].Y, 1e-6);
    }

    [TestMethod]
    public void LineCircleIntersect_No_Intersection()
    {
        var results = LineCircleIntersect.Compute(
            new Point2d(0, 10), new Vector2d(1, 0),
            new Point2d(0, 0), 5
        );

        Assert.AreEqual(0, results.Count);
    }
}

19.2.2 运行测试

# 运行所有测试
dotnet test LightCAD1.sln

# 运行特定测试项目
dotnet test test/LightCAD.MathLib.Tests/LightCAD.MathLib.Tests.csproj

# 运行特定测试类
dotnet test --filter "ClassName=LightCAD.MathLib.Tests.Point2dTests"

# 运行特定测试方法
dotnet test --filter "FullyQualifiedName~Distance_Origin_To_3_4"

# 生成代码覆盖率报告
dotnet test --collect:"XPlat Code Coverage"

19.3 调试技巧

19.3.1 断点策略

几何计算断点

在数学库的关键计算位置设置断点:

  • 交点计算结果
  • 曲线参数求值
  • 矩阵变换结果

渲染调试断点

在渲染管线的关键位置设置断点:

  • 几何体创建
  • 材质设置
  • 场景更新

输入处理断点

在输入系统的关键位置设置断点:

  • 鼠标坐标转换
  • 捕捉点计算
  • 命令执行

19.3.2 日志系统

public static class Logger
{
    private static readonly string logDir = "Logs";
    private static StreamWriter writer;

    public static void Initialize()
    {
        Directory.CreateDirectory(logDir);
        var logFile = Path.Combine(logDir,
            $"lightcad_{DateTime.Now:yyyyMMdd_HHmmss}.log");
        writer = new StreamWriter(logFile, true);
    }

    public static void Debug(string message)
    {
        Log("DEBUG", message);
    }

    public static void Info(string message)
    {
        Log("INFO", message);
    }

    public static void Warning(string message)
    {
        Log("WARN", message);
    }

    public static void Error(string message, Exception ex = null)
    {
        Log("ERROR", message);
        if (ex != null)
        {
            Log("ERROR", $"Exception: {ex.Message}");
            Log("ERROR", $"StackTrace: {ex.StackTrace}");
        }
    }

    private static void Log(string level, string message)
    {
        var line = $"[{DateTime.Now:HH:mm:ss.fff}] [{level}] {message}";
        writer?.WriteLine(line);
        writer?.Flush();
        System.Diagnostics.Debug.WriteLine(line);
    }
}

19.3.3 可视化调试

/// <summary>
/// 调试辅助:在视口中绘制调试几何
/// </summary>
public static class DebugDraw
{
    private static List<TemporaryGraphic> debugGraphics = new();

    public static void DrawPoint(Point2d point, LcColor color)
    {
        debugGraphics.Add(new TemporaryGraphic
        {
            Type = TempGraphicType.Point,
            Center = point,
            Color = color
        });
    }

    public static void DrawLine(Point2d start, Point2d end, LcColor color)
    {
        debugGraphics.Add(new TemporaryGraphic
        {
            Type = TempGraphicType.Line,
            Points = new[] { start, end },
            Color = color
        });
    }

    public static void DrawBounds(BoundingBox2d bounds, LcColor color)
    {
        DrawLine(bounds.Min, new Point2d(bounds.Max.X, bounds.Min.Y), color);
        DrawLine(new Point2d(bounds.Max.X, bounds.Min.Y), bounds.Max, color);
        DrawLine(bounds.Max, new Point2d(bounds.Min.X, bounds.Max.Y), color);
        DrawLine(new Point2d(bounds.Min.X, bounds.Max.Y), bounds.Min, color);
    }

    public static void Clear()
    {
        debugGraphics.Clear();
    }
}

19.4 性能优化

19.4.1 内存优化

对象池

public class ObjectPool<T> where T : new()
{
    private readonly ConcurrentBag<T> pool = new();
    private readonly int maxSize;

    public ObjectPool(int maxSize = 1000)
    {
        this.maxSize = maxSize;
    }

    public T Rent()
    {
        return pool.TryTake(out var item) ? item : new T();
    }

    public void Return(T item)
    {
        if (pool.Count < maxSize)
        {
            pool.Add(item);
        }
    }
}

// 使用示例:复用Point2d列表
var pointListPool = new ObjectPool<List<Point2d>>();
var points = pointListPool.Rent();
try
{
    // 使用points...
}
finally
{
    points.Clear();
    pointListPool.Return(points);
}

19.4.2 渲染优化

批量渲染

public class BatchRenderer
{
    /// <summary>
    /// 将多条线段合并为单次绘制调用
    /// </summary>
    public void DrawLinesBatch(List<LcLine> lines, LcColor color)
    {
        var positions = new float[lines.Count * 6]; // 每条线2个点×3坐标

        for (int i = 0; i < lines.Count; i++)
        {
            positions[i * 6 + 0] = (float)lines[i].StartPoint.X;
            positions[i * 6 + 1] = (float)lines[i].StartPoint.Y;
            positions[i * 6 + 2] = 0;
            positions[i * 6 + 3] = (float)lines[i].EndPoint.X;
            positions[i * 6 + 4] = (float)lines[i].EndPoint.Y;
            positions[i * 6 + 5] = 0;
        }

        var geometry = new BufferGeometry();
        geometry.SetAttribute("position",
            new BufferAttribute(positions, 3));

        var material = new LineBasicMaterial
        {
            Color = ConvertColor(color)
        };

        scene.Add(new LineSegments(geometry, material));
    }
}

空间索引

public class SpatialIndex
{
    private QuadTree<LcEntity> quadTree;

    public SpatialIndex(BoundingBox2d worldBounds)
    {
        quadTree = new QuadTree<LcEntity>(worldBounds, maxDepth: 8);
    }

    public void Insert(LcEntity entity)
    {
        quadTree.Insert(entity, entity.Bounds);
    }

    public void Remove(LcEntity entity)
    {
        quadTree.Remove(entity);
    }

    /// <summary>
    /// 范围查询(比遍历所有实体快得多)
    /// </summary>
    public IEnumerable<LcEntity> Query(BoundingBox2d searchBounds)
    {
        return quadTree.Query(searchBounds);
    }
}

19.4.3 性能分析

public class PerformanceMonitor
{
    private Dictionary<string, List<long>> timings = new();
    private Stopwatch stopwatch = new();

    /// <summary>
    /// 计时开始
    /// </summary>
    public IDisposable MeasureTime(string operationName)
    {
        return new TimeMeasurement(this, operationName);
    }

    private class TimeMeasurement : IDisposable
    {
        private PerformanceMonitor monitor;
        private string name;
        private long startTicks;

        public TimeMeasurement(PerformanceMonitor monitor, string name)
        {
            this.monitor = monitor;
            this.name = name;
            this.startTicks = Stopwatch.GetTimestamp();
        }

        public void Dispose()
        {
            var elapsed = Stopwatch.GetTimestamp() - startTicks;
            var elapsedMs = elapsed * 1000 / Stopwatch.Frequency;

            if (!monitor.timings.ContainsKey(name))
                monitor.timings[name] = new List<long>();
            monitor.timings[name].Add(elapsedMs);
        }
    }

    /// <summary>
    /// 输出性能报告
    /// </summary>
    public void PrintReport()
    {
        foreach (var (name, times) in timings)
        {
            var avg = times.Average();
            var max = times.Max();
            var min = times.Min();
            Console.WriteLine(
                $"{name}: 平均={avg:F2}ms, 最大={max}ms, 最小={min}ms, " +
                $"调用次数={times.Count}");
        }
    }
}

// 使用示例
var monitor = new PerformanceMonitor();

using (monitor.MeasureTime("渲染场景"))
{
    viewportRenderer.Render(viewport, document);
}

using (monitor.MeasureTime("视图构建"))
{
    viewBuilder.BuildView(buildParams);
}

monitor.PrintReport();

19.5 常见问题排查

19.5.1 渲染问题

问题 可能原因 解决方法
图元不显示 图层被冻结/关闭 检查图层状态
3D模型黑色 法线方向错误 重新计算法线
闪烁 Z-fighting 增加深度偏移
性能低 实体过多 启用LOD/空间索引

19.5.2 几何计算问题

问题 可能原因 解决方法
交点丢失 容差设置不当 调整Tolerance值
布尔运算失败 退化几何体 检查输入有效性
变换结果错误 矩阵乘法顺序 检查变换组合顺序

19.6 本章小结

本章介绍了LightCAD的测试、调试和性能优化方法。单元测试覆盖了数学库的核心功能,确保几何计算的正确性。调试技巧包括断点策略、日志系统和可视化调试。性能优化涵盖了对象池、批量渲染、空间索引等技术。性能监控工具帮助开发者识别和解决性能瓶颈。


上一章第十八章:标注与文本系统

下一章第二十章:实战案例与二次开发指南