游戏引擎学习第249天:清理调试宏
欢迎大家,让我们直接进入调试代码的改进工作
接下来,我们来看一下上次停留的位置。如果我没记错的话,上一场直播的结尾我有提到一些我想做的事情,并且在代码中留下了一个待办事项。所以也许我们今天首先做的就是解决这个问题。但首先回顾一下上次的进展,我们其实已经开始着手完成一些任务了,我发现那时的状态还没有完全完成。
回顾:调试系统相对可用,但所有名称总是被强制通过层级系统,这样会为一些内容创建虚拟节点
实际上,调试系统已经相对可用了,因此可以通过它来创建各种系统。不过,有几个地方需要清理。首先,我们遇到了一个奇怪的问题,就是所有的名称都被强制通过层级系统,这会为一些东西创建虚拟节点,即使我们可能并不希望这样做。当然,在某些情况下,我们其实是希望这样做的。因此,需要决定如何处理这些节点。是希望总是为此目的创建数据块,还是有其他更合适的做法?我们需要进一步思考和决定。
我们还想让分析器(profiler)重新起作用
我们接下来的任务是重新开启性能分析功能,并提升其实用性。目前虽然已经在记录性能分析信息,但实际上并没有真正加以利用。为了实现这一点,我们希望能够将性能分析器系统性地整合进现有的功能集合中。
这个过程应该相对简单,因为我们已经具备相关功能的表达和组织方式。接下来,我们会加载游戏,定位到 game.cpp
文件。在这个文件中,我们曾经添加了一个调试模块。现在的目标是使我们能够更灵活地将性能分析器添加到系统中,并能够借助这个机制去管理和使用性能数据。这样,我们就可以更充分地利用已有的性能信息来辅助优化和开发工作。
game.cpp:在调试数据块中添加 DEBUG_PROFILE()
我们希望实现的功能是:当在调试模块中使用某些代码时,可以直接创建一个性能分析器(profile),并将其嵌入到调试代码块中。我们的目标是能够非常方便地指定某个函数,并对其进行性能分析。
例如,现在我们有一个用于计时的函数,我们希望通过这个机制对 game_update_and_render
这个函数进行计时,并将其纳入性能分析器的记录范围内。这将使我们能够追踪这个函数的执行时间,从而进行进一步优化。
我们打算这么做:调用该计时函数,把需要分析的函数包裹起来,并创建一个新的调试性能分析器。但当前的实现中,如果尝试直接编译,会遇到严重的错误,这是因为我们并没有真正定义 debug_profile
这个功能。因此,下一步我们需要做的就是实现或补全 debug_profile
的定义,使其能被正常调用并用于调试和性能分析。
game_debug_interface.h:定义 DEBUG_PROFILE
我们现在的目标是在接口中加入性能分析器的支持,使其能够被方便地嵌入现有的调试系统中。实现这项功能的思路其实非常清晰,就是仿照之前已有的调试变量(如 debug value)等方式进行处理。具体来说,只需要添加一个新的 #define
宏,例如 DEBUG_PROFILE
,并传入我们想要分析的函数名称。
这个宏的作用就是在调用时记录当前正在进行性能分析的函数名称。由于已经存在函数名,所以可以直接引用该函数。我们希望能够传递一个表示该函数在性能追踪体系中的类型,例如之前定义的 trace
类型。这个系统中已经存在线程计数器列表(counter thread list)的概念,因此我们可以继续使用这个机制。
接下来,计划引入一个新的类型,用来表示正在计时或分析的函数集合(counter function list),将其与函数名称关联,从而在性能数据流中记录该事件。这样在调试系统处理这类事件时,就可以识别出这部分数据并对其进行追踪。
实现逻辑上,当数据被写入调试数据块时,会作为一个元素被存储。一旦这些元素被遍历,我们就能重新拾取到它们并加以利用。不过现在还存在一个小问题:我们可能尚未实现真正的绘制(绘图)逻辑。当前的实现中,似乎只调用了 debug_draw_event
,而不是 debug_draw_element
,因此不能确定是否能正确显示这些新类型的数据。
这里的困惑源于系统本身对数据块与调试元素的区分方式。我们尚不确定这些函数分析器是否应作为普通数据块的一部分来处理,也可能需要独立处理方式。这是目前需要进一步厘清和设计的部分。
总结起来,我们的工作包括:
- 添加
debug_profile
宏以支持性能分析调用; - 为函数分析数据指定类型(如 counter function list);
- 将分析事件写入调试数据流;
- 确保调试系统能够识别和处理这类事件;
- 最终考虑如何绘制和可视化这些数据,以完成从记录到展示的闭环。
我们有调试元素(可以存在于多个层级中)和调试事件(通过流传入的内容,我们试图记录这些事件)
当前的系统中存在两种不同的调试结构:调试元素(debug elements) 和 调试事件(debug events)。调试元素是可以存在于多个层级结构中的实体,可以有多个不同的实例,组成一类树状或分层的可视化数据。而调试事件则是通过数据流记录下来的事件,属于时间线上实际发生的操作记录。
基于这一结构差异,现在考虑在实现函数性能分析器时,或许不需要让其成为依附于某个数据块的数据结构。换句话说,不必再将性能分析数据硬塞进已有的数据块中,而是直接将其作为独立的调试元素来处理。
具体的思路是在事件遍历过程中,当遇到性能分析相关的事件时,直接创建一个新的调试元素,将其作为系统的独立组成部分处理,而不是让它参与已有的数据块结构。例如,在处理事件类型时,系统目前已经有多个事件类型,如 data block open
、data block close
等。针对新的分析器事件,也可以在此类结构中引入对应的类型,并在遍历时识别并创建相应的调试元素。
这种做法的好处是结构更清晰,便于管理和可视化,避免将分析器事件混淆在普通数据块中。这样可以更灵活地展示和操作分析数据,也更符合调试系统中“元素”和“事件”的设计分离原则。总之,这是一种将性能分析系统更好地融入现有调试框架的方式,有助于提升整体的功能完整性和可维护性。
game_debug.cpp:为 DebugType_CounterFunctionList 添加一个空的 case,检查是否使用现有的调试元素足以用于分析
我们当前的任务是处理一个特定类型的调试数据:counter_function_list
,这是用于函数性能分析的一种结构。思路是,当系统在解析调试事件时遇到这种类型的内容时,应该识别出这是一个用于分析的函数,并据此创建对应的性能分析器。
我们已经实现了解析过程,也成功生成了一个 debug_element
实例。接下来希望做的是直接利用这个已经生成的调试元素来创建性能分析器。初步判断这个过程可能可以自动完成,不需要太多额外处理。
然而,在调试的过程中,尝试查看一个被命名为 game_update_and_render
的函数是否已成功作为调试元素加载时,却并没有发现它出现在调试界面中。这引发了一个问题:元素明明被创建了,但似乎没有被真正地插入到调试系统中可视化的结构中。
分析后发现,原因可能在于这些元素没有被加入到某个“组”(group)中。在调试系统的设计中,调试元素需要通过 add_element_to_group
函数被插入到对应的分组结构中,而这一步当前似乎没有正常执行。系统在尝试获取父分组时未能成功,导致元素无法归属到任何组,从而未能被显示或处理。
下一步,我们需要检查这个 add_element_to_group
的具体实现,确认其对父组的处理逻辑。如果没有正确识别或创建根组,那么后续的元素自然也无法加入。这一问题的核心在于组结构未能建立或关联,导致有效的调试数据未能呈现。
因此,接下来的操作是:
- 找到
add_element_to_group
的具体行为逻辑; - 设置断点观察
debug_value
相关元素在加载时是否能正确获得父组; - 确保即便当前元素没有上层组,也能以根元素的形式被添加到调试系统中;
- 最终让
counter_function_list
类型的函数分析数据,能够被识别、存储并可视化展示。
这一过程将确保函数性能分析器能够真正嵌入调试系统,并在运行时动态记录和可视化指定函数的性能信息。
game_debug.cpp:检查 DebugType_MarkDebugValue 是否仍在使用,并删除它
目前正在清理一些代码,发现某些部分似乎已经没有作用,因此尝试删除它们以简化系统结构。在这一过程中,仍然存在一个疑问:即使尝试将性能分析器插入到调试系统中,操作本身看似成功,但结果却没有可见效果。
深入分析发现,虽然插入函数分析器的操作已经执行,但并未显示在可视化界面上的原因,是因为当前调试系统只会为一些特定的预定义模块(如 global_renderer
和 camera
)创建节点,至于最终用户自定义的节点(如 game_update_and_render
),并不会被自动处理。也就是说,虽然数据已生成,但从未主动使用或展示。
为了验证这一判断,进行了一个测试:手动创建一个叫 Foo_GameUpdateAndRender
的节点。结果验证了猜测:系统确实能创建这些节点,只不过默认不会对最终目标函数做任何进一步处理。因此,game_update_and_render
也确实已经存在,只是因为没有显式使用或绑定,它没有被展现出来。
为了解决这个问题,需要将性能分析器插入到合适的位置,并指定其作用范围。也就是说,在调用分析器时,除了传入函数名外,还必须指定其挂载的调试数据结构(比如挂在哪个调试组、属于哪个区域)。目前的分析逻辑只处理了函数名,没有指定目标位置,因此即便创建了分析数据,也无从显示和管理。
这进一步说明,也许将分析器挂在某个数据块中是一个更合理的做法,因为数据块提供了上下文结构,让系统知道该将性能数据展示在哪个层级或模块下。
不过,还有一个不确定的问题:当前系统是否支持在数据块中嵌套或挂载这类函数分析器,还需要进一步确认。如果支持,那么通过数据块管理将是最清晰、结构最合理的路径。
总结关键点如下:
- 删除无效代码以清理系统结构;
- 分析发现性能分析器数据已生成但未展示;
- 原因是默认系统只处理部分预设节点,忽略了最终目标函数;
- 为了让分析器数据可视化,必须指定插入目标位置(如所属数据块);
- 使用数据块来组织分析数据可能是可行的优化方向,但需要确认系统支持程度;
- 后续要扩展性能分析器调用方式,支持传入函数名和目标挂载点,以实现更完整的集成。
game.cpp:检查调试系统是否支持嵌套的数据块
在调试系统中测试了在数据块(data block)内部嵌套其他数据块的行为,目的是了解是否支持这种结构层级。在实验过程中发现,系统目前不支持数据块嵌套。例如创建了一个用于性能分析的 profile
数据块,虽然这个数据块本身被创建了,但并没有嵌套到任何已有的数据块中,呈现的是平级结构。这说明嵌套机制并未被实现。
如果未来确实需要嵌套结构,就必须显式地实现相关逻辑。目前系统的行为还不具备自动处理子块的能力。这也让整个调试结构显得有些混乱,不容易按预期组织好分析数据。
这一发现促使我们重新思考整个系统中调试结构的组织方式。当前实现方式在表现上不够直观,导致即使某些操作“成功”了,实际上也没有完全按照需求被呈现出来。
因此,当前策略是先不强行解决嵌套结构的问题,而是优先完成性能分析器本身的显示工作。先把分析数据在调试界面中正确渲染出来,确保能看到具体的分析内容。之后再回过头来,重新梳理整个调试接口的设计,优化嵌套结构的支持方式。
当前的计划是:
- 确认数据块不能嵌套;
- 先将性能分析器成功写入调试系统,确保可视化正常;
- 后续着手重新设计调试接口,使其清晰、统一、易于扩展;
- 明确最终每个调试功能(如性能分析)的调用方式和数据结构;
最终目标是让调试系统的接口明确规范,能正确支持调试数据的组织与展示,特别是在涉及多个层级或模块时具备良好的可维护性与拓展性。
game_debug.cpp:在 DEBUGDrawElement 中包括 DebugType_CounterFunctionList 的 case
当前的任务是让函数分析器的内容(即函数列表)能在调试系统中被正确绘制出来。我们已经确认,当调用 debug_draw_element
函数时,如果元素的类型是 counter_function_list
,就应该执行对应的绘制逻辑。为了快速验证,临时让它复用现有线程列表(thread_list
)的绘制方式,虽然现在还没有真正绘制函数列表本身,但这只是个过渡方案,后续可以替换为具体的函数数据渲染逻辑。
但在实际运行时发现,并没有看到预期中的函数列表显示出来。进一步排查时注意到,该元素确实是被创建在某个数据块(data block
)内部的,这可能会影响其可见性。理论上,这种结构下它应该在数据块上层进行显示,但实际上并没有呈现。
因此下一步是检查是否函数列表真的被加入了要绘制的元素集中,或者只是被创建但没有注册到绘制流程中。也就是说,有可能这个元素虽然存在于内存中,但由于没有被添加到用于渲染的树状结构或分组列表里,系统并没有真正调用到它的绘制逻辑。
初步猜测的问题点包括:
- 虽然
counter_function_list
类型的元素被创建,但没有被插入到调试元素组(group)中; - 在调用
debug_draw_element
时,系统根本没有迭代到这个元素; - 数据块嵌套受限,导致它被遮蔽或忽略;
- 绘制逻辑没有正确绑定该类型到实际可视化步骤中。
接下来的重点是:
- 检查调试元素是否被正确加入到数据结构中;
- 确保遍历元素时不会跳过
counter_function_list
; - 临时使用
thread_list
的绘制逻辑验证是否能触发显示; - 一旦验证流程通畅,再补充具体的函数列表绘制代码。
整体上,这一阶段的目标是:确认分析器类型的调试元素可以完整地进入绘制流程,并在界面上被看到,哪怕内容暂时不完整。这将为后续功能完善奠定基础。
game_debug.cpp:删除之前为 DebugType_CounterFunctionList 添加的拦截调用(空的 case)
我们现在已经基本理清了调试系统中的分析器功能是如何工作的,函数列表最终能够正确存储到数据块中,这是通过拦截调用机制实现的。验证后也成功确认数据被加入并能够被调试系统识别与处理。
当前的整体架构虽然已能跑通基本功能,但代码结构较为松散、零散,逻辑分布在多个地方,导致维护和扩展不便。因此,下一步目标是对接口部分进行精简与统一,使整个调试和分析功能变得更清晰、更系统化。
当前已有的功能点包括:
- 时间函数记录机制已可用;
- 函数分析器的调试元素能够被识别与存储;
- 能够通过数据流将这些信息传入调试绘制系统。
接下来需要明确和优化的是:
- 界面调用统一化:决定一个清晰、统一的调用接口,使得所有分析相关的功能都能通过一个规范的路径执行,而不是散落在不同模块。
- 调试接口设计:定义一套标准的调试接口,使新增功能只需走这条路径,而不需要修改底层结构或分支逻辑。
- 不再冗余分支判断:现在的代码逻辑中有些地方是为了解决之前未统一处理的特殊情况,优化后希望能减少这种“补丁式”的判断,让逻辑更流畅。
- 重构插入方式:将调试元素的插入流程标准化,比如函数分析器不需要手动判断其位置,而是交由统一的管理器根据类型和上下文自动完成插入和绘制注册。
最终目标是让调试系统具备以下特点:
- 模块结构清晰、逻辑集中;
- 所有数据流动路径可预测、可追踪;
- 添加新调试类型时无需大范围修改原有代码;
- 可视化呈现自动关联,无需手动干预渲染注册流程。
我们将从实际调用入手,回头重新检查每一处分析相关功能的调用路径,确保这些路径清晰、合理并且能够复用。之后再集中整理界面接口,使整个调试与分析系统更简洁、更强大。
game.cpp:优化数据块的语法
我们现在的核心目标是简化并理顺调试系统中的数据块(data block)结构,确保数据能够清晰、合理地呈现,同时具备良好的可维护性和扩展性。
目前构想的做法是,将数据块表示方式简化为一个统一的结构体调用,去除繁琐的 Begin/End
配对调用模式,改为像 debug_data_block("名称")
这样的语义性调用,这样可以通过构造函数/析构函数自动完成开启与关闭,代码逻辑更紧凑、更不易出错。
我们当前的改动和计划如下:
1. 数据块调用方式简化
改为类似如下写法:
{DebugDataBlock block("菜单名/子菜单名/数据块名");DebugValue("变量名", 数值);
}
通过构造函数打开,析构函数自动关闭,不再需要手动管理 Begin/End
。
2. 命名不再强制使用下划线(_)连接
以前用下划线拼接结构名和变量名(例如 Global_Camera_X
),是因为命名限制。现在我们可以直接使用清晰的路径方式,例如 "Global/Camera/X"
,可以更直观地表示调试项的分层结构,也便于菜单系统展示成层级结构。
3. 支持层级结构的路径表达
路径用 /
分隔形成层级,例如:
"Renderer/Camera/FOV"
"Game/GroundChunks/SimulationTime"
这类似于文件系统结构,使调试信息呈现更清晰,并支持自动归类。
4. 唯一标识问题(GUID ID)
为了在多个运行中能识别出相同的数据块,我们使用哈希机制,基于路径字符串进行哈希,避免依赖不稳定的指针地址。
- 使用字符串路径的哈希值作为
GUID ID
; - 避免多个相同名字数据块混淆(例如多个实体的
"SelectedEntity"
); - 后续如果需要更复杂识别机制,可以引入辅助 ID 或作用域识别。
5. 统一处理方式带来的好处
- 不需要关注当前是数据块、线程、计数器还是事件,所有内容都走统一数据结构;
- 数据自动插入层级结构中,渲染时按层级组织;
- 降低开发接入成本,逻辑更加一致。
6. 示例结构(更清晰的菜单展示)
例如:
DebugDataBlock block("AI/Familiar/FollowsHero");
DebugValue("IsFollowing", true);
DebugValue("Distance", 42.7f);
将会呈现为:
AI
└── Familiar└── FollowsHero├── IsFollowing = true└── Distance = 42.7
7. 后续工作
- 将目前已有的 Debug 值迁移为新格式;
- 确保调试绘制逻辑支持路径识别;
- 考虑支持数据块内部嵌套(如 AI 模块中嵌套多个子系统);
- 抽象出更好用的调试接口工具,减少重复代码;
- 统一处理带有名字哈希的调试项注册与管理逻辑。
整体来看,这套调整将会大大提升调试系统的清晰度与可维护性,使其更加模块化、结构化,同时减少不必要的分支判断与手动管理逻辑,为未来功能扩展(如运行时搜索调试项、自动对比等)打下良好基础。
game_debug_interface.h:重写 DEBUG_DATA_BLOCK 以适应 API 的变化
我们现在正在重构调试系统的数据块结构,目标是将原本显式调用 BeginDataBlock
和 EndDataBlock
的方式,改为使用一个自动管理作用域的 RAII(Resource Acquisition Is Initialization)风格结构体,也就是用构造函数开始数据块,用析构函数自动关闭。
当前遇到的技术细节和解决思路如下:
1. 数据块作用域封装
我们希望简化成这样的形式:
{DebugDataBlock block("名称/子项");// 数据项
}
这样只需要一个 debug_data_block
宏或者直接结构体调用,就能完成整个生命周期管理。
2. 不再需要使用宏生成唯一名称
在定时器(例如 timed_block
)中使用宏是因为同一个作用域内可能有多个定时器,需要生成唯一变量名。
而现在我们约定:debug_data_block
只能写在作用域顶部,这样就不可能发生重名问题,也就不需要用宏扩展搞乱七八糟的唯一命名了。
这极大简化了实现:
DebugDataBlock db("System/Renderer");
就足够了,不需要什么 #define
和 __LINE__
拼接的宏技术。
3. 构造函数 & 析构函数自动管理
- 构造函数负责发起“开始数据块”的调试调用;
- 析构函数负责发送“结束数据块”的调试调用。
这样可以防止忘记关闭数据块,减少 Bug。
4. 不再需要 BeginArray
/ EndArray
之前系统中曾定义 BeginArray
和 EndArray
,但现在发现它们实际上没有实现任何实际功能,因此可以直接忽略,无需保留这套 API。
5. 接口最终形态
我们现在构思的接口形式如下:
{DebugDataBlock block("Game/GroundChunks/Renderer");DebugValue("NumVisibleChunks", chunk_count);DebugValue("LastUpdateTimeMS", last_time_ms);
}
这种结构清晰、层次分明、无需额外手动管理闭合、也避免重复变量命名,同时也便于调试 UI 中以树状方式展示。
6. 实现上的下一步
我们只需要:
- 实现
DebugDataBlock
结构体,它在构造时发出BeginDataBlock(name)
,析构时发出EndDataBlock()
; - 调整现有调试代码路径,使用这个结构替换老旧的
Begin/End
风格; - 确保调试渲染器(debug UI)正确识别和绘制这种新结构的分层数据块。
总结
这一重构目标清晰:用更现代、安全、简洁的方式组织调试信息。我们避免了 C++ 宏的复杂性,去掉了无用的接口,统一了调试结构的风格和表现方式,为后续功能扩展(如多层级数据展示、过滤、折叠)提供了更坚实的基础。整个方向正确,当前就是逐步实现构造和数据传递部分的逻辑。
实现数据块开始和结束的构造函数
当前的重点在于重构和简化调试数据块(debug data block
)的管理逻辑,同时让它更整洁、自动化并易于使用。以下是内容的详细总结:
主要目标
- 实现一个自动管理的调试数据块机制,简化之前繁琐的手动调用
begin_data_block()
和end_data_block()
的过程。 - 新方案希望通过构造函数和析构函数的方式,利用对象生命周期自动调用打开和关闭逻辑。
- 使用宏定义(如
DEBUG_DATA_BLOCK(...)
)简化调用,便于调试代码的书写。
技术实现方式与考虑
-
自动封装 Begin/End 调用:
- 利用 RAII(构造/析构)机制,
debug_data_block
类型在构造时调用begin_data_block()
,在析构时调用end_data_block()
。 - 这样可以保证数据块在作用域结束时自动正确关闭。
- 利用 RAII(构造/析构)机制,
-
简化宏定义的形式:
- 使用宏如:
DEBUG_DATA_BLOCK("name")
来自动创建一个本地的debug_data_block
对象。 - 避免了手动书写打开/关闭语句。
- 使用宏如:
-
唯一变量命名问题:
- 宏中通过
##
拼接方式自动生成唯一变量名(如:DataBlockRender_
)。 - 避免变量名冲突并确保每个作用域中只有一个对应的块对象。
- 宏中通过
-
附加信息(如文件名、行号等):
- 思考是否需要额外保存文件名、行号等信息,但目前的系统似乎并没有在普通事件中使用这些信息。
- 因此当前不准备引入这些字段,以保持实现简单。
当前实现状态
- 构造函数中能正确接收块名称,并自动开始一个数据块。
- 宏定义能够顺利展开,生成本地作用域对象,无需手动指定唯一变量名。
- 并未实现文件名、行号等调试信息的存储。
- 数据块的唯一标识目前仅依赖于字符串名称或指针,不涉及多重策略。
后续打算
- 初步实现完成后,下一步是:
- 清理已有的
begin_data_block
和end_data_block
调用方式。 - 将它们替换为新方式统一管理。
- 进一步测试实际运行中调试数据的层级组织是否如预期。
- 清理已有的
- 最终目标是让整个调试 UI 更整洁,并减少人为调用时可能造成的错乱或遗漏。
小结
整体方案旨在利用 C++ 的语言机制(RAII + 宏展开)来构建一个更简洁、自动化的调试数据块系统。当前重点是宏命名展开与生命周期管理,后续将继续完善界面呈现、数据唯一标识等细节。
game_debug.cpp:检查 GUID 的使用位置
当前的工作主要集中在调试系统中数据块(data block)的唯一标识生成与调试事件记录机制的合理性分析。以下是详细的中文总结:
核心关注点
当前正在分析调试数据块中 GUID
参数的用途以及 record_debug_event()
函数调用中 unique_file_counter_string
的实际意义和潜在问题。
具体分析内容
-
GUID
的用途与 ID 生成:- 在执行
open_data_block()
时,系统尝试根据传入的GUID
来生成唯一的标识符(ID)。 GUID
的存在目的,是为了帮助系统区分不同的数据块,使调试信息可以绑定到具体的上下文或结构体实例上。- 虽然目前的逻辑看起来有点复杂,但这是出于对特定数据追踪的需要,暂时不能省略。
- 在执行
-
关于
unique_file_counter_string
的问题:- 此参数通常是在
record_debug_event()
时生成,用于标识事件发生的具体位置(文件 + 行号 + 唯一编号)。 - 但此机制存在一个严重的问题:记录位置是按函数调用位置计算的,而不是按实际数据上下文计算。
- 举个例子,在当前的场景下,如果
record_debug_event()
是在宏展开或统一构造器中被调用,那么所有事件都会获得相同的unique_file_counter_string
。 - 这会导致调试信息错误地指向同一位置,不能反映实际数据结构或层级。
- 此参数通常是在
-
简化的可能性:
- 鉴于现有方案中部分信息(如文件/行号)未必在所有场景中都需要,可以考虑对
record_debug_event()
做一些重构,减少依赖或改进记录机制。 - 比如,如果调试块本身已经包含上下文信息(例如路径式命名或唯一 ID),那么就不需要通过文件/行号反推来源。
- 鉴于现有方案中部分信息(如文件/行号)未必在所有场景中都需要,可以考虑对
初步结论与方向
GUID
是当前系统中确保数据唯一性的关键机制,虽然结构复杂,但仍然必要。unique_file_counter_string
的使用可能带来误导,特别是在抽象封装后统一调用的场景下。- 后续可考虑以下方向优化:
- 精简或替代
unique_file_counter_string
的记录方式; - 让调试事件记录更贴近数据结构本身而非调用代码的位置;
- 保留
GUID
的使用,但简化生成逻辑或提升自动化程度。
- 精简或替代
总结
目前调试系统中存在信息绑定不准确的潜在问题,尤其是在事件记录与源位置关联方面。需要进一步优化,使调试数据块的标识与数据本身而非调用位置绑定,从而提升系统的准确性和可靠性。同时保留关键机制如 GUID
以维持唯一性控制。
game_debug_interface.h:考虑通过 RecordDebugEvent 调用链传递 GUID
一、核心问题与目标
我们的目标是确保调试数据块(debug data block)在事件记录(record_debug_event
)中使用正确的 GUID 作为唯一标识,以便在多次程序运行或多个实例之间能够准确区分和识别调试信息。
二、问题详解
1. GUID 的必要性
- GUID 用于标识每一个调试数据块,使其在调试记录中具有全局唯一性。
- 系统依赖
record_debug_event
中通过 GUID 进行唯一标记,以识别每条记录属于哪个数据块。
2. unique_file_counter_string
的不足
- 虽然
unique_file_counter_string
尝试结合文件、行号、计数器等信息形成某种唯一标识,但:- 它仅在调用宏的位置处生成,无法反映实际的数据语义;
- 多个逻辑上独立的调试数据块可能因宏位置相同而误用相同标识。
3. 当前设计的问题
record_debug_event
在当前设计下无法自己生成可靠的 GUID,因此必须从外部传入。- 例如
timed_block
和debug_data_block
等辅助封装体,都需要传入生成好的 GUID,确保一致性。
三、解决方案与优化建议
1. 显式传入 GUID
- 每个
debug_data_block
或timed_block
的创建应带入显式构造的 GUID,而不是依赖宏展开后的位置生成。 - GUID 可通过一个统一的构造函数来生成,例如:
make_debug_guid("Renderer/Camera", __FILE__, __LINE__);
2. 将 GUID 构建逻辑封装
- 建议构造一个结构体如
DebugGUID
或DebugIdentifier
,包含:- 逻辑名称(如
"Renderer/Camera"
) - 文件名
- 行号
- 编译时间戳等可选信息
- 逻辑名称(如
- 所有事件记录函数、数据块构造器统一使用该结构体,简化参数传递。
3. 精简宏与结构设计
- 当前大量使用的 C++ 宏(如
DEBUG_DATA_BLOCK
)导致代码冗长且不易维护; - 可考虑用类封装带有析构器的方式,如:
struct DebugDataBlock {DebugDataBlock(DebugGUID guid) { OpenDebugBlock(guid); }~DebugDataBlock() { CloseDebugBlock(); } }; #define DEBUG_DATA_BLOCK(name) DebugDataBlock _dbg_##name(make_debug_guid(#name, __FILE__, __LINE__))
四、语言机制限制与哲学思考
- 当前所有的繁琐处理,根源在于 C++ 缺乏对 元编程(metaprogramming)和代码注解(annotations) 的原生支持;
- 如果语言支持编译时 AST 操作或代码插桩(instrumentation),如某些现代语言(Rust、Zig、自定义 DSL),这一切都可由编译器自动完成;
- 所以我们才会不得不手动构造 GUID、手动包装 RAII 析构器、手动展开宏来实现调试功能。
五、最终结论与实践路径
- 抛弃
unique_file_counter_string
,改为统一使用显式构造的GUID
; - 所有调试相关的事件记录必须传入该 GUID;
- 封装一个
DebugGUID
类型并统一传递; - 尽可能简化宏定义,降低耦合,提升可读性;
- 如果后续改用支持元编程的语言或构建工具,调试信息系统可以重构为声明式或注解式写法。
这样一来,调试信息的识别将更可靠,系统的扩展性与可维护性也将显著提升。
将 “Name” 添加到 UniqueFileCounterString(),移除 RecordDebugEvent 和 debug_event 结构中的 BlockName
在这段内容中,主要讨论了如何简化和优化调试信息的存储和记录。以下是具体的细节总结:
-
数据存储格式:
- 如果已经确定了数据存储的格式,可以考虑通过将数据结构调整为包含必要信息的单一字符串(例如,包含块名和数字),从而简化调试记录的内容。这将有助于减少记录时的复杂度。
- 这种方法的好处是减少了传递多个参数,特别是通过仅存储一个字符串指针,可以轻松地管理和访问这些信息。
-
简化信息记录:
- 在调试记录中,除了存储事件类型和(GUID)信息外,其它如线程ID、核心索引等细节可以被简化或省略,减少数据包的负担。
- 记录的结构保持相对简洁,避免了过多的冗余信息。虽然某些信息(如事件的特定细节)可能被省略,但这些不一定影响核心功能。
-
优化事件记录:
- 每个事件只需要记录两个基本信息:事件的类型和相关的网格。通过这样简化的信息,可以减少复杂度,提高代码的可维护性。
GUID
和type
是必须要传入的核心参数,而其他的细节(如文件名、行号等)可以选择性地存储或忽略。
-
减少编译错误和复杂性:
- 在重构过程中,命名的简化可以减少编译时的错误,尤其是在调整变量名称或删除不必要的元素时。通过保持一致的命名规则和结构,可以更容易地维护和更新代码。
-
未来可能的优化方向:
- 如果担心信息量过大,可以考虑进一步简化数据存储结构,甚至可能在未来的某个时刻通过不同的策略进行更精细的优化,比如减少传递的参数数量,或者对记录的数据进行压缩或分层。
-
调整代码简洁性:
- 为了让代码更易于输入和理解,可能会通过给一些变量(如
debug GUID
)起更简洁或更易懂的名字来提高代码的可读性。例如,将debug GUID
改为更直观的名称,避免混淆。
- 为了让代码更易于输入和理解,可能会通过给一些变量(如
总结来说,目的是通过减少不必要的数据记录和传递参数,简化调试信息的存储方式,确保代码更高效、简洁且容易维护。这种方法的核心是通过存储包含所有重要信息的单一字符串,来减少对多个复杂字段的依赖,从而简化代码逻辑并提高性能。
将 UniqueFileCounterString 重命名为 DEBUG_NAME
我们设计了一种统一的方式,用于记录调试事件,并简化调试信息的书写流程。在这个方案中,我们只需要提供一个 调试名称(debug name),而其他额外信息(如文件名、行号、宏计数器等)则由预处理宏自动补全,无需手动传递。
我们可以直接使用一个 RecordDebugEvent("调试名称")
这样的接口来记录事件,宏会自动展开并补全完整调试信息,包括:
- 源文件路径(
__FILE__
) - 代码行号(
__LINE__
) - 宏展开计数器(
__COUNTER__
) - 用户指定的调试标识字符串
宏将这些信息包装起来,形成唯一的事件标识。这样做有几个优点:
- 避免手动拼接字符串,减少错误和重复劳动。
- 事件名称具有唯一性,可准确对应具体位置。
- 在调试输出中自动携带来源信息,提升可读性。
通过这种机制,我们在需要标记事件的位置,只需提供核心语义名称,其他辅助信息完全自动生成。这种方式适用于调试计数器、性能标记、帧标记、块范围标记等多种用途。
目前我们暂且认为该方式合理,即便未来可能需要调整或重构,现在也可以先按这个思路继续推进设计和实现工作。
此外,我们暂时忽略了 int
相关细节或用法,后续根据具体需求再决定是否使用整数或其他参数形式。
检查 Counter 是否使用并移除它,简化 FRAME_MARKER()
我们分析并整理了当前调试事件记录逻辑的结构,逐步清理无用部分并梳理其真正的功能和调用流程,重点聚焦于以下几个方面:
对 counter = counter
的确认
我们发现代码中存在 counter = counter
的语句。经分析:
- 这个语句本身没有任何实际功能,既不会修改数据,也不会带来副作用。
- 它看起来像是“遗留代码”(vestigial),可能是早期开发阶段保留下来的残余内容。
- 所以我们决定将其视为无效代码,可以放心删除或忽略。
调试帧标记(Frame Marker)的实现逻辑
我们进一步确认了帧标记的记录流程:
- 本质上,帧标记就是通过
RecordDebugEvent
函数记录一个事件,并把当前帧的耗时或帧序号等“数值”记录进去。 RecordDebugEvent
是主要的记录入口,它负责将必要的调试信息存储下来。- 用户只需要调用该接口,并传入表示“帧计数”的数值。
宏调用顺序与位置的组织
在实现上,RecordDebugEvent
会被多个调试相关宏所调用,尤其是像 TimeBlock
这类宏,它们用于标记一段代码块的执行时间。
- 因为宏需要展开并调用底层实现,所以我们确保
RecordDebugEvent
函数的定义出现在宏调用语句之后。 - 这样做是为了避免编译器在宏展开前遇到未定义的问题。
TimeBlock
的后续集成
我们计划将 TimeBlock
宏接入 RecordDebugEvent
,使它能记录:
- 当前代码块的标识(如函数名或模块名)
- 执行时间(开始/结束)
- 其他辅助信息(如线程 ID、帧数、事件类型等)
最终,这些信息都会通过统一的记录接口写入调试数据中。
结论与下一步
- 清理无用代码(如
counter = counter
)。 - 统一所有调试事件记录接口,将
RecordDebugEvent
作为核心。 - 完善 TimeBlock 相关的逻辑实现,让它能够无缝接入记录系统。
- 保持代码结构清晰,确保宏定义和函数定义顺序合理,避免编译错误。
这一阶段的工作主要是为了扫清冗余,并构建稳定、统一的调试记录体系。
清理 BEGIN_BLOCK_() 和 END_BLOCK_(),并在调用 RecordDebugEvent 时添加 GUID
我们正在逐步推进调试事件记录系统的规范化,当前聚焦的核心是 调试名称(Debug Name)和事件记录的接口适配问题。以下是具体分析和调整内容:
所有 RecordDebugEvent
统一需要传入 Debug Name(调试名称)
- 现状:原有调用中,有些
RecordDebugEvent
并未显式传入名称参数。 - 调整后:现在所有的事件记录调用都必须传入一个调试名称(debug name),也就是说,必须显式指定当前事件的名称字符串。
- 目的:统一所有事件标识方式,便于后期调试数据检索和可视化。
原始结构(如 BeginBlock 等)不再自动生成名称
- 之前某些宏(如
BeginBlock
)可能会隐式拼接调试名称或通过某些机制间接生成。 - 现在调整为:调用者必须直接传入预先生成好的名称标识(例如:一个已构造的 debug GUID 或 string)。
- 所有这类使用宏的地方也必须修改,确保传入的是完整的调试名称而非之前的参数列表。
GUID
或 Name
的生成与传递方式标准化
- 每次调用
RecordDebugEvent
都必须传入一个有效的调试标识,可能是通过某种宏构造出来的 GUID。 - 所有原来依赖自动生成的地方都必须修改为手动传入,例如:
RecordDebugEvent(Thread, DebugEvent_FrameMarker, name);
这里的 name
必须在调用前就构造好,不再由内部自动拼接。
关于 counter
的进一步确认
- 之前结构中存在
counter
参数,但目前的系统中已不再使用它。 - 原因在于:早期版本中我们可能是手动插槽定位的,现在采用的是 原子自增(atomic add) 方式来分配事件槽。
- 因此,
counter
这个参数已经完全没有用途,可以清理出所有接口和调用中相关的部分。
事件插槽的分配方式已彻底更新
- 现在事件记录结构是通过原子操作自动申请槽位,无需调用者手动指定或维护计数器。
- 这不仅减少了出错概率,也提升了多线程场景下的稳定性。
结论与后续计划
- 全部事件记录接口必须显式传入调试名称,不再自动拼接。
- 清除所有关于
counter
的遗留逻辑,包括宏参数、函数参数和变量定义。 - 梳理所有使用
RecordDebugEvent
和相关宏的地方,确保调用格式符合新的规范。 - 核心目标是:简化、统一、可控,让事件记录系统更加明确、易维护、易扩展。
这一阶段工作核心是参数接口的规范化与遗留结构的清除,是为后续调试系统稳定运行打下基础。
清理 BEGIN_BLOCK(Name) 和 END_BLOCK(Name),只传递 DEBUG_NAME(Name)
我们对代码进行了大幅清理,主要目的是去除过去实验过程中遗留下来的、不再需要的冗余结构,确保系统简洁、明确、稳定。以下是详细总结:
代码中存在大量过时和无用结构
- 之前进行过多种实验性实现,积累了不少临时性或重复性的代码。
- 清理后发现这些结构完全不再需要,属于多余冗余内容,应当彻底移除。
开始和结束时间块的处理逻辑大幅简化
- 原本存在多个用于支持 begin/end block 的辅助结构。
- 现在发现其功能可以通过统一的接口和 debug name 机制简单实现。
- 因此,大部分原有复杂的支持逻辑都可以精简掉,仅保留核心功能。
当前的宏或函数只需简单传递调试名称即可
- 举例来说:
实际上只需要把名称传给BEGIN_BLOCK(debugName)
BeginBlock
宏或函数即可,不需要其他复杂逻辑。 - 同理,结束时间块、记录调试事件等函数也只需要传入一个统一的调试名称。
统一名称传递的好处
- 逻辑清晰,代码阅读和维护更加简单。
- 避免重复造轮子或冗余封装,每个事件都使用统一的名称传递格式。
- 所有调用点职责明确,只负责传入调试名称,不参与事件结构管理。
对结构简化成果的肯定
- 本次清理后,整体事件记录框架变得更纯粹、更易用。
- 再无复杂层层包装的 begin/end block 结构,事件记录功能更集中、更直接。
- 系统变得更加可靠,同时便于后期扩展与重构。
后续关注点
- 检查是否仍有旧接口调用未迁移到简化后的接口。
- 确保所有宏和函数都统一遵循传入调试名称即触发事件的模型。
- 可以考虑移除已经不再调用的辅助结构与多余文件。
这次清理不仅提升了结构的整洁度,更重要的是明确了调试事件系统的使用规范,真正实现了 高内聚、低耦合 的设计目标。
清理 timed_block 结构
我们现在进入了对时间块(time block)系统进一步精简的部分,并取得了一些关键优化成果,以下是详细总结:
不再存储 counter
是一个重要优化
- 之前的结构体中包含
counter
字段,现在已确认完全不需要。 - 移除后,该结构体不会占用栈空间,即使编译器优化不激进,也不会为其分配栈内存。
- 这使得调试系统更加轻量化,有利于在性能关键路径中使用。
初始化逻辑也进行了简化
- 以前时间块初始化时会传入多项参数,现在只保留最核心的
GUID
参数。 - 初始化只需要做一件事:调用
BeginBlock(GUID)
。 - 所有与之无关的辅助数据全部移除,保持逻辑最小化。
精简后的时间块结构如下:
- 开始块只需要传入一个调试标识(GUID),不再依赖其他复杂状态。
- 结束块无需新的标识,只需引用起始块的标识即可。
- 明确了:只有起始块需要唯一的调试名,结束块只作引用,不需额外标识。
“命名闭合块”作为清晰的标识辅助
- 结束块现在以一种明确方式命名,如
x_block
,以区分开始块。 - 这让整体语义更加直观,便于代码结构化阅读和维护。
关于 hit count 的 TODO 留存
- 曾考虑统计命中次数(hit count),但目前尚未实现。
- 暂时将 TODO 保留,未来可在系统完善后加入该功能。
- 命中统计可能在性能分析中有价值,因此需要在结束时阶段性评估其优先级。
当前设计的总体优势
- 逻辑极简:只需标记一次起始,结束只作闭合处理。
- 结构轻量:无冗余数据、无额外存储负担。
- 可维护性强:接口单一清晰,未来如需拓展也易于插入。
这一阶段的重构进一步压缩了调试系统的运行和存储开销,同时保持功能完整性,为后续性能分析和维护打下坚实基础。
清理 TIMED_BLOCK 和 TIMED_FUNCTION 宏
我们对 time_function
和相关宏定义的部分也进行了简化与重构,以下是详细的整理总结:
原有结构存在的复杂性和问题
- 原来用于时间记录的宏如
time_block
、time_function
非常繁琐,需要传入多个参数(如行号、文件名、代码块名、计数器等)。 - 实际使用时这些信息大多数并不必要,反而增加了维护和阅读成本。
- 其中有些字段(如 counter)甚至完全不再使用,属于遗留冗余。
宏的重构与清理逻辑
- 现在已有一个通用的处理函数,利用它可以简化绝大多数操作。
- 对于
time_block
或time_function
,我们只需创建一个调试名称(debug name),并传递给通用函数即可。 - 不再需要传入行号、文件名、块名等信息,全部移除。
仍需保留宏的唯一原因
- 如果希望支持同一位置多次调用的情况(例如同一函数中多次使用
time_block
),仍需在调试名后附加编号来区分。 - 因此宏不能完全消失,但已经极大简化,只保留为处理唯一编号而存在。
简化后操作方式示意
time_block("example")
→ 自动构造 debug name,并调用begin_block(debug_name)
。time_function()
→ 通过当前函数名构造 debug name,调用begin_block(debug_name)
。- 所有复杂拼接、宏参数注入(如
__LINE__
、__FILE__
)都被剔除。
优化后的好处
- 简洁易懂:新结构逻辑清晰,没有多余的信息传递。
- 可维护性强:去除了宏中大量不必要的信息拼装,更容易修改或替换。
- 通用性更高:统一使用
debug_name
接口进行记录,便于集中管理和扩展。 - 无需重复工作:不必再手动维护多个宏定义和命名逻辑。
当前状态总结
- 功能不变,依然支持时间记录与标记。
- 内部结构极大精简,移除了历史遗留代码和无用字段。
- 所有宏或函数的调用现在都转向通用的记录函数,形成单点控制入口,便于后续集中优化。
这一阶段的工作从结构、调用方式到命名策略进行了整体性重构,实现了从复杂混乱到精简统一的转变,为进一步的系统性能分析或工具化处理打下了良好基础。
先预留后面再初始化 防止段错误
相关文章:
游戏引擎学习第249天:清理调试宏
欢迎大家,让我们直接进入调试代码的改进工作 接下来,我们来看一下上次停留的位置。如果我没记错的话,上一场直播的结尾我有提到一些我想做的事情,并且在代码中留下了一个待办事项。所以也许我们今天首先做的就是解决这个问题。但…...
TwinCAT数据类型,%MX,%MD这些特殊符号
在 TwinCAT(Beckhoff PLC 编程环境)中,%MX、%MD 等符号是 IEC 61131-3 标准的地址表示法,用于直接访问 PLC 的物理 I/O 或内存区域。这些符号通常用于 变量声明 或 直接寻址,特别是在 TwinCAT 2 和 传统 PLC 编程 中较…...
力扣——20有效的括号
目录 1.题目描述: 2.算法思路: 3.代码展示: 1.题目描述: 给定一个只包括 (,),{,},[,] 的字符串 s ,判断字符串是否有效。 有效字符串需满足: 左括号必须…...
正点原子STM32H743单片机实现ADC多通道检测
目标 使用STM32CubeMX工具,配置ADC相关参数,实现在STM32H743单片机上获取ADC多通道电压值。共14个ADC引脚,ADC2有5个,ADC3有9个,全部设置单通道 ADC引脚 PF3PF4PF5PF10PC0PC2PC3PH2PH3PA3PB0PB1PA4PA5PA6 STM32cube…...
前端封装WebSocket工具n
Web API 提供的 WebSocket 类,封装一个 Socket 类 // socket.js import modal from /plugins/modal const baseURL import.meta.env.VITE_APP_BASE_WS; const EventTypes [open, close, message, error, reconnect]; const DEFAULT_CHECK_TIME 55 * 1000; // 心…...
Docker进入MySQL之后如何用sql文件初始化数据
关闭Docker-compose.yml里面所有容器 docker compose -f docker_compose.yml down后台形式开启Docker-compose.yml所有容器 docker compose -f docker_compose.yml up -d罗列出所有启动过的(包括退出过的)容器 docker ps -a进入指定容器ID内部 docke…...
Docker搜索镜像报错
科学上网最方便。。。。 尝试一: 报错处理 Error response from daemon: Get https://index.docker.io/v1/search?qmysql&n25: dial tcp 31.13.84.2:443: i/o timeout 国内从 DockerHub 拉取镜像有时会遇到困难,此时可以配置镜像加速器。Docke…...
【Unity笔记】基于距离驱动的参数映射器 InverseDistanceMapper 设计与实现
需求: 当用户距离目标位置越近,参数值越大。 可用于控制场景亮度、动画进度、交互强度等多种效果。 一、需求背景:如何让“距离”成为设计的一部分? 在虚拟现实(VR)、增强现实(AR)乃…...
【Spring AI】Java结合ollama实现大模型调用
在较新的Java版本中,编译器已经支持了接入各种AI模型工具进行开发,这篇文章我会介绍如何利用Spring AI进行大模型的调用的基础方法。 环境准备 由于这篇文章是结合ollama进行演示,所以在开始前需要先安装ollama服务,这个的具体步…...
docker制作python大模型镜像(miniconda环境),工程改造记录
**环境说明:**从系统镜像开始打造python大模型镜像,之前是人工手动装的方式,并且模型和依赖在公网中,对于离线交付环境不太友好,所以打造的离线化交付版本 Dockerfile: FROM centos:7.9 ENV PYTHONIOENCODINGutf-8 E…...
在油气地震资料积分法偏移成像中,起伏地表处理
在油气地震资料积分法偏移成像中,起伏地表情况会带来波场传播路径畸变、静校正问题以及成像精度下降等挑战。以下是处理起伏地表的常用方法和技术要点: 1. 静校正预处理 高程静校正:将地表各接收点校正到统一基准面(浮动基准面或…...
经典算法 独立任务最优调度问题
独立任务最优调度问题 题目描述 用2 台处理机A 和B 处理n 个作业。设第i 个作业交给机器A 处理时需要时间ai ,若由机器B 来处理,则需要时间bi 。由于各作业的特点和机器的性能关系,很可能对于某些i,有ai >bi,而对…...
在TensorFlow中,`Dense`和`Activation`是深度学习模型构建里常用的层
在TensorFlow中,Dense和Activation是深度学习模型构建里常用的层,下面就详细解释它们的使用语法和含义。 1. Dense层 含义 Dense层也就是全连接层,这是神经网络里最基础的层。在全连接层中,每一个输入神经元都和输出神经元相连…...
基于 Rancher 部署 Kubernetes 集群的工程实践指南
一、现状分析 在当今的云计算和容器化领域,Kubernetes(K8S)已经成为了容器编排和管理的事实标准。根据 Gartner 的数据,超过 70% 的企业在生产环境中使用 K8S 来管理容器化应用。然而,K8S 的安装和管理对于许多企业来…...
Seaborn
1. Seaborn概述:Seaborn是基于Matplotlib的Python数据可视化库,专注绘制统计图形。它简化可视化流程,提供高级接口与美观默认主题,能以少量代码实现复杂图形绘制。 2. 安装与导入:安装Seaborn可使用 pip install seabo…...
0基础FWT详解2(巩固)
FWT巩固1 FWT巩固1卡常技巧巩固习题luogu6097CF662Cluogu4221FWT巩固1 在 上篇文章 中,我们学习了 F W T FWT FWT,本文将带读者一起做几道题,巩固对 F W T FWT FWT 的使用 卡常技巧 一个常数大的 F W T FWT FWT 是非常不利于做题的,所以我们需要卡常。 作者简单总结…...
阿里云 ECS 服务器进阶指南:存储扩展、成本优化与架构设计
一、弹性存储架构:块存储深度解析与挂载实践 (一)块存储类型与技术特性 阿里云块存储作为 ECS 核心存储方案,提供三种主流类型: ESSD 云盘 性能等级:PL0/PL1/PL2/PL3,最高支持 100 万 IOPS …...
运维打铁: 存储方案全解析
文章目录 一、引言二、思维导图三、常见存储方案介绍3.1 直接附加存储(DAS,Direct Attached Storage)1. 原理2. 优缺点3. 适用场景 3.2 网络附加存储(NAS,Network Attached Storage)1. 原理2. 优缺点3. 适用…...
Semtech公司简介以及主流产品
Semtech 公司是一家美国的半导体公司,总部位于加利福尼亚州卡马里洛。以下是其简介和主流产品介绍: 公司简介 成立时间与地点:1960 年成立于加利福尼亚州纽伯里帕克。发展历程:最初为军事和航空航天公司提供零部件,1…...
flutter 专题 五十六 Google 2020开发者大会Flutter专题
由于疫情的原因,今年的Google 开发者大会 (Google Developer Summit) 在线上举行,本次大会以“代码不止”为主题,全面介绍了产品更新以及一系列面向本地开发者的技术支持内容。我比较关注的是移动开发,在本次大会上,关…...
93. 后台线程与主线程更新UI Maui例子 C#例子
在.NET MAUI开发中,多线程是常见的需求,但UI更新必须在主线程上执行。今天,我们来探讨一个简单而优雅的解决方案:MainThread.InvokeOnMainThreadAsync。 一、背景 在跨平台应用开发中,后台线程常用于执行耗时操作&am…...
5.运输层
5. 运输层 1. 概述 第2~4章依次介绍了计算机网络体系结构中的物理层、数据链路层和网络层,它们共同解决了将主机通过异构网络互联起来所面临的问题,实现了主机到主机的通信然而在计算机网络中实际进行通信的真正实体,是位于通信两端主机中的…...
ActiveMQ 可靠性保障:消息确认与重发机制(二)
ActiveMQ 重发机制 重发机制的原理与触发条件 ActiveMQ 的重发机制是确保消息可靠传输的重要手段。当消息发送到 ActiveMQ 服务器后,如果消费者由于某些原因未能成功处理消息,ActiveMQ 会依据配置的重发策略,将消息重新放入队列或主题中&am…...
Vue+tdesign t-input-number 设置长度和显示X号
一、需求 Vuetdesign t-input-number 想要设置input的maxlen和显示X号 二、实现 t-input,可以直接使用maxlength和clearable属性 <t-input v-model"value" clearable maxlength10 placeholder"请输入" clear"onClear" blur&q…...
机器学习|通过线性回归了解算法流程
1.线性回归引入 2.决策函数 3. 损失函数 4.目标函数 5.目标函数优化问题 6.过拟合 7.正则化...
两向量平行公式、向量与平面平行公式、两平面平行公式;两向量垂直公式、向量与平面垂直公式、两平面垂直公式
目录 一、两向量平行公式 二、向量与平面平行公式 三、两平面平行公式 四、两向量垂直公式 五、向量与平面垂直公式 六、两平面垂直公式 观察与总结 一、两向量平行公式 二、向量与平面平行公式 三、两平面平行公式 四、两向量垂直公式 五、向量与平…...
vscode 个性化
vscode 个性化 设置 吸顶效果 使用前使用后 设置方法 VS Code 的粘性滚动预览 - 类似于 Excel 的冻结首行 插件 代码片段分享 - CodeSnap 使用方式 CtrlShiftP输入CodeSnap 唤起插件选择代码 行内报错提示 - Error Lens 使用前使用后 VSCode Error Lens插件介绍&…...
OpenHarmony-简单的HDF驱动
学习于:https://docs.openharmony.cn/pages/v5.0/zh-cn/device-dev/driver/driver-hdf-manage.md 首先,OpenHarmony系统里的HDF(Hardware Driver Foundation)驱动框架,已经规范设备驱动的模型、设备节点的配置与统一的…...
Copilot重磅更新:引用文件夹创建Word文档
大家好,AI技术笔记为您带来一则好消息: 根据广大用户的反馈,Microsoft 365 Copilot在Word中的引用能力全面升级啦! 不管是撰写、审阅还是定稿文档,现在你可以更快、更高效地引用更多资料! ✨三大重磅改进…...
SQL Server数据库提权的几种方法——提权教程
SQL Server数据库提权的几种方法——提权教程 一、简介 在利用系统溢出漏洞没有效果的情况下,可以采用数据库进行提权。 数据库提权的前提条件: 1、服务器开启数据库服务 2、获取到最高权限用户密码 (除Access数据库外,其他数据库基本都存在数据库提权的可能) 二、使用x…...
解决在Mac上无法使用“ll”命令
在 macOS 上,ll 命令是一个常见的别名,它通常是指向 ls -l 的。但是,如果你看到 zsh: command not found: ll,这意味着你当前的 zsh 配置中没有设置 ll 作为别名。 解决方法: 1. 使用 ls -l 命令 如果只是想查看目录…...
Dockerfile最佳实践:构建高效、安全的容器镜像
一、前言 Dockerfile是一个文本文档,它包含用户可以在命令行上调用的所有指令,每一条指令构建一层镜像。在日常开发中我们常常需要自己编写Dockerfile来构建镜像,而构建一个精巧、实用且高品质的镜像对运行环境来说尤为重要。下面我们来排一排如何构建这样的镜像。 二、目…...
mac电脑pytest生成测试报告
时隔了好久再写代码,感觉我之前的积累都白费了,全部忘记了,看来每一步都有记录对于我来说才是最好的。 最近又要重新搞接口自动化,然而是在mac电脑,对于我长期使用windows的人来说真的是个考验,对此次过程…...
鸿蒙 应用开发 项目资源结构及资源访问
三层工程结构 模块分类 使用...
C#学习第20天:垃圾回收
什么是垃圾回收? 定义:垃圾回收是一种自动内存管理机制,负责回收不再使用的对象所占用的内存。目的:通过自动化内存回收,减少内存泄漏的风险,并简化开发者的工作。 垃圾回收的核心概念 1. 垃圾回收器的工…...
C#学习笔记 项目引用添加异常
这个问题出现多次了 我觉得有必要记录一下 场景 同一个解决方案下添加了多个项目 我想在单元测试项目中引用一下项目1,按照步骤:添加引用- 项目- 浏览- 在指定目录下找到项目的工程文件XXXSystem.csproj- 确定 然后就触发了异常 解决方案 首先 清理解决…...
使用模块中的`XPath`语法提取非结构化数据
想要在代码中使用Xpath进行处理,就需要模块lxml 模块安装 pip install lxml -i https://pypi.tuna.tsinghua.edu.cn/simplelxml的使用 使用lxml转化为Element对象 from lxml import etreetext <div> <ul> <li class"item-1"><a …...
复杂度和顺序表(双指针方法)
目录 目录 目录 前言: 一、时间复杂度和空间复杂度 1.1概念 1.2规则 二、顺序表 2.1静态顺序表 2.2动态顺序表 三、双指针法 四、总结 前言: 时间复杂度和空间复杂度是用于判断算法好坏的指标,程序性能的核心指标。时间复杂度主要衡…...
day006-实战练习题-参考答案
老男孩教育-99期-实战练习题 1. 你作为"老男孩教育99期云计算"新晋运维工程师,在入职首日遭遇紧急事件: "生产环境3台Web服务器突发性能告警,技术总监要求你立即完成: 快速建立故障诊断工作区收集关键系统指标分…...
批量删除OpenStack实例
在Linux终端实现批量删除OpenStack实例,支持并发删除、安全确认、重试机制、优先清理运行中实例 #!/bin/bash # # 增强版 OpenStack 删除实例脚本 # 功能:支持并发删除、安全确认、重试机制、优先清理运行中实例 # 更新:2025年4月30日 # ##…...
楼宇智能化一、二章【期末复习】
一章、楼宇概述 智能建筑的定义:以建筑物为平台,基于对各类智能化信息的综合应用,集架构、系统、应用、管理及优化组合为一体,具有感知、传输、记忆、推理、判断和决策的综合智慧能力,形成以人、建筑、环境互为协调的整合体,为人们提供安全、高效、便利及可持续发展功能…...
三生原理与西方哲学的具体对比?
AI辅助创作: 一、本体论差异 生成论与构成论的分野 三生原理以《周易》“太极生两仪,两仪生四象,四象生八卦”、《道德经》“道生一,一生二,二生三,三生万物”为基础,构建动态层级生成的宇…...
呼叫中心座席管理系统:智能升级,高效服务
在数字化转型加速的今天,客户服务体验已成为企业竞争力的核心要素。传统 呼叫中心系统 依赖硬件设备、人工操作的模式已无法满足高效、智能、灵活的现代企业需求。畅信达呼叫中心 座席管理系统 V5.0应运而生,以WEBRTC软电话接入、智能座席辅助、知识库管…...
PCB设计实战技巧宝典:从库管理到布线优化的全流程解析
知识点1【PCB设计流程】 器件 符号 封装 (3D模型 实物图 ) 流程介绍 1、如果没有需要的的库,先画库:器件,符号,封装 2、新建工程,放置器件在原理图 3、原理图转PCB 4、导出ROM和Gerber…...
微信小程序 XSS 防护知识整理
场景1:用户输入表单(如评论框) 错误做法:直接渲染未过滤的用户输入 // WXML <view>{{ userInput }}</view>// JS(用户输入了恶意内容) Page({data: { userInput: <script>alert("…...
平衡截断(Balanced Truncation)—— MTALAB 和 Python 实现
平衡截断balreal 算法原理平衡截断过程求解 HSV 为什么不使用定义而是使用 Cholesy 和SVD 分解? MATLAB 实践Python 实现 先验知识:可控性 Gramian W c W_c Wc、可观性 Gramian W o W_o Wo 以及 Hankel 奇异值(HSV) σ i \s…...
机器手电机驱动器小体积解决方案
市场背景 随着工业4.0与人工智能技术的深度融合,智能机器人正加速渗透至医疗、物流、制造及服务等核心领域。据行业分析显示,2023年全球协作机器人市场规模同比增长23%,其中高精度关节驱动与小型化硬件设计成为技术迭代的关键需求。然而&…...
(数智化)采购管理系统平台开发费用
随着招标采购数智化升级加速,采购管理系统平台开发费用成为企业关注的焦点——从几十万到几百万不等,那么开发成本差异的背后藏着怎样的技术逻辑与价值密码呢?采购管理系统研发商郑州信源信息技术股份有限公司根据行业特点及客户实际实践总结…...
K8S Secret 快速开始
一、什么是 Secret? Kubernetes(K8s)中的 Secret 是一种用于存储和管理敏感信息(如密码、令牌、证书、API 密钥等)的资源对象。它避免了将敏感数据明文写入配置文件、镜像或代码中,提供了一种更安全的方式…...
TEN:开启实时语音交互的下一代AI Agent引擎
在AI技术飞速发展的今天,语音交互正成为人机交互的重要方式。传统的文本对话已无法满足用户对自然、高效沟通的需求,而TEN开源框架的出现,为开发者提供了构建超低延迟、可听可说的AI Agent的终极解决方案。 一、TEN的核心优势 超低延迟实时交…...