您的位置:首页 > 健康 > 养生 > 网站推广软件排名_富阳房产网_爱奇艺科技有限公司_百度搜索链接

网站推广软件排名_富阳房产网_爱奇艺科技有限公司_百度搜索链接

2025/4/15 21:54:30 来源:https://blog.csdn.net/TM1695648164/article/details/147071364  浏览:    关键词:网站推广软件排名_富阳房产网_爱奇艺科技有限公司_百度搜索链接
网站推广软件排名_富阳房产网_爱奇艺科技有限公司_百度搜索链接

调整椅子α

昨天,我们实现了将数据输出到调试流中的功能,之前的调试流大多只包含性能分析数据,而现在我们可以将任意数据放入调试流中。

完成这个功能后,我们接下来要做的是收集这些数据并显示出来,这样我们就能有一个方便的方式来展示我们想放入调试流的任何内容。因此,今天我要开始处理这个问题。

昨天我们已经做到了能够开始实现这个目标的地步,今天我们就着手处理这个问题。但在开始之前,我得调整一下我的椅子,显然它没有调好,大家都知道,没人想坐在不合适的椅子上。

回顾我们上次停留的地方

目前一切都在正常运行,之前的问题都已解决,系统正在按照预期工作。但虽然已经收集到了一些数据,比如一些对象信息和实体信息,这些信息还没有被有效地使用,数据只是简单地被推送到流中,并没有做进一步的处理。因此,接下来的目标是将这些信息展示出来,以便可以查看和分析。

最近几天的代码工作主要集中在调试上,整个过程非常复杂,涉及到许多微妙的决策,这些决策非常难以做对,整个过程是一个反复调整和改进的过程。如果没有经过多次迭代,很可能最终得到的代码质量并不好,存在许多问题。

很多人可能会选择不进行这些迭代和改进,直接使用代码,结果往往会得到一些有缺陷的代码,这对项目来说并不好,甚至可能会导致后期问题的积累。因此,在开发过程中,要确保花足够的时间打磨代码的每个细节,直到系统能够顺利完成预定的功能,并且能够高效地运行,而不是在使用过程中需要付出大量的额外努力或操作。

许多调试系统最终会变得非常复杂,使用起来需要很大的开销,或者工作流程很繁琐,这样就偏离了调试系统设计的初衷,导致其价值大打折扣。因此,设计调试系统时必须保持简洁和高效,避免过度复杂化。

game_debug.cpp: 实现各种 DebugEvents

我们现在要做的是将数据输出到调试流中。之前,调试流中主要存储了性能分析数据,而现在我们可以将任意数据放入其中。接下来,我们的目标是收集这些数据,并在调试代码中以一种方便的方式显示出来,就像我们之前处理性能分析数据一样。我们已经完成了数据输出的部分,今天的任务是将这些数据整理好并显示出来。

在调试协调部分,我们需要做的工作和处理性能分析数据时类似。首先,当遇到一个“打开的块”(open block)时,我们会分配一个新的块,这个块将用来存储数据。接下来,我们会将相关信息填充到这个数据块中。

有趣的是,大部分代码可以在处理这两种数据时共享,因为它们的结构基本相同。唯一的区别在于我们处理的是“代码块”和“数据块”,它们在链式结构上稍有不同。具体来说,代码块和数据块会通过链式结构连接,我们只需确保在正确的块中插入数据。

因此,我们可以使代码更加自动化,避免每次都手动编写相似的代码。比如,当打开一个调试块时,我们可以自动分配一个新的数据块,并通过链式结构将它连接起来。我们可以将这个过程自动化,以减少手动编写重复代码的工作。

在调试时,我们会为每个调试记录分配一个“源代码”,记录下当前的帧索引和事件。虽然代码块和数据块有所不同,但这些基本的步骤可以保持一致。我们会先处理调试记录的事件,确保它们符合预期,最后再处理块的关闭操作。

至于调试块的结束部分,处理方法也类似,但由于内部处理可能有所不同,因此我们可能需要在不同的地方进行调整。不过,整体结构是一样的,我们希望尽量自动化处理过程,避免手动操作。

总的来说,我们的目标是每当遇到一个打开的数据块时,自动进行处理,确保数据和事件的匹配。这包括获取匹配的块,检查事件是否匹配,并根据需要进行调整。这样就能保证整个调试过程更加高效和自动化。
在这里插入图片描述

game_debug.cpp: 引入 EventsMatch

我们在处理事件匹配逻辑时,遇到了一个可以轻松处理的案例。这个逻辑本质上类似于一个内联规则,当事件满足某些条件时,系统会执行相应的操作。

我们目前在调试事件的阶段,针对的是开发环境中的 debug 事件 B。在这个过程中,我们的目标是判断事件是否相同。具体地,我们想确认以下条件是否同时成立:

  • 相同的 A 出现多次;
  • 相同的 C 出现多次;
  • 相同的 B 出现。

这里本不需要使用 if 语句的判断逻辑,那段代码可能是之前复制粘贴时留下来的残余部分。

为了判断事件是否匹配,可以直接比较事件是否相等。如果事件是相等的,就说明它们匹配。

我们当前的做法是,当事件匹配时,即我们定义的 debug 事件满足条件时,就执行相应逻辑。我们可以进一步判断 debug 事件中是否包含某些特定属性,比如 platform(平台)或 flatworm(可能是某种特定标识或字段名),从而确认是否继续处理。

另外,我们正在考虑如何命名匹配逻辑,是用“匹配”这个词还是继续用“事件”。这部分仍在进一步探索中。总的来说,整个流程是围绕 debug 事件进行的条件判断和事件匹配,目的是提高事件处理的准确性与清晰性。
在这里插入图片描述

game_platform.h: 调整 debug_event

我们在处理 debug 事件时,发现其中包含了很多信息。最初我们尝试使用事件是否“相等”来判断是否匹配,但很快意识到这并不准确。实际情况更接近一种“模糊匹配”,也就是说,事件并不需要完全相同,而是需要某些关键字段或内容对齐、符合预期。

因此,“events match” 这个说法更加贴切,它强调的是匹配的逻辑而不是完全相等。匹配的核心是:某些字段的值需要在结构上或逻辑上保持一致。

其中,有一个部分显得有些特殊 —— 就是关于线程的信息,特别是 thread id 和与之相关的一些后续数据,比如 core 或 next。我们认为至少 thread id 是必须被提取和保留的,因为它在整个匹配流程中扮演了关键角色。thread id 是在多个不同类型的事件中都需要统一使用的字段,它属于一种全局上下文信息,不能只放在事件数据内部,而需要在结构层面被“提升”出来。

因此,我们考虑将 debug 事件数据分为两部分:一部分是通用的数据包(data package),另一部分是额外的线程标识信息。这样做可以使匹配逻辑在面对不同事件结构时更具一致性和可扩展性。

与此同时,这也暴露出一个问题:我们当前还没有在游戏平台模块中获取或提取这些必要的线程信息。查看我们用于记录 debug 事件的逻辑,比如和 tc thread id 相关的部分,目前这些关键字段似乎没有被正确采集或使用。

所以接下来需要做的几件事包括:

  • 明确 thread id 是必须保留的核心字段;
  • 重新设计事件结构,把 thread id 从数据包中抽离出来,作为顶层字段;
  • 更新事件记录逻辑,确保从平台中正确获取如 tc thread id 这类信息;
  • 在匹配逻辑中加入 thread id 的比较或匹配条件,以提高事件匹配的准确性和可用性。

总的来说,整个流程需要围绕模糊匹配、线程上下文抽离、事件结构调整和平台数据获取四个方面进行重构和优化。

在这里插入图片描述

game_platform.h: 用 RecordDebugEvent 替换 RecordDebugEventCommon

在回顾公共部分的处理方式时,我们逐渐意识到,其实这一套逻辑现在已经可以适用于所有情况。也就是说,无需再区分“普通版本”和“通用版本”,因为从现在起,每次事件记录几乎都会使用通用方式处理。

基于这个认识,我们可以简化架构,将“common”和“regular”的版本合并为一个统一流程,也就是说,原先用于处理通用情况的版本将变成唯一的标准实现。这样一来,原有的“common”版本就不再需要单独存在,可以直接移除。

此外,这些通用逻辑下的字段也将会在每一次事件处理过程中被统一设置,不再需要根据不同情况做分支判断,这样使整个事件处理流程更加一致和简洁。

因此,所有使用记录操作的地方,都将统一采用这种简化后的结构,比如将原来的记录方法统一为 record_debug_events。这样可以确保逻辑一致性,同时减少冗余代码和维护成本。

总结:

  • 公共处理逻辑现在适用于所有场景,不再需要区分版本;
  • 原有的“common”部分可以移除;
  • 所有字段在每一次事件处理时都会被设置;
  • 所有记录方法统一为一种新的、标准化的形式;
  • 架构更简洁,逻辑更清晰,便于后续维护和扩展。
    在这里插入图片描述

game_debug.cpp: 调整这些 DebugEvents

在当前的结构下,处理逻辑已经变得相当清晰并且合理。我们对事件的匹配机制进行了简化,现在的做法是直接判断“opening event”是否与目标事件匹配,如果匹配,就继续执行后续逻辑。

这种判断逻辑(events match)已经可以在多个地方共享使用,无需为不同情况编写不同的处理分支,也就是说,这种匹配方式已经具有通用性,不再需要额外的特殊处理。

在实现上,我们可以在多个处理路径中复用 if events match 这部分逻辑,不仅避免了冗余,而且让整个判断过程统一标准。这样一来,流程的连续性也得到了保证。

整体上,这样的结构基本上就完成了核心部分的重构:

  • 判断事件是否匹配成为主导逻辑;
  • 判断逻辑可以复用,无需区分不同路径;
  • 一旦匹配成功,就可以立即进入下一阶段处理;
  • 逻辑结构清晰、简洁,便于维护和扩展;
  • 所有相关模块可以共用同一套匹配标准,保持一致性。

简而言之,事件匹配机制得到了标准化和统一化处理,结构设计上也达到了预期效果。
在这里插入图片描述

game_debug.cpp: 让 DeallocateOpenDebugBlock 使用 **FirstOpenBlock

在处理 reallocate open debug work 的过程中,我们发现整个流程与之前的逻辑非常类似,因此完全可以将其统一,并复用已有的处理方式。

在分析其工作原理时,我们意识到这个过程其实可以很好地承担起线程管理和链表结构的维护。这部分逻辑本身具有一致性,所以可以通过封装或共用的方式来简化处理。

具体而言,在处理 open data block(或称为 open code block)时,我们只需要确保某些关键节点的管理是正确的。比如,“first open block”这个节点是当前逻辑的核心,我们希望它能够始终等于当前上下文中的 parent 节点。

在 reallocate 的过程中,当我们执行分配释放操作时,目标 block 实际上总是顶部的那个,即“first open code block”,因为当处理结束时,弹出的永远是栈顶的 block。我们确认了这一点之后,就可以将逻辑进行统一简化。

于是我们提出了这样的结构:

  1. 明确 free block:将 free_block 设为当前的 first_open_block
  2. 维护链关系:让 free_block.next 指向当前的 free_blocks,然后更新 free_blocks 为新的 free_block
  3. 释放后的结构维护:将 first_open_block 设置为被释放 block 的 parent,从而维持正确的结构层次;
  4. 逻辑复用:在多个分支中都使用这套通用逻辑,保持一致性。

这种做法带来的好处是:

  • 逻辑统一,处理路径清晰,易于维护;
  • 不再需要多余的判断和特例处理;
  • 节点的分配与释放变得可控且标准化;
  • 栈结构管理得以简化,使 debug 事件的生命周期管理更加可靠;
  • 代码结构更加干净,减少重复。

总结:

  • first_open_block 成为核心控制节点;
  • 释放和重分配的逻辑统一为一套链式操作;
  • 线程管理和事件结构绑定在一起,通过 block 的 parent 实现回溯;
  • 所有相关模块可复用该逻辑,不再需要特殊处理。

整体上,这是一种高度统一、简化和结构清晰的事件块管理机制。
在这里插入图片描述

game_debug.cpp: 修复编译错误

在当前的调试和实现过程中,我们准备继续推进这部分逻辑的整合,并通过实际运行验证方案的可行性。为此,接下来主要进行了一些代码修复和编译错误的处理。

在修复编译错误的过程中,发现当前存在数组索引和多重定义的问题。初步判断是在定义 record_debug_events 的时候出现了失误,造成了重复定义的情况。这类问题通常是因为代码块被多次包含或调用,且没有做足够的作用域隔离或定义保护。

具体地,像 end_block 这样的内容,在没有合适的封装情况下被多次写入代码流中,如果它被多个地方引用,就会导致符号在编译时出现重复定义的问题。

为了解决这个问题,必须在相关代码外围加上适当的作用域封装(例如局部代码块或命名空间处理),防止内部变量和定义泄漏到外部作用域中。通过将这些定义包裹在独立的作用域结构中,可以有效避免变量冲突或污染其他模块的命名空间。

总结当前工作与调整:

  • 继续推进线程管理和事件结构统一的重构;
  • 编译过程中出现了数组索引及符号重复定义的问题;
  • 原因是缺乏作用域封装导致多次调用时变量污染;
  • 解决方案是使用局部作用域或包裹结构对 record_debug_events 及其相关定义进行隔离;
  • 通过修复这些问题,确保事件记录逻辑可重复调用且不会引起命名冲突;
  • 当前目标是先完成编译并验证整体流程的正确性,之后再做功能性测试与优化。

整体上,工作重心从逻辑设计过渡到了实际代码维护和调试阶段,目标是保证结构清晰的同时,提高系统的健壮性和可维护性。
在这里插入图片描述

运行游戏并查看相同的结果

当前这一阶段的工作已经顺利完成,整体结构调整和修复都已就绪。从理论上来说,尽管我们对内部逻辑进行了简化和结构上的统一,但这些修改不会对最终的功能表现产生任何变化。

也就是说,在执行这些优化之后,最终在使用过程中,例如查看性能分析(profile)的视图时,结果应当与之前保持一致,不会出现任何预期之外的差异或错误。从目前的验证情况来看,实际输出与原有预期是相符的,这说明结构调整在不破坏功能的前提下提升了代码的清晰度和一致性。

整体总结如下:

  • 所有结构调整和作用域封装已完成;
  • 理论上行为应保持一致,无需担心功能回退;
  • 预期结果是“无变化”,包括性能视图等关键功能;
  • 当前验证结果表明修改是正确的,输出与原有一致;
  • 整体流程达到预期目标,代码结构更清晰,维护性更高。

后续将继续观察并验证边界情况,确保整个系统在各类场景下都能稳定运行。

game_debug_variables.h: 动态生成 DEBUGAddVariables

当前阶段的任务主要是将调试数据结构进一步完善,使调试系统可以更好地解析并展示数据。具体目标是将已有的变量系统功能整合到新的 debug block 机制中,实现变量信息的动态填充与可视化。

目前调试块系统已经具备结构,下一步是将变量写入这些调试块。原本测试中使用的代码是静态定义的,并不符合实际需求,因此需要将这部分逻辑迁移到动态流程中去,即在运行过程中根据实际事件生成调试变量并添加到当前打开的调试块中。

具体过程如下:

  • 利用已有的变量系统创建变量节点;
  • 在每次打开 debug block 时,动态创建变量组(group);
  • 使用 begin_variable_groupend_variable_group 来组织变量;
  • 在添加变量时,调用对应的添加接口(如添加 int32, real32, v2, v3 等);
  • 所有变量添加操作都定位到当前线程对应的 first_open_data_block 中;
  • 变量名称通过事件中记录的 debug_record 获取,即从事件中读取记录索引,然后解析出 block_name 用作变量显示名;
  • 这实现了从事件中提取数据并通过变量系统展示的完整流程;
  • 类型支持方面,目前已确认支持包括 int32, real32, v2, v3,并可能扩展支持 rect2, rect3 等更复杂结构;
  • 同时通过逐步实现的方式,逐个填充每种类型,确保逻辑清晰、错误可控。

当前的问题点包括对部分类型支持情况不明确(如 rect2, rect3),需要进一步确认或补全类型处理逻辑。同时强调了采用递进的开发方式,以便在结构尚未完全整理的情况下保持清晰的演进路径。

总的来说:

  • 正在将静态调试测试代码重构为动态、可扩展的变量记录系统;
  • 每个调试事件将实时生成并插入对应类型的变量;
  • 利用已有的 debug_record 信息自动命名变量,确保结构可读性;
  • 初步支持基础数据类型,后续将持续扩展;
  • 所有操作围绕当前线程的 first_open_data_block 进行组织;
  • 最终目标是形成完整可视化的调试信息树,提升调试效率和准确性。

这为后续调试工具的可用性和可维护性打下坚实的基础。
在这里插入图片描述

game_debug.cpp: 实现变量添加功能

我们目前的目标是实现真正用于添加调试变量的逻辑。为此,我们首先需要明确这些变量在内存中应该“住”在哪里。考虑到调试数据的时序性,我们决定将变量的组织方式与帧(frame)绑定,即每一帧都拥有自己的一套调试变量结构,从而在帧生命周期结束时自动清理,避免污染后续帧的数据。

具体思路如下:


基础结构调整

  • 我们已经有调试帧(debug_frame)的结构存在,其中包含了例如 Regions 这样的字段来存储调试数据。
  • 目前调试变量的根组(root group)是全局的,只用于测试,现在计划改为每帧都拥有一个独立的 root group。
  • 这个 root group 会在每次创建新的调试帧时自动分配并绑定到当前帧中。

在帧创建时初始化 root group

  • 在调试系统中,每当生成新的帧记录时(collate frame),我们会创建新的 debug_frame。
  • 此时需要修改初始化逻辑,在新帧创建的同时,顺带构建并初始化对应的 root group。
  • root group 初始化可能不是设置为零,而是通过变量系统动态分配一个新的变量组结构体。

整合变量系统逻辑

  • 原先用于测试的调试变量创建代码存放在一个孤立的文件中,计划将其彻底移除,并将其逻辑整合进现在的帧协作系统中。
  • 调试变量系统将不再依赖于“外部静态测试代码”,而是完全嵌入到调试事件的处理流程中。
  • 变量添加的流程将通过遍历调试事件,依次添加到当前帧的 root group 中。

接下来要做的事情

  • 搭建变量系统与调试帧之间的连接通道:每帧都有一个 root group,并支持变量在其下注册;
  • 移除旧的变量测试代码文件,完全转向新逻辑;
  • 调整 debug_variable 的分配策略,让其基于当前帧的小内存区域(region)分配,而不是统一的调试全局空间;
  • 在调试数据整理时,通过事件中的内容动态生成 debug_variable,挂载到当前帧的变量组上。

最终,我们将拥有一个更清晰、更模块化的调试系统架构,其中调试变量按帧组织、按需添加、按帧生命周期释放。这种结构更适合分析时序相关的数据,调试也将更加高效、精准和整洁。
在这里插入图片描述

game_debug.cpp: 让 DEBUGBeginVariableGroup 使用 DebugState

我们现在需要在调试系统中实现每一帧都自动生成变量组(variable group)的逻辑。这个变量组将作为每帧调试数据的根节点(root group),用于组织和收集该帧中记录的所有调试变量。


核心思路

  • 当开始处理一帧数据时(即进行帧的相关性整理/correlation),会自动创建该帧的 root variable group;
  • 原先 BeginVariableGroup 这种逻辑依赖于上下文 context,而现在只需要知道父节点即可,因此可以简化;
  • 我们可以直接将 debug_state 作为参数传入,以此为基础构建 root group;
  • 实际添加变量的逻辑先暂时跳过不做,但 group 的结构已经要搭好;
  • 添加 root group 本质就是将该 group 初始化好,并挂接到当前帧下,这样整个变量树就有了基础结构。

内存分配与使用

  • 原来的变量是通过 debug_arena 分配的,但现在不再使用调试系统全局空间;
  • 为了让变量的生命周期跟随每一帧,现在我们改为使用 collation_arena 进行分配;
  • collation_arena 是与当前帧关联的内存区域,用于暂存本帧所有调试数据,便于生命周期管理;
  • 所以在 DebugAddVariable 这样的函数内部,我们需要将分配逻辑从使用 debug_arena 改为使用当前帧对应的 collation_arena

总体目标

  • 每一帧都会自动生成一个 root variable group;
  • 所有变量添加都基于该 root group 进行组织;
  • 所有内存使用都依赖于帧专属的 collation_arena,保证清理时机准确;
  • 此外,我们不再手动管理上下文,而是通过帧自身的结构自动完成关联;

当前实现阶段

目前我们专注于基础结构构建部分,即:

  • 在帧创建时,自动调用创建 root group 的逻辑;
  • 简化 BeginVariableGroup 接口,使其仅基于父节点即可运作;
  • 调试变量创建相关的调用先留空,但整个系统接口已经梳理清晰,为后续填充做好准备。

通过以上调整,我们的调试系统将从之前“静态、全局、不清晰”的变量组织方式,转变为“动态、按帧划分、结构清晰”的方式。这不仅提升调试数据的可管理性,也大大增强了数据与时序的对应性,为进一步的数据可视化与调试分析打下良好基础。
在这里插入图片描述

game_debug.cpp: 将 DEBUGAddVariable 重命名为 CollateCreateVariable,将 DEBUGBeginVariableGroup 重命名为 CollateCreateVariableGroup

我们目前的目标是实现调试系统中,按帧组织的变量组管理,并且在新的实现中继续保持一定的兼容性,以便逐步去掉旧的实现方式。具体操作和思路如下:


具体操作

  • 目前,我们决定将新的调试变量管理系统命名为 “CollateCreateVariable”,用于与现有的管理系统并行工作,直到可以安全地去掉旧的代码;
  • 我们将继续使用字符串的复制操作,这样做的目的是确保即使在动态代码重载的情况下,字符串也能保持一致性和稳定性,因为动态重载可能会引发某些问题,如果不保存字符串副本的话,可能会导致不可预料的行为;
  • 在新的系统中,我们实现了 BeginVariableGroup,即开始一个新的变量组,但这次我们直接针对 “Collate”(关联)来创建变量组,简化了处理流程;
  • 我们的目标是能够将一个变量添加到已经创建的变量组中,虽然现在这个部分还没有完全实现,但基本的框架和结构已经搭建好了。

当前进展

  • 在新的实现中,变量组的创建是基于帧的关联(collation),这意味着每一帧的调试信息会在该帧的上下文中进行管理;
  • 为了保持系统的鲁棒性,我们仍然保持对字符串的复制操作,避免在动态代码重载时出现数据不一致的问题;
  • 我们目前已经完成了创建变量组的过程,接下来需要的是将变量添加到这些组中的功能。

总结

新的调试变量管理系统已经初步构建完成,并能够按帧管理变量组。虽然还没有完全去掉旧的实现方式,但目前新系统和旧系统能够并行运行,确保逐步过渡到新的实现。同时,保持字符串的复制操作也是为了确保在动态重载情况下系统的稳定性。接下来,我们将重点实现变量添加到组中的功能,以完善整个调试变量的管理流程。
在这里插入图片描述

game_debug.cpp: 引入 CollateAddVariableGroup

我们正在实现一个新的调试系统,涉及到变量组的管理和调试信息的处理。具体的工作流程和设计思路如下:


实现步骤和进展:

  1. 新的变量组命名与添加:

    • 我们使用了一个名为 “CollateAddVariableGroup” 的变量组管理方式,它负责处理变量组的创建和添加功能。该系统能够自动将变量添加到指定的组中,这个过程中,唯一的不同是变量会被放入 "CollateArena"中;
    • 变量组创建时,当前的目标是为每个帧创建一个变量组,而我们当前为方便起见,使用了“帧”作为默认命名。未来可能会根据实际情况调整命名方式,使用更符合实际帧的名称。
  2. 帧名称处理:

    • 在创建变量组时,需要为每个变量组命名。当前,我们使用了“帧”作为名称,但这只是一个临时的解决方案。未来,可能会进一步改进,使用更具体的帧名称,确保系统能够更精确地识别和管理每个变量组。
  3. 添加变量到组:

    • 通过对变量组的管理,能够灵活地将变量添加到相应的组中,确保变量按照帧来组织管理。这个操作的重点是与当前的调试框架的集成。

当前的设计与思考:

  • 变量组的管理:我们把变量组的管理与帧的关联结合,确保每个帧的变量都能正确地放入相应的变量组。这对于后期的调试和变量管理非常重要;
  • 帧命名的灵活性:现在的帧名称可能不完全符合实际的需要,未来可能会根据需要调整为帧的实际名称。现在的处理方式更多是为了方便开发,确保能够逐步完善系统。

总结:

新的调试变量系统已经逐步实现了基本的变量组管理功能,能够为每个帧创建和管理变量组。目前使用的命名方式为“帧”,但未来可能会进行调整。通过在关联框架中管理变量组,系统能够确保变量的正确组织和管理,为后续的调试功能打下基础。
在这里插入图片描述

game_debug.cpp: 启动 DEBUGAddRootGroup

在实现数据块(data block)相关功能时,工作流程如下:


数据块的处理和组管理:

  1. 数据块的开闭处理:

    • 每当打开和关闭数据块时,系统会检查是否有一个关联的 “colletion frame”(汇聚帧)。如果存在这个帧,就意味着该帧下有一个“根组” (root group),并且我们可以向该组中添加数据。
    • 当一个数据块被打开时,它会被添加到相应的根组中,这样数据就能按照帧进行正确的组织和管理。
  2. 根组的使用:

    • 每次打开数据块时,如果帧存在,就会为该帧创建一个根组。然后,所有的调试数据(例如变量)都会被添加到该根组中,以便进行进一步处理和查看。
  3. 管理调试变量:

    • 每个数据块在被打开时,如果存在调试变量,那么这些变量将会被分配到一个特定的组内。根组管理了这些变量,并且在数据块处理完成时,它会确保数据被正确地添加到相应的组里。
    • 这里的重点是,当数据块打开时,首先会检查是否有调试变量。如果有,这些变量就会被组织并添加到相应的组内,以便后续处理。
  4. 框架和组的配合:

    • 系统确保每当一个数据块被打开时,都能够在相应的根组中管理所有相关数据。只有在确保帧和组都存在的情况下,才会继续执行变量的添加过程。

总结:

通过这个流程,系统能够灵活地管理每个数据块的开闭操作,并根据每个帧的存在与否,确定是否为其创建一个根组。在数据块处理的过程中,相关的调试变量会被正确地添加到这些组中,以确保调试信息的组织和管理。这样设计能够有效地支持调试过程中的数据追踪和问题定位。
在这里插入图片描述

game_debug.cpp: 调整 DebugEvent_OpenDataBlock

在处理数据块(data block)时,系统的工作流程如下:

打开数据块的流程:

  1. 创建变量组:

    • 当打开数据块时,首先会创建一个变量组,并将该组与当前数据块关联。变量组的名称通常是与数据块名称相同的块名,这样可以明确标识该组与数据块的关系。
    • 这个变量组将作为当前数据块的父级组,所有的调试数据都会被添加到这个组中。
  2. 检查是否已有父级数据块:

    • 在处理每个数据块时,系统会检查当前是否已经有一个已打开的数据块(即一个父级数据块)。如果有,则需要将当前数据块的变量添加到这个父级组中,而不是创建一个新的组。
    • 如果有父级数据块,当前数据块的变量将被添加到父级数据块的组中,从而形成一个层级结构。
  3. 没有父级数据块时的处理:

    • 如果没有父级数据块,系统会将当前数据块的变量直接添加到当前汇聚帧(collected frame)的根组中。这是默认的行为,因为没有父级数据块可用时,根组会作为容器来存放数据块的变量。
  4. 变量添加逻辑:

    • 每次打开数据块时,都会通过 collate add variable to group(汇聚变量添加到组)将变量添加到合适的组中。如果存在父级数据块,则添加到父级组,否则直接添加到根组中。
    • 这样就保证了数据块的变量能够根据数据块的父级关系进行层次化管理。

关闭数据块的流程:

  • 在关闭数据块时,通常不需要做额外的操作,因为变量已经根据其所在的父级数据块或根组进行了处理。
  • 系统会自动清理和整理这些变量的归属,确保所有的变量都正确地被归档到相应的组中。

总结:

在打开数据块时,系统会通过创建一个变量组并根据是否存在父级数据块来决定变量的存放位置。若存在父级数据块,变量会被添加到父级组中;若没有父级数据块,则变量将被添加到汇聚帧的根组中。整个流程自动化处理了数据块变量的组织和归属,确保数据在不同层级的组中正确管理。
在这里插入图片描述

在这里插入图片描述

game_debug.cpp: 引入 CollateCreateGroupedVariable

为了实现上述功能,主要步骤如下:

1. 创建变量:

  • 在实现过程中,首先需要创建一个新的变量。由于已经有了所需的参数,创建变量的过程非常直接。系统会用已有的参数生成一个变量。

2. 添加变量到组:

  • 创建完变量后,下一步是将该变量添加到相应的组中。此时,系统会进行检查,确保是否有已打开的数据块(数据块通常在“变量组”中)。如果存在数据块(或父级组),则会将该变量添加到当前的组中。

3. 验证存在数据块:

  • 在添加变量之前,需要确认是否已经存在数据块。通过验证,如果没有打开的数据块或者组,这一过程将中断并给出错误提示。为此,加入了一个断言(assert)机制:如果没有数据块,系统将触发错误,避免在没有数据块的情况下进行不必要的操作。

4. 避免潜在问题:

  • 在进一步的实现中,如果没有合适的父级组(即没有打开的数据块),则无法进行添加变量的操作。通过提前断言这两个条件,系统确保不会出现意外错误。

5. 简化流程:

  • 该过程相对简单,主要是在已存在的系统基础上稍作修改。只需要在创建变量后,确保其正确地添加到相应的组内即可。整个过程没有太多复杂的操作,系统通过检查和验证,确保操作的正确性。

总结:

通过创建变量并将其添加到相应的组中,系统会在验证是否有父级数据块的基础上,确保变量的归属。使用断言机制进一步确保操作的安全性,避免在没有父级数据块的情况下进行操作。
在这里插入图片描述

调试器: 查看传递到 DebugBlock 的内容

在这一过程中,出现了一些问题,导致程序崩溃,明显是因为空指针引用的错误。这表明在某个地方,我们对传递数据的假设可能有误,或者某些变量的状态没有按照预期进行更新。

问题诊断:

  1. 空指针引用:

    • 程序崩溃的原因是某个变量的值为 null,即某个对象没有正确初始化或赋值。具体来说,可能是因为父级组或者 collation frame 的组没有正确设置,导致访问时出现空指针错误。
  2. 追踪变量传递:

    • 在调试过程中,发现父级组(即 group)为 null,这表明在传递变量时,父级组未正确设置。这需要检查传递给函数的数据,以确保所有数据在进入函数时已经正确初始化。
    • 在这个过程中,发现问题可能出现在对 debug block 或者 collation frame 的处理上。通过调试,确认 debug state 中的 root groupnull,也就意味着该变量在此时未正确初始化。
  3. 根本原因:

    • 错误的核心在于某些对象未按预期初始化,导致在访问时无法找到对应的组或父级。特别是在处理数据块时,系统没有正确识别父级或者根组,进而引发了后续的空指针问题。

调试过程:

  1. 检查传递的变量:

    • 首先需要查看变量是否被正确传递,并确保数据在进入某个函数或模块时已经完全准备好。使用 thread first 和其他调试工具检查变量值,发现 parent 为空,表明在数据传递过程中某个环节发生了问题。
  2. 验证根组的状态:

    • 需要仔细检查 debug state 中的 root group,确保它在进入后续处理之前已经被正确初始化。如果根组为 null,说明程序尚未分配或创建该组,导致后续操作出现问题。
  3. 问题分析:

    • 程序在某个阶段应当创建和初始化根组,但由于某些逻辑错误,根组仍然是空的。这个问题需要通过代码检查和进一步调试来确认,确保在所有必要时刻根组都已经正确设置。

解决方案:

  1. 确保初始化顺序:

    • 必须保证在任何尝试访问根组之前,根组已被创建并正确初始化。可以在代码中加入更多的检查点,确保对象在使用前已经准备好。
  2. 添加错误检查:

    • 在可能的空指针位置,添加额外的错误检查和断言,确保在访问对象之前,所有的依赖都已经初始化完毕。
  3. 修复数据传递:

    • 确保数据传递的完整性,尤其是在涉及父级组和 collation frame 的部分,避免因未传递正确的组信息而引发崩溃。

总结:

通过调试可以发现,程序出现问题的根本原因是对父级组或根组的处理不当,导致空指针错误。在进一步的修复中,需要确保所有必要的对象在使用前已经正确初始化,且数据传递的过程是完整的。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

game_debug.cpp: 修正拼写错误

在调试过程中,尽管在创建变量组时看到了协同框架(collation frame)能够正确生成一个变量组,但仍然遇到了一些问题。

目前遇到的问题:

  1. 拼写错误:

    • 在代码中,有一个小的拼写错误(例如 “CollationFrame->RootGroup”),这需要被修正,以避免不必要的错误。
  2. 全局规则组的处理:

    • 现在有一个 “rootgroup”(全局规则组)存在,虽然目前不确定是否仍然需要这个组,但它似乎是多余的,可能已经不再需要。因此,需要在后续工作中逐步移除它,避免它继续占用资源和影响逻辑。
  3. 逐步清理:

    • 在解决这些问题时,需要逐步进行清理和优化。首先,可以先去掉这些不再需要的组,并逐步调整代码逻辑,确保框架的其他部分能够正常工作。

解决方案:

  • 修正拼写错误: 修正代码中的拼写错误,确保不会影响到其他部分。
  • 移除不必要的组: 如果 “RootGroup” 不再需要,应当在后续的代码中移除该组,以简化系统的管理和维护。
  • 逐步调整代码: 在进行移除操作时,确保代码的其他部分不会受到影响,逐步进行清理和优化。

总结:目前主要问题是一些细节上的拼写错误和全局规则组的处理,可以通过修正这些问题,逐步清理代码,确保代码逻辑简洁高效。

运行游戏并查看我们是否工作正常

现在理论上,系统已经在正常工作了,虽然目前还没有方法直接打印输出这些内容,但一切应该是按预期运行的,这个进展是个好兆头。接下来,为了查看发生了什么,所需要做的就是在框架的根组(root group)上进行一些操作,具体来说,可以通过某种方式来显示框架的根组内容。这样就能够检查根组的状态,确保系统在处理这些数据时没有出现问题。

game_debug.cpp: 提供每帧查看 RootGroup 的方式

在创建变量组时,为了便于调试,将做一些优化。默认情况下,变量组通常不会是展开的,但为了调试方便,将使得新创建的变量组默认展开。这意味着,在创建变量组时,系统会确保它们是展开的,从而便于查看和调试。

当前,变量组的处理是基于链接链的。当创建一个新的变量组时,它并没有立刻被添加到任何地方。当调用add variable to group时,链接才会实际被设置。具体来说,创建变量组时,next指针会暂时为空,直到第一次向该组添加变量时,链条才会建立。通过这种方式,变量组的链接关系会逐渐形成。

game_debug.cpp: 让 DEBUGDrawMainMenu 默认显示所有展开的内容

在考虑如何更好地管理和展开某些功能时,可以采取一种策略,使得所有的部分默认都是展开的。通过这种方式,不再需要处理某些特定部分的展开与收缩。举个例子,某个菜单的调试功能可以被设定为默认展开,这样在运行时就不需要再手动调整某些特定设置,从而确保所有内容始终可见,避免了需要额外处理的麻烦。

这种方法的好处在于,它简化了某些操作,尤其是在面对复杂的系统时,可以减少不必要的步骤和繁琐的调整。通过这种方式,即使在开发过程中面对复杂的功能模块,也能一步步验证每个功能的实现效果,确保每个部分都按预期正常工作。这种做法尤其重要,因为在系统整合和调试时,需要对每个小部分进行详细的检查和确认,确保各个模块之间能够顺利连接。

此外,面对复杂系统时,保持每次只处理一个功能点的做法能够帮助逐步缩小范围,使问题不至于过于庞大和复杂。通过分步操作和逐步调试,可以有效避免出现大范围的混乱和错误,确保代码运行更稳定。最终,随着开发进度的推进,系统会逐步精简和优化,最终达到一个更加高效和稳定的状态。
在这里插入图片描述

game_debug.cpp: 设置 debug_variable *Group = Tree->Group,并有条件地覆盖 Group

现在的目标是查看当前系统的输出,而不是像之前那样使用根组(root group)进行调试。具体来说,以前的做法是依赖树结构来进行可视化展示和交互,但在调试时,这个树状结构并不是必须的,因为它基本上只是为了记录每个交互的状态。

考虑到这一点,可以通过创建一个新的树结构来简化调试流程,特别是在需要输出调试信息时。例如,当前每个框架都有自己对应的状态,调试时可以通过框架的变量来确定当前的状态。可以通过简单的方式让每个树结构与对应的框架状态相关联,这样在输出时可以直接对应到相关的框架。

在操作时,可以通过帧计数来关联每个树,这样就能让每个树代表某个特定的框架。为了更方便的调试,可以选择固定使用第零帧(frame zero)来进行测试,而不是依赖复杂的树组结构。

具体的做法是,在调试过程中,栈操作可以用来推送状态,而不是使用原本的树组。通过修改代码,将调试变量组与框架对应起来,使得在测试时,输出的调试信息可以直接来自于框架零的状态,而不需要额外的处理。

最终的目标是简化调试过程,确保每次调试时能够清晰地看到当前系统的状态,而不会受到不必要的复杂结构的影响。通过这种方式,可以更高效地进行开发和调试,确保在后续的开发过程中能够快速定位问题并优化系统。
在这里插入图片描述

运行游戏并查看打印的内容

需要指出的是,当前的行为显然并不符合预期。每次操作时,系统都在不断地向同一个组添加内容,而每个帧应该有自己的独立组,而不是每个帧都向同一个组添加数据。也就是说,问题出在每个帧应该有自己单独的组,而不是不断重复添加到同一个组中。

目前的问题是,帧和组之间的关联出了问题,导致每次处理时,所有帧的数据都被添加到同一个组中。这样就出现了不正确的行为,导致系统的状态和输出不符合预期。

需要进一步检查代码的实现,找出为何每个帧没有正确地分配到独立的组,并修复这个问题。

game_debug.cpp: 调查为什么打印了这么多 RootGroups

目前,关于如何清理数据的流程存在一些不确定性。原本的预期是每帧结束时应该清空数据区域(collation arena),但是现在似乎出现了每帧的数据仍然不断添加到同一个地方的情况。虽然之前有尝试在每个帧结束时清理,但似乎没有生效,所以我们需要进一步检查这个过程,确认是否真的在每一帧结束时正确清理了。

目前不太清楚每帧保存了多少数据,可能我们保存了很多帧,因此可能导致了数据堆积的问题。尤其在处理"begin block"和"end block"时,代码没有正确地结束处理,这样可能导致一些预期外的结果。为了进一步排查问题,建议检查"end data block"是否真正在每个数据块结束时执行,确保代码确实在合适的时机关闭数据块。

在调试过程中,可以观察是否存在成功关闭数据块的情况。在一些特定的代码块中,检查是否成功匹配事件,并且数据块是否正确被动态分配和释放。特别是在第一次打开数据块时,需要确认父级内容是否正常更新。

总的来说,问题可能出在数据块的分配与释放上。需要验证是否有正确地关闭和清理所有的数据块,以确保每个帧都能正确独立处理,而不会造成数据重复或堆积。

调试器: 跟踪 FirstOpenDataBlock,找出事件不匹配的原因

目前出现了事件不匹配的情况,这表明代码中存在问题。尽管没有立即看到明显的原因,但事件确实没有匹配,导致了不应发生的行为。在这种情况下,应该深入检查代码,找出问题所在。

事件不匹配的原因可能出在数据块的处理上。正常情况下,如果数据块跨越帧边界,事件不匹配是可以理解的。但在当前的环境下,并没有使用来自其他线程的数据块,因此这种情况不应该发生,表明存在错误。

在进行调试时,发现了匹配块中,打开事件和关闭事件的记录索引不同,这是不应该出现的结果。我们期望这些索引和数据应该匹配。翻译单元(translation unit)保持一致,类型也应该对齐,但问题出现在事件的索引部分,特别是核心索引部分,可能存在某些意外的情况,导致这些不匹配。

需要进一步排查数据块的分配和事件匹配的机制,确保在处理数据时没有任何跨线程或数据不一致的问题。这将有助于找出为什么事件不匹配,并解决这一问题。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

game_platform.h: 考虑移除 EventsMatch 的概念

目前遇到的一个问题是调试记录索引在两个事件之间不相同。这个问题可能会导致在处理数据块时出现不一致的情况。我们需要仔细考虑如何解决这个问题,尤其是在处理时间块或类似的块操作时,之前已经小心地记录了计数器。

为了保持一致性,可能需要在代码中采取相同的方法,确保调试数据对齐。看起来这就是一种合理的方式,虽然会有些繁琐,可能需要更多的精细操作。但这也让人有些不确定,是否应该以这种方式处理,因为最终这只是一种调试检查,用于确保调试数据没有错误。

有时感觉这种处理方式可能并不理想,特别是在块的打开和关闭是对称的情况下,检查和结束块的操作似乎总是会变得多余,因为块操作本身是对称的。处理这些块时的主要目标就是确保没有错误,而不仅仅是验证调试数据是否对齐。因此,可能存在其他更简洁或更有效的处理方式,而不必这么繁琐地进行调试检查。

对于这个问题,目前的决定是将其暂时搁置,推迟到明天再做进一步的思考和处理。首先会移除当前的设置,看看目前的状态是什么,再考虑更详细的方案。

调试器: 运行并发现当我们包裹时遇到第一次异常

具体来说,我们还不确定当前打印出来的数据是否准确,因为我们甚至不知道那个打印输出对应的数据究竟是什么,也不了解对应的实体是谁。这种情况下,我们的验证和观察还不够充分。

不过,从整体进展来看,已经非常接近最终结果。剩下的关键工作是搞清楚打印数据的来源、确认具体实体,以及验证这些数据是否与预期相符。这些细节确认之后,整个系统就可以真正完成和稳定运行了。

调试器: 检查 RootGroup

目前我们正在进入最后的收尾阶段,目标是进一步稳固系统逻辑,确保没有任何残留的问题。现在的问题出现在调试视图(debug view)处理某个链接(link)时,返回的变量(var)是空的,也就是说,我们从内部链接获取的变量为 null,这表明有些原本应该被清理掉的内容似乎仍然存在于对应的 group 中。

这显得相当奇怪,因为照理说这些内容应该在某个时刻已经被移除,而目前它们还留在那里。我们继续查看渲染逻辑时发现,每一帧的数据实际上都被绘制了,系统最多保留了 32 帧,所以当我们查看第 30 帧时,不应该出现第一帧的变量链接仍然指向无效数据的情况。

进一步调查时,我们查看了第 0 帧(frame 0)的 debug state,并检查了 root group,结果发现 root group 已经变成了垃圾值(garbage),这显然是不正常的。而 root group 理应是持续存在并保持完整性的,它不应该在某一时刻就变成无效内容。

为此我们开始分析内存清理逻辑,尤其是与 restart collation(重启聚合过程)有关的部分。理论上,restart collation 会重置一切,包括 frame counter,以及所有与调试变量、数据聚合、内存管理相关的状态。这样一来,所有老旧的调试数据都应该被彻底清除,不应该残留任何“幽灵变量”。

因此,现阶段的困惑点集中在:

  • 为什么 frame 0 的 root group 会被提前回收或替换成了垃圾数据;
  • 明明重启聚合过程时应该清理得很彻底,为什么还会出现无效引用;
  • 是否某些结构在 collation 逻辑之外被不当访问或错误保留;
  • debug 变量的引用计数或生命周期是否出现管理不当的情况。

后续的解决思路是围绕 restart collation 的覆盖范围和顺序做深入验证,检查是否有部分变量或对象在清理前被错误引用或未及时释放,确保所有调试数据的生命周期和状态转换都完全符合预期。只有这样,系统整体的稳定性才能最终得到保障。

调试器: 步进到 RestartCollation

目前的逻辑中,在设置好帧数之后,只调用了一次 restart collation。之后程序开始正常运行,但在某个时刻发生了崩溃。从现象来看,这种崩溃显得非常危险,属于一种“很糟糕的事情”——也就是说,根 group 被破坏了,或者说其内部结构出了问题,导致系统行为异常。

根据已有分析,我们非常确定 root group 不应该在任何时刻以这种方式出现异常。它本应始终保持有效,不被随意破坏。于是我们回溯到 restart collation 的调用逻辑,在那一步里确实重新初始化了状态。

我们进一步检查 debug state frames[0] 的时候,发现 root group 的状态变成了空值,这显然是不对的。理论上,root group 应该是初始化好的空 group,具有稳定结构,而不是 null 或被破坏的状态。具体表现为:

  • 崩溃前 root group 仍然存在;
  • 崩溃时 root group 内容已经无效或被意外修改;
  • 系统中可能存在某处引用仍然保留旧的 root group 指针;
  • 也可能是某个写入逻辑直接覆盖了 root group 的内存区域;
  • 又或是调试数据在生命周期上处理不当,导致未释放或多次释放;
  • 更进一步猜测,可能 restart collation 后我们并未覆盖所有旧引用,因此在后续处理中又访问了旧地址,从而出现非法操作。

目前唯一已知的规律是:restart collation 只执行了一次,之后再也没有被调用,但 root group 却仍在运行中被破坏,因此我们怀疑问题不是出在初始化逻辑,而是运行过程中某个模块误操作了 root group。

接下来应重点排查的方向包括:

  • 是否有对 debug group 的不当写入或共享访问;
  • 是否有模块在没有复制的情况下直接持有对 root group 的引用;
  • 是否有释放未完全清除依赖关系的内存对象;
  • 是否有逻辑误以为某 group 不再被使用,提前做了销毁;
  • 是否某些调试变量在运行期间未能绑定到新一帧正确的 group 上。

总之,这种“看起来很糟糕”的现象背后隐藏着深层次的状态管理或内存生命周期问题。下一步应继续跟踪 debug 变量的来源与绑定过程,逐帧比对 group 的指针变化和内容写入过程,确保整个 collation 和 debug 数据系统的运行机制不再有状态穿插或引用紊乱的问题。

game_debug.cpp: 测试 HackyGroup

目前我们还发现了一个额外的问题需要处理:在处理树结构(hax tree)时,尽管这和当前主 bug 无关,但仍应该一并修复。具体来说,当尝试访问某个变量的 group 时,不能默认它始终有效,因为在某些情况下,这个 group 可能是 0。因此,应该加入一个判断逻辑,确保我们访问的 debug_variable 的 group 是有效的,防止使用无效引用。换句话说,我们应更严谨地判断 group 的存在,而不是假设其总是非空。

尽管这是一个必须修复的健壮性问题,但它与当前主线问题并无直接联系。目前的核心问题仍然是:

  • 程序运行一段时间后出现异常;
  • 原本正常的 group 结构在某个时间点变得异常或失效;
  • 崩溃发生时,group 的内容不再正确,已被破坏;
  • 之前 root group 是有效的,之后变得“非常不对劲”;
  • 也就是说,运行期间某个地方对 group 做了破坏性写入;
  • 而这个问题在最初运行阶段是不会出现的,要等到运行一段时间、数据积累或轮转发生后才暴露出来。

当前我们推测可能的原因包括:

  1. 内存管理问题:某处错误地释放或重用 group 所在的内存区域,导致旧指针指向无效地址;
  2. 引用生命周期管理失败:某个模块持有了对旧 group 的引用,restart collation 后未正确更新;
  3. 数据结构未初始化完整:可能某些 frame 的 group 创建不完整,导致初始可用但后续访问失败;
  4. 状态轮换逻辑错误:frame 环状缓冲区或历史帧数据可能有覆盖、索引错位等问题;
  5. 树结构处理逻辑有遗漏:某些调试路径或打印逻辑在未校验 group 有效性的前提下访问其数据,导致访问非法内容。

当前我们计划采取的应对方式是:

  • 插入更严格的 group 有效性检查(如上述 hax tree 修复);
  • 加入断点或日志,记录 group 结构的创建与释放时间点;
  • 比较崩溃前后某一帧的 group 内容变化;
  • 明确在 restart collation 之后哪些引用还可能保留旧值,排查是否所有依赖都已更新;
  • 尝试定位是哪一帧的数据第一次出现异常,逆推引发该变化的操作。

总的来说,目前问题的表现是 group 在运行中遭到破坏,导致调试数据访问失败,而问题产生的核心很可能与内存管理或生命周期控制相关,后续将进一步细化追踪路径,逐步定位根本原因。

game_debug.cpp: 在 CollateDebugRecords 中提供在第 31 帧时断点的功能

我们现在想做的是,每次调用 debug_end 的时候,都观察当前我们拥有多少帧(frame)。具体操作思路是:

  • 在执行调试记录收集(collation)的时候,添加一段检查代码;
  • 通过访问 debug_state.frame_count,来监控帧的数量;
  • 如果帧数等于 31(即接近上限),并且某些条件满足(如大于零),我们希望在此时打断点或输出信息,方便进行调试;
  • 目的是在出问题之前捕捉系统状态,特别是在即将发生帧覆盖(wrap-around)时;
  • 打算在 collect_debug_records 函数内设置监控点,以便在关键时机及时中断和观察数据状态。

这个操作可以帮助我们分析帧循环使用(frame wrap)是否是引发 group 被破坏的关键点。因为之前的观察显示,程序会在运行一段时间之后(可能是帧数达到限制后轮转)才出现 group 崩坏的问题。

通过添加这个断点或条件日志,我们就能:

  • 精确捕捉到帧数达到关键值时的状态;
  • 检查 root group 是否被替换或清空;
  • 对比前后帧的数据是否被非法覆盖;
  • 为后续追踪问题提供更多上下文。

这属于为问题重现提供更可控的入口,方便我们对调试状态进行精准观察与分析。

调试器: 步进到 CollateDebugRecords 并检查数据

当前我们正处于调试阶段,聚焦于第 31 帧这一关键时刻,观察问题的具体发生点。我们发现第 0 帧的 root group 在第 31 帧结束后仍然是正常的,并未出现之前那种被破坏的情况。我们继续推进到下一轮运行——第 32 帧,然后问题就出现了。

此时我们能确认的是:

  • 在第 32 帧的时候,frame 0root group 被破坏;
  • 我们设置了断点,在问题发生前停下,可以观察 bug 出现的全过程;
  • 可以确认,在这个关键帧(frame 0)即将被重新写入的时候,它的 group 数据结构被破坏(也可能是内存被非法写入或被提前释放);
  • 我们怀疑问题与新加的那段代码有关,因为此前没有这段代码也没有这类 bug 产生;
  • 在这个推测下,我们将重点检查新逻辑中,是否有部分操作影响到了 frame 0root group
  • 实际观察发现,group 的破坏其实发生在进入新代码逻辑之前
  • 这意味着问题可能并不是新逻辑直接破坏了数据,而是该逻辑间接触发了某种机制,使得 frame 0 被提前或错误地重写或回收;
  • 现在我们的猜测转向是否在 “complete frame” 的操作过程中,出现了对 frame 0 的误处理;
  • 我们尝试检查 max_debug_event_array_count 的相关定义,怀疑帧数限制或者索引循环(wrap-around)机制有问题;
  • 推测在某个地方帧索引被误用或数组下标超界,导致写入发生在不应被覆盖的位置,从而破坏了有效数据。

接下来应该深入验证:

  • “complete frame” 或帧轮转逻辑是否在不恰当时机重置了 frame 0
  • 新逻辑是否无意中触发了某种“清理”或“回收”操作;
  • root group 是否被某个结构共享引用,从而间接受到其他修改影响;
  • 调试输出记录每一帧的生命周期,观察其从创建、使用到被销毁的全过程。

目前,问题的轮廓已经越来越清晰,我们接近锁定 bug 的真正根源。

game_platform.h: 确定当我们遇到第 32 帧时,数组会被包裹

目前我们基本定位到了问题的根源,和帧计数达到最大值之后数组越界相关。我们将 max_debug_event_array_count 设置为了 8,这意味着只能存储 8 帧的调试事件。一旦 frame_count 达到 32,我们会对这个数组进行 wrap-around(环绕写入),也就是从头开始复用旧帧的数据槽位。

我们的问题就出在这里:

  • frame_count == 32 时,调试事件数组会从头开始重新写入;
  • 然而此时旧数据(比如 frame 0 的 root group)仍然可能被其他地方引用;
  • 一旦我们在不做妥善处理的情况下复用这个帧槽位,原本还在使用的 root group 数据就会被覆盖,导致数据损坏;
  • 这也解释了为什么一开始运行没问题,只在帧数循环回来以后才出错;
  • 我们进一步怀疑,原本应该有某种 restart_collation() 的机制在帧数达到某个阈值时触发,从而清除旧帧并重置状态,但这个机制似乎没有正确执行或没在合适的时间点被触发;
  • 回顾旧逻辑,我们甚至开始怀疑原本根本就没有一个完善的 wrap 处理方案,可能只是暂时性的“试验代码”;
  • 现在的行为更像是一个未完善的设计草稿——缺少完整性检查、没有保护指针或引用、也没有在 overwrite 时对旧引用做有效断链;
  • 目前代码是“实验性质”的,所以在结构和健壮性上本就没完全搭建好,因此这一切的混乱也是意料之中的。

接下来需要认真处理的问题有:

  1. 清晰定义帧生命周期
    每一帧在被重新使用前,应该确保其所有引用已经释放或断开,不能再有任何指向它的活跃指针。

  2. 添加 wrap-around 保护逻辑
    在帧数超过最大值即将环绕写入之前,应该调用 restart_collation() 或类似的清理逻辑,确保数组中即将被覆盖的帧已经安全结束生命周期。

  3. 添加调试断言
    增加断言,例如在写入一个帧槽位前判断它是否仍被使用,从而更早地发现潜在的引用错误。

  4. 更合理的帧上限与使用策略
    重新评估 max_debug_event_array_count 是否设置得太小,或者应该改为动态分配、更大缓存、更慢回收等策略。

总结来说,我们目前基本锁定问题是调试帧数组环绕写入时出现了错误写入导致原有数据被破坏,而原始设计中对此情况没有做好完整处理,这是下一步要重点修复的方向。

game_debug.cpp: 让 RestartCollation 提前一帧发生

我们现在发现了一个根本性的逻辑问题:我们曾经为了暂时绕过滚动缓冲区(rolling buffer)的处理逻辑,写了一个临时的 hack 式实现,但这个实现本质上就是错误的。

具体来说:

  • 当前这套临时机制中,当帧数继续增长并超过缓冲区上限时,它会越界写入;
  • 超出了原本预设的帧数量限制,写到了数组允许的最大下标之外;
  • 之前没有直接崩溃,只是因为我们用的是一个较大的内存区域,所以写越界也暂时没造成明显的问题;
  • 但这绝对不是合理或安全的行为,写越界始终是不被允许的,这只是偶然间没出问题;
  • 这个“临时方案”本意是先绕开滚动缓冲逻辑、简化实现过程,然而实际上它的行为是完全不对的;
  • 我们已经确认这个越界写入是确切发生的,并不是仅仅逻辑风险,而是实际在运行中发生并引发了数据损坏;
  • 总之,这段临时逻辑从根本上来说就是“完全不合法”的实现,只是原先没被触发出来而已。

接下来需要立即着手修复的部分:

  1. 彻底移除这个错误的临时代码
    无论后续是否立刻实现完整滚动缓冲逻辑,都不能继续保留这个写越界的做法。

  2. 建立正确的帧索引机制
    应该通过 frame_index % buffer_size 的方式限制写入位置,确保永远不会越界。

  3. 增加断言与保护
    在写入前做严格检查,一旦即将越界或覆盖尚未释放的帧,应立即报错或触发清理。

  4. 重构 rolling buffer
    是时候实现真正的滚动缓冲机制了,避免因临时 hack 方案带来后续更多 bug。

总之,问题的根本是我们曾经为图省事跳过了关键设计点,现在这一跳过的代价已经暴露出来。我们不能再依赖这种“写了不出错就是可以”的做法,必须立刻修正、规避写越界的行为。

运行游戏并查看实体选择器是否正常工作

现在的系统已经开始正常工作了,这感觉还是挺酷的。

我们已经可以在界面上通过鼠标悬停获取到对应的实体数据,而且确实能看到有两个实体出现在热区。这一切运作正常,说明逻辑链条基本已经打通。

这两个实体的出现完全符合预期:一个是树的实体,另一个是其下方的空间实体。当前的处理能正确识别和返回这两个实体的信息,说明我们的调试和数据结构关联已经初步稳定下来。

从整体上看,这一阶段的开发已经逐渐接近目标,整个系统也开始表现出理想中的行为。这让人非常振奋,因为功能部分已经基本跑通。

接下来,虽然还有不少整合工作要做,包括代码清理、结构优化、边界处理等,但整体框架已经显现出清晰的雏形,并且在逐步收敛到最终形态。

目前的状态令人非常满意,是一个实质性的进展。现在需要的,是继续打磨细节,让这套系统变得更坚固、更易维护。整体上已经进入收尾和精炼阶段了,值得期待。

我们已经在调试代码上花了相当长的时间。你认为我们还需要多长时间才能完成?

我们已经在调试代码上投入了相当多的时间,对于“还需要多久才能完成”这个问题,其实是很难给出一个确切答案的。

如果这是一个有明确截止日期的任务,比如需要在某个时间点之前完成,那么当然可以通过裁剪内容、控制工作范围来压缩进度,达到目标。但如果是为了构建一个真正合理、正确的系统,并打算花时间把它做对,那就很难预测具体需要多久才能搞定。

编码的现实情况就是这样:只要在认真构建系统,就无法精确估计完成时间。

很喜欢一点——有人在处理这类调试代码时会感到不耐烦。如果觉得这种过程不适合用来“直播”或展示,确实很抱歉,但事实是:这才是真正严肃编程的日常。真正的工作过程就是这种样子。如果在这种场景下,无法专注并清晰地理清问题和思路,那说明还需要提升。

我们目前在调试代码上已经花了大约二十个小时,这其实并不算多——考虑到这些时间大多数都在反复调试和逻辑验证中。这种规模的工作,应该是程序员一周中正常工作量的一部分。如果有人觉得这已经“太久”或者“太复杂”,那其实是一个不太好的信号。

对真正热爱引擎开发的人来说,这种程度的调试和修正工作应该是日常、是理所当然的,是一点也不值得抱怨的事。如果感觉这些过程枯燥、耗时,那可能就需要重新审视一下自己的心态和方向了。

当然,这并不是在批评谁不适合写引擎,而是说如果目标是成为优秀的底层或系统程序员,那就必须要学会喜欢、欣赏这类打磨过程——就像一个手工匠人在捏陶杯时,会耐心地修整每一个细节,而不是随便捏一个洞就叫它是“能喝水的杯子”。

所以,要培养对过程的热情,要享受把东西做得尽善尽美的过程——这是成为优秀程序员非常重要的部分。

你有使用数据断点吗?我发现当某个值在某个地方变化时,你不确定具体位置时,数据断点很有用。

有一个问题问到是否使用数据断点,尤其是当遇到某些病毒扫描时不确定问题出在哪里。答案是肯定的,确实会使用数据断点。不过,之所以在直播时很少看到使用数据断点,是因为在学习编程的最初二十年里,数据断点要么根本就不存在,要么非常不可靠。所以习惯上,自己并不常用它们。

以前的数据断点要么完全没有,要么就是在使用时不太稳定,因此曾经是一个非常不可靠的调试工具。因此,自己习惯了没有数据断点的调试方式,哪怕在现在,它们的可靠性有所提升,依然没有把它们作为常规手段来使用。很多时候,自己更倾向于通过编写代码并运行的方式来模拟数据断点的功能。

虽然现在的数据断点已经比以前稳定得多,但因为过去的不良经验,自己仍然习惯于通过手动方式调试,而不是使用断点。也许现在它们更可靠,但自己还是更喜欢手动调试。尽管如此,如果数据断点对某些人来说非常有效,且使用稳定,那完全可以使用,没有问题。

总结来说,并不是认为数据断点不好,而是因为过去的数据断点常常不工作,已经养成了不依赖它们的习惯。如果发现数据断点可靠且有效,那就完全可以使用它们,自己并没有反对的意思。

数据断点(Data Breakpoint)是一种调试技术,允许开发人员在程序中的某个变量或内存位置的值发生变化时暂停程序执行。这与传统的代码断点(如在某行代码处暂停执行)不同,数据断点是基于数据的变化触发,而不是执行到特定行代码。

工作原理:

  1. 设置数据断点:开发人员指定一个特定的变量或内存地址。
  2. 监视数据变化:当该变量或内存位置的值发生变化时,调试器会暂停程序的执行,并提供相应的调试信息。
  3. 调试分析:开发人员可以查看当前状态、调用堆栈、内存内容等,帮助定位程序错误或分析问题。

使用场景:

  • 调试不可见的修改:当某个变量的修改是由代码的间接影响或其他地方的代码修改时,使用数据断点可以帮助追踪到变量的变化源头。
  • 定位内存泄漏:如果程序中某个内存块不断变化,设置数据断点可以帮助监测何时发生了变化,帮助分析内存管理错误。

优点:

  • 数据断点可以帮助开发人员精确地监控和调试变量或内存的变化,而无需手动插入代码来打印这些值。
  • 能够更好地追踪数据的流动,特别是在复杂的程序中,查找变量何时被修改可能非常困难。

缺点:

  • 数据断点可能影响程序的性能,因为每次变量值变化时都需要暂停程序,这可能导致调试过程中程序变慢。
  • 并非所有的调试工具和平台都支持数据断点,尤其是一些老旧的或不完整的调试器。
  • 如果数据断点设置不当,可能会导致程序行为异常或出现无法预料的问题。

总结来说,数据断点是一个强大的调试工具,可以帮助开发者在变量或内存内容发生变化时捕捉到相关信息,有助于深入分析程序行为,但也需要注意使用时的性能开销和调试工具的兼容性。

这个调试系统的设计与其他游戏中使用的调试系统有很大的不同吗?

这个调试系统与我以前接触过的任何游戏的调试系统完全不同。实际上,它看起来与我曾经写过或使用过的任何调试系统都不相似,尽管可能有一些调试系统与这个类似,只是我没有使用过。

你在自己的代码中是如何处理 GL 句柄的?是由资源系统处理还是其他地方处理?

通常,它们不是通过资源系统来处理的,而是通过其他方式来处理。这个问题比较复杂,可能更适合在将来进行渲染时再讨论,或者可以在论坛上提出,大家可以在那里探讨。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com