游戏引擎学习第220天

发布于:2025-04-13 ⋅ 阅读:(20) ⋅ 点赞:(0)

介绍

今天的工作主要是进行一些代码整理和清理,目的是将我们之前写过的代码重新整合在一起,使它们能够更好地协同工作。现在的阶段,我们的任务并不是进行大规模的功能开发,而是集中精力对现有的代码进行整合和思考,确保它们在当前的架构下能够正常工作。

总体来说,今天的工作重点就是清理和整理现有代码,让它们能够顺利整合到一起,确保在实际运行时没有问题。这是一个需要耐心和细致思考的过程,但它是必要的,以便能够继续进行后续的开发工作。

调试系统回顾

目前,我们的进展大致到了一个点,其中层级结构已经能够自动构建起来。到目前为止,这一部分的功能做得还不错,甚至可以像预期那样展开和折叠。然而,虽然层级结构本身已经可以正常显示,但仍然有一些小问题需要处理,比如显示的名称过长,导致不够简洁。这一点虽然不是特别重要,但显然还是需要修复一下,让显示的名字更加合理。

接下来,我们还需要做一些事情,比如查看现有的代码片段,特别是之前写的那个可以高亮实体的功能。现在,我们依然能够像之前那样选择多个实体,并且这些信息会被输出到调试系统中。不过,当前这个功能并没有完全和我们现在的调试系统整合在一起。因此,我们需要回过头来,重新审视并调整这部分功能,使其能够以更方便和合理的方式与现有的系统进行集成。

总的来说,接下来的任务是解决显示名称过长的问题,同时整合和完善高亮实体功能,使得调试视图能够更加流畅和有效。
在这里插入图片描述

今天工作的想法

目前,正在考虑如何优化调试系统中的一些交互元素,特别是下拉菜单和可折叠部分。现在的实现方式存在一些问题,比如,当选择某些项目时,调试信息直接被输出到界面上,显得很混乱,用户无法直观地理解发生了什么。例如,某些信息可能会直接显示第一个项,导致显示的内容显得没有意义。因此,计划对这些输出进行改进,使得它们能够以更合理的方式出现在调试层级结构中。

具体来说,目标是将信息输出到调试层级中的特定位置,而不是随意地显示出来。这样,当用户展开某个部分时,可以看到该项的详细数据。如果选择了多个项目,系统应该能够以某种形式(比如数组的形式)显示每个项,并且这些项是可展开的,便于查看和分析。

此外,还有一个重要的任务是重新整合性能分析器(profiler),这也是下一步的重点。通过这些改进,可以让调试系统达到一个相对完整和可用的状态,确保架构上是合理的,并且之后如果需要添加新的功能或事件处理机制,开发者能够清晰地知道如何操作和扩展。

总之,接下来的目标是让调试系统达到一个满意的阶段,能够稳定运行,并为将来可能的扩展奠定基础。完成这些后,可以将焦点转移到其他的任务上,虽然目前还没有明确的下一个目标,但肯定是会涉及到一些不同于当前调试系统的工作。

回顾 DEBUG_VALUE 和 DEBUG_BEGIN_DATA_BLOCK 的工作原理,考虑统一唯一 ID

在调试系统中,之前使用了 DEBUG_BEGIN_DATA_BLOCK 来输出像“Simulation Entity”这样的数据,并且可以看到输出了类似实体的 EntityDebugID,这个 ID 是用来标识实体的唯一标识符。它代表了实体的唯一性,就像一个网格一样,用来区分不同的实体。

现在考虑的一个问题是,是否能够将当前的数据块系统和网格系统统一起来。也就是说,将数据块视为网格,并将它们整合进当前的系统中,让它们在适当的位置输出相关数据。这种方式可能会使得所有的网格数据得以统一,简化系统的设计和管理。

这种方法的优势在于,所有的网格相关数据能够集中管理,避免了当前系统中可能存在的冗余和重复,从而使得调试过程更加简洁和高效。
在这里插入图片描述

思考在 BEGIN_DATA_BLOCK 中使用字符串进行调试 UI 布局

另一个考虑的方面是,当使用 DEBUG_BEGIN_DATA_BLOCK 时,可以注意到目前我们有 simulation_entity,而这个实体可以作为数据块的名称。例如,我们可以考虑用类似 highlighted_entity 或简单地使用 entity 来标识当前选中的实体。这样,在调试过程中,所选中的实体名称就能更清晰地显示出来。

具体来说,可以通过定义类似 simulation_highlighted_entity 的名称,将当前选中的实体与调试数据块的名称关联起来。这样,当数据块被输出时,相关的实体信息就会被正确地放置到指定的位置中。这不仅能帮助更好地管理和显示调试数据,还能提升调试时的可操作性和清晰度。

在这里插入图片描述

更多关于使用 EntityDebugID 区分实体的想法,而不涉及游戏代码

有趣的是,调试系统中还可以创建一个唯一的 debug_id,这个 ID 能够唯一标识一个实体。这使得我们能够在调试系统中将数据组织起来,从而能够区分不同的实体,而不需要干扰到游戏的实际逻辑。通过这种方式,我们可以在调试系统中准确地识别和区分每个实体,而无需修改游戏代码本身。

这种方法的有趣之处在于,它为我们提供了一种在不干扰游戏的情况下进行实体区分和调试的方式。因此,今天的目标就是将这个功能集成到调试系统中,并确保它能够正常工作。希望今天能够在这个问题上取得一些进展。

回顾调试数据块的工作原理,以及它们如何与由 MarkDebugValue 事件类型处理的调试开关不同

在当前的调试系统中,mark debug value 用于控制类似调试开关的功能,比如开启或关闭调试特性。这个过程与我们处理的 begin blockend block 不同,因为 begin blockend block 仅在代码中的特定位置生成,但它们可以在运行过程中多次触发,特别是当这些代码被循环调用或在其他地方重复执行时,每次执行都会生成一个新的数据块。

与此不同,调试值(debug value)通常只会在代码的单一位置存在。例如,调试开关通常仅在一个代码点上定义,并且只有两种状态——开启或关闭。而 begin blockend block 是在代码中固定的两处地方,但它们生成的数据块数量可以非常庞大,特别是在循环或多次调用的情况下。

同样,open data blockclose data block 的生成也仅来自代码中的单一位置(目前只有一个地方),但是如果有多个模拟实体被选中,那么每个实体的数据块都会被生成,尽管它们都是从同一位置发起的。每次循环或调用都会生成多个数据块。这与调试值不同,调试值通常只有一个存在点,而且状态只有开关两种选择。

因此,调试数据块的生成方式与调试值有明显的不同,调试数据块是可以在运行时多次生成的,而调试值通常只有一个固定的存在点,并且它们的状态变化比较简单。

讨论我们希望实现的调试数据块的更改,以及它们如何帮助调试

我们希望能够存储经过调试的块数据,并在打印出层级结构时,将这些数据存储的位置定义为一个统一的地方。当数据被打印出来时,可以在这个位置展示一个小列表,指明该位置被调用了多少次,并允许选择要查看的具体调用。这意味着我们可能需要为每个数据块使用一个标签来标识,利用实体的调试ID来区分它们。

除此之外,为了使调试信息更加人性化,我们可能还需要传递一个友好的索引。这不仅是为了唯一性,而是为了让数据更加容易理解。例如,可以使用实体的存储索引,而不是一个庞大的指针值。这样做的好处是,用户可以看到类似“实体6240”这样的信息,而不是一个看似无意义的指针(例如“x8091a…”),这对于用户来说更具可读性和实际意义。

通过这样的方式,即使这些数字不直接表示实体的顺序,也能给用户一个更直观的理解,因为像“实体6240”这样具有可辨识性的数字,至少比单纯的指针地址更有意义,虽然指针可能是按顺序分配的,但通常不会直接以递增的方式增加。

考虑更改调试数据块的分配策略,使其与调试元素的分配策略一致

我们目前的做法是通过分配和释放调试数据块的方式来存储信息,这在一定程度上是有效的。然而,考虑到当前的实现方式,我们可能需要调整一下。我们现在的方式是为每个数据块单独分配存储空间,而我在考虑是否可以将这些数据块直接推送到一个类似的滚动格式中,就像存储其他事件一样。具体来说,我们可以将这些调试数据块链式地添加到元素中,而不是每次都单独分配和释放内存。

现在,我们的存储事件是以单独的块形式存在的,但如果我们改变这种做法,可以将调试数据块作为事件的一部分存储在同一个元素里。这意味着我们可以设计一个更加紧凑的调试元素,它可以存储不同类型的内容,比如事件、数据块等,而不仅限于某一种类型。这样的话,在清除帧时,我们就能按照这个新的存储方式,依次处理和释放数据。

这种改进的思路让我们能够更加灵活地处理调试数据,同时也可能提高内存管理的效率。

统一调试数据块和调试元素代码的想法

由于所有这些信息都是作为事件传入的,所以可以将它们堆叠在同一个数据块中。例如,考虑到我们当前的调试值处理方式,可以将这些调试数据直接堆叠在一个数据块里,这样处理可能就能正常工作。实际上,经过思考,发现没有什么问题,这种做法应该是可行的。

具体来说,当处理这些调试数据时,如果是来自调试值的事件,我可以将其存储在当前打开的调试元素中。由于“模拟实体”可以告诉我它的位置,理论上来说,这种方法应该是一直有效的。也就是说,所有的调试数据最终都会被归纳到相同的系统中,所有的事件都能以一致的方式进行处理。

在事件索引循环中,当遇到“调试开始数据块”时,它会获得一个对应的元素,并进入相应的代码,这样就能知道具体是哪一个元素。这种方式将调试数据处理过程统一到一个系统中,使得整个流程更加简洁和高效。

更改 OpenDataBlock 调试事件类型的处理,以适应新的分配思路

在处理“打开调试块”时,可能不需要进行额外的工作,比如创建变量组等。这些步骤看起来并不是必须的。因此,可以简化流程,直接按照之前的方式进行处理。也就是说,直接使用我们之前的方法,省略那些不必要的操作。

关键是要将调试元素存储在适当的位置。具体来说,当我们打开调试块时,会记录下该调试块关联的元素。接下来,在处理存储事件时,我们可以覆盖原本存储的元素,替换为当前调试块应存储的元素。

如果查看代码的处理方式,会发现我们实际上可以在存储事件时直接更新存储的元素,而不需要依赖过多的中间步骤或复杂的操作。通过这种方式,整个过程变得更加简洁和直接。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

快速查看 StoreEvent 如何与调试元素一起工作

我们讨论了在 store event 函数中发生的操作,具体来说,我们传递了一个元素进去,然后进行必要的释放操作。接下来,我们处理了事件并将其存储到指定的元素中。

在理论上,接下来的步骤就是:如果此时存在一个打开的调试块(debug block),那么我们不再将事件存储到原本的元素中,而是将它存储到数据块(data block)下方。这就是我们所需要做的操作。

总结如下:

  1. 讨论了在 store event 函数中,如何处理事件存储的过程。
  2. 提出了一个修改方案:如果有打开的调试块,应该将事件存储到数据块下方,而不是直接存储到原始元素。
  3. 目标是简化存储逻辑并确保在调试块存在时,事件能被正确存储。
    在这里插入图片描述

澄清调试系统当前的工作方式和计划中的更改

我们讨论了代码的复杂性,并表达了希望确保每个人都能理解这些内容的想法。指出代码路径现在非常复杂,所有的调试值调用都走同一条路径。虽然这对于扩展功能是好的,但这不是我们原本想要的效果。我们希望这些调用能够被适当分组,而不是直接在同一个路径上处理。

我们提到的解决方案是,在打开数据块时,接下来的所有调试值都应该被添加到该数据块的元素上,这样它们就被包含在一个单独的逻辑单元中。当打印时,只需从该元素中提取所有内容并以合适的方式显示,这样我们就可以将相关的调试信息聚集在一起,而不是让它们孤立开来,形成一条条独立的记录。

总结如下:

  1. 讨论了当前代码的复杂性,并表达了希望让所有人都能理解这部分代码的意图。
  2. 代码的调试值调用现在都通过相同的路径处理,但这并不是理想的情况。
  3. 提出了一个改进方案:当打开数据块时,将调试信息添加到该数据块下,而不是将它们直接处理为独立的行。
  4. 最终目标是让这些信息在一起,能够更方便地展示和处理,而不是杂乱无章。

更改 CloseDataBlock 调试事件类型的处理

当前不需要继续使用某些代码,决定暂时移除它们。虽然也考虑到可能在数据块跨越多个帧时需要一些额外的处理,但希望这不再是一个问题。如果真有需要处理的情况,会在之后再进行思考。

总结如下:

  1. 决定暂时移除一些当前不需要的代码。
  2. 考虑到数据块跨越多个帧的情况,可能需要一些额外的处理,但希望这不再是问题。
  3. 如果以后发现需要处理,将会在稍后再进行考虑。

更新 AllocateOpenDebugBlock 函数和 open_debug_block 结构,以存储 debug_element

讨论中提到,当分配并打开调试块时,主要目的是跟踪已打开的调试块。这些调试块需要存储一个元素,以便知道我们正在处理的是哪个元素。通过这种方式,可以避免每次查找元素,因为这样会比较麻烦。

具体实现思路如下:

  1. 不必每次都传递元素,可以直接通过事件获取元素,但为了提高效率,可以将元素存储在调试块中,避免每次都进行查找。
  2. 传递元素到调试块时,直接将元素存储,这样在需要时就可以直接访问。
  3. 确保打开的事件没有在其他地方重复存储,避免冗余。

最终,这种方式简化了处理过程,直接存储元素,减少了查找操作,提高了代码的效率和可维护性。

允许调试数据块覆盖 StoreEvent 存储的 debug_element

在这个过程中,提出了一个方法,目的是如果存在一个打开的调试块,就使用该调试块的元素来替代当前的元素。具体的操作是:

  1. 检查是否有需要覆盖的元素,如果有,就将当前元素赋值给该元素。
  2. 如果有打开的数据块,则将数据块的元素存储到该元素中。
  3. 最后将元素存储起来。

然而,按照预期进行的操作并没有生效,结果并不像预想的那样工作,可能存在一些问题或者需要进一步的调试来修复。

尽管如此,开发过程中依然保持了对问题的积极态度,并且在遇到问题时继续努力解决。

实际使用正确的变量,运行游戏

问题的出现可能是因为没有传递实际计算的值。当前的代码在处理某些元素时存在问题,导致无法按预期工作。为了确保正确性,需要确保计算后的值被正确传递到相关的部分。这可能是导致当前问题的根本原因。
在这里插入图片描述

通过不再将字符串传递给 DEBUG_BEGIN_DATA_BLOCK 宏,去除调试显示中的错误引号

当前的问题出现在模拟过程中,主要是因为之前使用的是字符串,但现在我们转向了字符串数组的方式,因此不再希望继续传递字符串给相关部分。尽管可能需要重新调整宏来适应这种变化,但目前看起来不需要。目标是让系统按照新的方式工作,而现在的情况已经开始显示出一些进展,能够清楚地看到模拟已经在按新的方式运行。
在这里插入图片描述

再次运行游戏,注意到一个缺少分组的问题,找出原因

目前遇到的问题是缺少了一个本应存在的组,应该是“可扩展实体”的部分。这可能是因为在打开数据块时,实际上没有调用 store event,但问题的根源并不是这个。事实上,当打开数据块时,我们是通过 GetElementFromEvent 来获取元素,这意味着应该为这个元素创建一个新的元素,并进行层级命名。因此,按理来说应该可以看到“模拟”下的“实体”部分。

但问题出在我们没有显式地声明这是一个组,可能正是这个原因导致没有显示出预期的内容。这一分析让问题的根源变得更清晰了,确实是因为没有明确标记为组,导致了结果的缺失。

开始尝试修复调试数据块没有作为分组显示的问题

为了确保能够正确创建组,需要显式地声明“可扩展实体”作为一个组。在打开数据块时,虽然它会为所有父级元素创建组,但它不会自动为自己创建组,因为它假设该名称已经是一个有效的值,并将被插入。然而,在这种情况下,实际需求是不同的。

因此,真正需要做的是在处理组名称时,要明确为“simulation_entity”创建一个新的组。具体来说,当通过 GetGroupForHierarchicalName 获取组时,系统会首先检查是否已经有一个类似的组名。如果没有,它会创建一个新的组。第一次遇到时,会创建名为“simulation”的组;如果接下来已经存在类似的组,则不会重复创建。

为了确保这一过程,我们需要手动为“entity”创建一个组,并确保每次操作时都能够按照正确的步骤进行。这可以通过在代码中添加适当的操作来实现。

向 GetGroupForHierarchicalName 添加一个标志,用于选择性地创建附加分组

为了确保最后的名称也能够作为一个组创建,可以采用一种简单的方式。思路是即使没有找到第一个下划线,也可以将最终的名称作为组来处理。具体实现时,可以使用类似以下的逻辑:如果找不到第一个下划线,就假设第一个下划线是整个名称的一部分,然后直接创建剩余部分的组。

这可以通过一种直观的方式来完成,而不必太复杂。这样做能确保即便没有找到分隔符,我们仍然能够正确地创建最终的组。最终,代码将按照预期的方式处理所有名称,确保不会遗漏任何组。

这样做的好处是简单且高效,没有必要为了优雅而增加额外的复杂性。
在这里插入图片描述

撤回标志的想法,决定直接将元素转换为分组

在重新考虑后,发现其实并不需要之前提到的复杂步骤。实际上,只需要将元素本身转换为一个组,这样的操作非常简单。最初的想法有些过于复杂,其实可以通过更直接的方法来解决。

具体来说,当添加一个调试变量链接时,默认情况下它会被设为一个元素,所需要做的就是将它更改为一个组。这样一来,问题就得到了解决,也不需要进行其他复杂的操作。之前认为复杂的步骤,实际上并不需要。

因此,之前的思路是多余的,只需要让元素本身成为一个组,问题就可以轻松解决,可以继续向前推进,不需要再纠结于之前的复杂方案。
在这里插入图片描述

在调试器中验证调试块的工作情况

在调试过程中,设置了一个断点来验证代码是否按预期执行。目标是将元素转换为一个组,并确保所有相关的操作都能正常工作。在检查过程中,发现有些细节没有如预期那样执行,特别是在树结构中的元素没有正确地建立子元素关系。

最初,期望能够看到树中的层级结构,但实际情况却并非如此。虽然能够看到某些元素(如“模拟”元素)在树中,但并没有显示出它们的子元素,这与预期不符。特别是,有些元素应该拥有子元素,而事实上它们并没有,这表明代码中可能存在一些逻辑错误。

在继续检查时,发现有些值并没有按照预期填充,而是被错误地填充了其他类型的元素。这导致了不正确的树结构,其中元素并未被正确地组织成层级结构。

总的来说,调试过程中发现了不符合预期的行为,需要进一步深入分析原因,特别是在树结构的生成和元素关系的建立方面。

解释调试器中显示的工作情况与预期的不同

从调试过程中可以看出,实际行为并未按照预期发生。原本预计,当事件名称为“simulation_underscore_entity”时,应该会触发相应的操作,将元素放置到树中,特别是应该将该元素作为子元素添加到根组中的某个节点下。

然而,尽管事件名称已经正确设置为“simulation_entity”,期望中的树结构并没有建立起来。调用“get element from event”并没有按预期触发元素的加入,导致该元素并未被正确地插入到树的结构中,未能成为任何一个节点的子元素。

这表明,尽管事件的名称和调用看起来正确,代码逻辑可能存在问题,导致元素未能按预期正确地嵌入到结构中。这需要进一步深入分析和调试,找出未触发预期行为的根本原因。

调试 GetGroupForHierarchicalName

在调试过程中,发现了一个问题。预期的行为是,在创建和管理模拟实体时,应该通过事件的名称将“simulation_entity”正确地解析和处理。然而,虽然我们在检查模拟实体时发现了正确的事件名称,但在后续的调试过程中并未完全符合预期。

首先,事件名称的第一个下划线是被正确识别的,期望中的模拟实体名(例如“simulation_entity”)应该通过一定的逻辑处理形成。然而,这个过程中我们只看到了一个分组创建,而不是具体的元素操作。进一步检查后,发现确实如预期那样,第一次找到的元素是“simulation”。但问题出现在后续元素的处理上,尽管已经进入了正确的分组和创建阶段,仍然没有看到完全按照预期的行为执行。

最终,通过逐步调试发现,代码在处理分组时没有按照预期返回,而是继续了不必要的步骤,导致元素添加过程中出现了问题。经过修改和调整,期望能顺利将完整的元素添加到正确的分组中,且行为如预期发生。

简而言之,虽然分组和元素创建的逻辑看似正确,但在实现的过程中,某些步骤并没有按预定方式执行,导致了元素未能正确地加入到分组中。需要进一步精细化调试和检查,以确保每个阶段都正确执行。

了解其行为,解释只有 debug_variable_groups 有名称

经过一段时间的调试和思考,现在对这个问题有了清晰的理解。问题的根本原因在于,调试元素和调试组的命名方式不同。具体来说,只有当它是一个可扩展的组时,调试组才会有自己的名称。而普通的调试元素(不是组)并不会有名字,它的名称是通过事件来定义的。

具体来说,当创建一个调试组时,它会有一个名称,而如果只是创建一个调试元素(不属于任何组),它并不会有名称,只有在事件中才会赋予它名称。如果没有明确的名称,调试元素的名称将由它所包含的事件来决定,如果事件没有提供名称,那么它就只有一个唯一的 GUID(全局唯一标识符)来标识。

总结来说,调试组和调试元素在命名上有很大的区别,调试元素的名称取决于事件,而调试组则有自己明确的名称。这就是为什么看到的调试信息中有些元素没有名称的原因。

讨论可能的解决方案以使 DEBUG_BEGIN_DATA_BLOCK 中传递的名称显示出来

在这个情况下,可以考虑有几种不同的方案来处理名称的问题。一种方法是将名称提升到链接上,确保每个调试元素都始终有名称。这样可以避免每次都需要查看事件来确定名称,而是直接通过链接来确定名称。这种方法可以让名称始终是已知的,方便使用和查找。

另一种方法是让调试元素仅使用第一个事件来确定其名称,这样做看起来也是一种合理的选择。通过第一个事件来确定名称,简化了元素的名称赋值过程,而不必依赖后续的事件来更新或确定名称。

这两种方法各有优缺点,具体可以根据需求和实际情况来决定采用哪种策略。

决定将 OpenDataBlock 事件及其内容的事件存储起来,因为它将包含名称

提议是将事件存储在元素中。具体来说,在创建并打开调试块时,需要将事件存储到元素中。这样,每一个事件块(例如每一组事件,如在模拟环境中)都可以对应一个打开的数据块事件。

然后,在显示这些事件时,可以直接使用事件的名称,即打开数据块事件的名称,将它们列出显示。这种方式相对来说是比较直接和简单的,能够有效地组织和展示相关的事件信息。

回顾调试事件的绘制方式,并为打开的调试数据块添加一个案例(最初是 bitmap_id 错误)

在绘制调试信息时,假设当前过程涉及了将调试事件转换为文本,但很可能在这个过程中并没有查看到打开的数据块事件,因此并没有显示相关信息。为了临时解决这个问题,可以直接修改相关的代码,确保事件能够正确显示出来。然而,尽管这样做,结果仍然不是预期的表现,显示的内容并没有按预期放置,可能是因为在处理过程中把某些信息放错了位置。因此,需要进一步调试和调整以确保事件信息能够正确显示。

质疑为什么错误的更改似乎在输出中产生了差异,找出原因

这是有点奇怪的,因为在没有进行某些操作时,调试信息没有显示出来。实际上,问题出在之前没有选择正确的元素或者执行了错误的步骤。通过进行正确的操作后,调试信息才正确显示了出来。这让我有些放心,因为之前可能有点过于焦虑,误以为出了问题。现在明确了,问题其实并不复杂,只是没有按预期进行一些步骤。

再次思考为什么开放数据块的名称没有显示出来

问题在于,只能看到最新的事件,而没有显示出“打开数据块”的事件。这是因为系统目前的逻辑只会处理并显示最新的事件,而不是所有的事件。为了能够看到所有相关事件,需要进行相应的修改和处理。如果想要显示出这些事件,可以通过调整代码,使其能够处理并展示多个事件,而不仅仅是最新的一个。这是目前理解到的核心原因。

对 CloseDataBlock 处理进行临时更改,导致数据块结束显示出来

可以通过调整代码顺序来看到“打开数据块”的事件,方法是将事件的顺序进行反转,这样就能按相反的顺序显示数据块。然而,这种做法并不是我们真正想要的解决方案,因为这样并没有解决根本问题。实际上,目标是能够正确地处理和显示所有相关事件,而不仅仅是通过改变顺序来临时显示数据块。

向 DEBUGDrawMainMenu 添加一些调试数据块特定的处理,以便绘制数据块中的值

我们希望以一种智能的方式来展示这些调试数据元素。在遍历它们的过程中,我们需要深入查看元素内部的内容。具体来说,在调试绘制主流程中,如果某个元素没有子节点,我们就要检查它的实际内容。

如果一个元素没有子节点,那它应该包含一个调试元素(debug element)。这时,我们就可以检查这个调试元素是否是一个调试数据块(debug data block)。原先的实现中,通过 get event from link 只获取了最后一个事件,这种方式不足以判断完整的结构。

因此,我们需要改进逻辑,开始查看该元素中包含的整个事件流(stream of events)。一种可行的方法是记录对应的“结束事件”(end events),然后通过倒序查找回第一个“开始事件”(begin event),从而识别一个完整的数据块范围。虽然目前还不确定哪种方式最优,但可以先尝试这种方法,看看效果如何。

当我们打印出一个元素时,流程是:先获取它的事件,再判断事件的类型。如果事件是数据块结束(例如 end 或 close),就可以往回查找对应的开始事件。代码中可能存在命名不一致的问题(例如 begin/end 和 open/close 并存),这需要做出选择或统一。

接下来,在确定是数据块的情况下,就可以从元素中提取数据块的结构,并将其以更具层次感的方式呈现。这段逻辑将被嵌入到调试绘制流程中,在遍历过程中主动识别并展开数据块,而不是只停留在是否具有子节点的简单判断。这样我们就可以更清晰地展示调试信息的完整结构。

创建 DEBUGDrawElement 和 DEBUGDrawEvent 函数来帮助绘制调试信息

我们可以通过递归调用来改进调试元素的绘制逻辑。为此,可以创建一个类似 DebugDrawElement 的内部函数,用于绘制单个调试元素。在当前处理流程中,将原有的绘制代码提取到这个函数中,然后在需要时调用它。

在主流程中,当我们处理到一个包含调试元素的节点时,比如一个包含 OpenDataBlockCloseDataBlock 的结构,我们需要解析这个事件序列所代表的树结构。这意味着,我们不仅要单纯显示事件内容,而是要解析它们的嵌套关系,从而正确反映出数据块的开始与结束。

为了支持这一逻辑,我们还可以定义一个辅助函数,比如 DebugDrawEvents,它用于处理事件链表并执行绘制。但由于我们使用的是一种特定的事件结构,普通方式可能无法直接遍历所有事件。需要确保这些事件是 DebugStoredEvent 类型的,这样我们就能通过它们的 Next 指针进行迭代。

这里还有一个关键点:如果我们从最新的事件开始处理,就无法顺利向后解析完整的数据块结构,因为它们的顺序是关键的。因此我们必须确保是从正确的位置(比如最早的相关事件)开始,才能正确解析 OpenDataBlockCloseDataBlock 的整个结构。如果顺序颠倒,就会导致结构解析失败。

总结:

  • 我们将调试绘制逻辑拆分成函数,提升结构清晰度。
  • 在处理包含调试元素的节点时,添加解析数据块的逻辑。
  • 引入事件链表的遍历函数,并确保事件结构支持顺序迭代。
  • 注意事件顺序的影响,必须从最早相关事件开始处理。

这样一来,我们就可以更智能地展示调试信息的完整层次结构,而不仅仅是静态地列出事件。
在这里插入图片描述

讨论 debug_events 和 debug_elements 的复杂性,以及它们如何与调试信息的打印相关

确实这一部分实现稍显棘手,这是处理复杂结构时常见的现象。我们当前的结构设计存在一些天然的不对称性,主要是由于调试信息的存储方式所导致的。

我们希望能够打印一个调试元素,因为它包含了一组事件的起始信息,即“第一个事件”和“最后一个事件”。这使得元素成为查看某一段完整调试数据的合适单位。

但当我们进入某个元素内部、试图逐一遍历和打印其中的事件时,每个事件本身并不包含额外的上下文信息。事件本身并不知道它属于哪个元素,或者它是否是某个数据块的开始或结束。这种信息只存在于外层的元素结构中。这种上下文缺失使得事件处理过程变得稍显繁琐,也导致了调试逻辑上略显“别扭”。

换句话说,在结构上我们拥有“调试元素”这种聚合体,它具备范围信息(起始事件与结束事件);但我们也需要针对每一个“调试事件”进行逐项操作,而事件本身并没有这种全局信息。这种分离带来了一些逻辑上的不对称,也让代码显得有点“敏感”,小改动可能就导致不易察觉的问题。

尽管如此,这种架构在处理复杂嵌套数据时其实是一种必要的折中方案。可以接受它带来的复杂度,只要我们在实现中保持清晰的抽象层次,比如:

  • 外层通过调试元素管理范围;
  • 内层通过事件列表处理内容;
  • 保持调试元素与事件之间的清晰边界。

总的来说,虽然这一套逻辑略显“触碰即碎”,但它本质上是合理的。只要小心处理,结构本身还是能胜任任务的,我们对这种设计也是可以接受的。

更改 DEBUGDrawElement,改用 debug_stored_event 代替 debug_event,以便能够遍历下一个指针

我们接下来准备修改 debug_draw_element 这个函数逻辑,希望它能够以 stored_event 为基础来运作。当前的判断是,这么做更符合我们想要的行为。

从目前的实现方式来看,有一个感觉是:这些事件之间的链式结构,好像方向是反的。也就是说,我们现在是从“最新的事件”向“最旧的事件”链接的——但其实在处理调试显示逻辑时,我们更希望能从“最旧”走向“最新”。

然而,这里的问题在于,内存管理是从最旧的事件开始释放的,所以事件只能单向地链接(从新到旧)。这也就意味着我们不太可能改变链表的方向,除非做成双向链表。但如果真要实现可以双向遍历的结构,那就得额外维护反向指针,这会增加复杂度和内存开销。

总结一下,这种结构方向的问题基本属于不可避免的限制。我们只能接受这种现实,除非愿意为遍历便利性付出额外的存储成本。

那么接下来的目标,就是围绕当前结构,看看我们能如何处理这批 stored_event,让它们以合适的方式被 debug_draw_element 使用,并进行展示。我们可以继续深入观察具体行为,看看这一方案是否真正奏效,再决定是否需要结构层面的进一步调整。
在这里插入图片描述

将大部分代码从 DEBUGDrawElement 移到 DEBUGDrawEvent

我们现在要做的是将绘制逻辑进一步抽象和整理,让 debug_draw_element 的工作真正由 debug_draw_event 来完成。因为实际上所有的渲染工作,比如绘制各种事件类型等,其实都是 debug_draw_event 在处理。

所以接下来的步骤是这样的:

我们把之前 debug_draw_element 中那些实际进行绘制的代码提取出来,放进 debug_draw_event 里去做。然后 debug_draw_element 本身其实只负责调用 debug_draw_event,它更多起的是一个组织角色,比如找到正确的 stored_event 之类。

这意味着我们需要重新整理参数,把 debug_draw_event 所需的全部上下文信息,比如绘制的位置、颜色、当前状态等等,都正确地传进去。需要查清它依赖的所有变量,并确保在调用时传递齐全。

这样调整之后,我们的结构会更加清晰,逻辑也更符合各模块的职责划分。debug_draw_event 专注于单个事件的绘制,debug_draw_element 则更关注如何组织事件链的绘制流程。这是让调试系统更清晰、可维护性更高的重要一步。
在这里插入图片描述

更改 DEBUGDrawElement,从元素中提取最旧的事件,并确定它是否需要转发到 DEBUGDrawEvent 或执行其他工作

我们现在的思路是,优化 debug_draw_element 的逻辑结构,使其具备根据不同类型的 element 判断是否需要特殊处理的能力,特别是那些包含额外数据的元素。

具体来说,主要调整和目标如下:

  1. 判断元素类型:在 debug_draw_element 函数中,首先会判断当前处理的 element 类型。这是为了识别那些包含更多信息或需要特殊渲染逻辑的元素。

  2. 处理 profile 类别信息:像 profile 这种带有跨时间段分析数据的 element,需要比普通事件更多的信息输出,因此准备将这一类逻辑抽离出来,专门在 debug_draw_element 中处理,而不是像之前那样在事件层级里简单打印。

  3. 关注最老的事件:由于 element 是时间段的集合,所以为了得到一个完整的上下文状态,将从 stored_event 中的“最老的事件”开始分析,而不是默认只查看最近的一个事件。

  4. 处理逻辑结构调整

    • 如果当前 element 包含特定类型的事件(如 Profile 或 CloseDataBlock),就进行对应的专属逻辑处理;
    • 如果不属于这些特殊情况,则回退到默认逻辑,使用最近的事件进行常规打印。
  5. 结构更清晰:通过这套逻辑划分,我们让 debug_draw_element 专注于组织层面的判断和控制,而真正的渲染任务交由 debug_draw_event 来做。同时针对需要聚合式渲染的元素(如 Profile 或 Begin/Close 类型),可以单独处理逻辑,保持主流程干净。

总的来说,这样做的好处是清晰明确地分离职责,同时为处理更复杂的数据结构(如嵌套数据块、时序分析等)打下基础,让调试渲染系统更加健壮和易于扩展。

在这里插入图片描述

通过将必要的变量传递给新函数来修复一些编译错误

现在我们需要整理一下 debug_draw_element 函数调用过程中所依赖的外部数据,确保其能在新结构中正确运行。当前遇到的问题主要是在获取和传递所需参数时出现遗漏或混乱,因此我们对所需内容进行一次梳理。

所需依赖总结如下:

  1. DebugState 状态对象
    这是调试系统的核心上下文,几乎所有调试渲染函数都会需要它来获取当前状态、记录信息或查找对应资源。

  2. 布局信息(Layout)
    用于控制调试信息的可视化布局。涉及坐标、缩进、字体行数等排版内容。如果不传递它,那么元素的绘制就无法对齐到正确的位置。

  3. 当前遍历的树结构(Tree)
    当前正被遍历的调试变量树或事件树,里面包含了结构信息,如父子关系等。这个信息原本应该在 layout 或者上下文中被传递,但现在发现并没有,所以需要手动传入。

  4. 打开状态(Open/Collapsed)信息
    控制每个调试节点是否展开,在绘制时判断是否需要深入展开并继续绘制子节点。它可能与 UI 状态相关,也可能与调试系统内部记录状态有关。

遇到的问题:

  • 当前 layout 中似乎没有包含树信息,这使得我们需要手动传递当前树;
  • 若不传入当前树结构,就无法访问该 element 的子结构或在绘制过程中展开节点;
  • 绘制函数涉及多个嵌套层次,传参链条容易断裂或遗漏。

下一步操作建议:

  • 显式传入当前树结构作为参数;
  • 检查 layout 是否真的应该承载树的上下文,若合理可考虑将其封装进去以避免传参繁杂;
  • 确保 debug_draw_eventdebug_draw_element 都能获取到它们各自需要的数据结构(包括 state、layout、tree 等);
  • 考虑对 layout 或绘制上下文结构进行微重构,使其具备自包含的绘制环境,简化调用。

总结:

我们正在将 debug_draw_element 从一个简单的事件包装层提升为结构性的绘制入口,因此它需要的上下文信息变得更加丰富。需要对其依赖进行整理,确保逻辑传递清晰,从而支持更复杂的调试结构绘制行为。这是调试系统设计中常见的“上下文传递”问题,处理好这一点将显著提升系统的健壮性和可维护性。

在这里插入图片描述

将 debug_tree 添加到布局结构中,并在 DEBUGDrawMainMenu 中设置它

我们决定将所有必要的上下文信息整合进 layout 结构中。当前已经发现 debug_state 其实也已经存在于 layout 中,因此进一步推理得出,layout 才应该是承载所有绘制所需上下文的地方。这种做法不仅逻辑清晰,也能简化参数的传递,避免函数调用中不断手动传递冗余的内容。

为实现这个目标,我们进行了如下调整:


结构性调整与设计思路:

  1. 将当前正在遍历的 tree 对象纳入 layout
    添加字段 debug_tree(或类似命名),专门存储当前操作的树结构,这样所有绘制函数只需接收 layout 即可自动访问树节点数据,无需每次手动传入。

  2. 在上层设置 layout.debug_tree = 当前使用的树
    在进行绘制之前,把当前使用的调试树直接写入 layout 中,以便后续所有操作都能通过 layout 获得一致的上下文环境。

  3. 函数参数简化
    所有调用 debug_draw_element 或类似函数的地方,统一改为只传递 layout。从而不需要再单独传入 treedebug_state,全部从 layout 中提取。


技术细节改动:

  • 所有需要访问 debug_state 的地方统一改为从 layout->debug_state 获取;
  • 所有需要访问当前树 tree 的地方改为从 layout->debug_tree 获取;
  • 函数内原本使用 . 操作符访问对象的地方,统一替换为 ->,以适应指针传参;
  • 每个绘制相关的函数都接收一个 layout* 指针并从中提取所有所需的上下文变量;
  • 避免冗余传参,使得代码结构更清晰可维护。

总结:

通过将 debug_state 和当前操作的 tree 合并进 layout,我们有效地将绘制过程中需要的所有上下文信息集中管理,从而显著提升代码整洁度与可扩展性。这种设计使得各层绘制函数之间的参数传递变得更轻量,逻辑更自洽,也为后续调试功能的扩展奠定了良好基础。
在这里插入图片描述

思考是否也将 debug_variable_link 添加到布局结构中,但选择重新构建周围的代码,改用 debug_id

我们意识到在进行绘制处理时,关于 debug ID 的管理和事件关联存在一定混乱,原本的方式显得冗余甚至有些“硬编码”——尤其是在事件已经存在的情况下,还要再次构造 debug ID 并查找事件,这种操作实际上是重复且多余的。因此我们对流程进行了理清与重构:


核心调整与思路:

  1. 将当前事件对应的 debug_id 明确传入绘制函数

    • 不再通过其他手段“反查” debug_id,而是直接在遍历元素时生成并传入。
    • 避免之前那种通过路径拼接重新构造 ID 再查找事件的做法,简化流程也提高效率。
  2. layout 中存储当前所处的调试层级 link

    • 每向下进入一个子层级时,更新当前 layout 中的 link 信息;
    • 这代表了“当前位置”的上下文,有助于在需要动态判断、查找或操作当前调试对象时不丢失路径。
  3. 替换原本多余的 get_event 调用逻辑

    • 原本从 ID 重新查找 event 的方式过于繁琐,其实事件本身我们已经手头有了,没必要再去“反推”;
    • 所以将相关逻辑简化,只保留直接使用已有事件的方式。
  4. 确保 debug ID 的唯一性

    • 每个调试元素对应的 debug ID 需要在树和路径中唯一,不能有混淆;
    • 因此构建 debug ID 的逻辑放在遍历点生成处,确保递归遍历过程中每个元素的 ID 是明确可控的。
  5. 统一 UI 交互绑定逻辑(item interaction)

    • 每个元素的 UI 交互(如点击、拖拽)绑定都依赖于 debug ID;
    • 所以统一方式将 ID 与元素类型一起传入 interaction 构造器中。

技术细节上的处理:

  • debug_draw_element 和相关函数的调用方式统一为传入 debug_id
  • 使用当前 layout 中的 link 生成唯一 debug_id
  • 进一步将每个子元素的递归遍历时生成独立 debug_id,以便后续 UI 操作定位到具体元素;
  • 原本不必要的 get_event_by_id 函数已不再需要;
  • 清除原有“临时 hack”式的绑定流程,改为按结构、路径严谨生成 debug 交互 ID。

总结:

我们对 debug 绘制流程进行了系统性梳理与重构,使得 ID、事件与 UI 交互三者逻辑上更统一、功能上更独立。这种方式清晰可控,不仅提升运行效率,也方便后续扩展。过去那种“反查式绑定”的写法过于繁琐且易错,现在则转向主动式传递与生成,整体逻辑更严谨清晰。

在这里插入图片描述

更新交互代码,将函数 VarLinkInteraction 更改为 EventInteraction,传递 debug_id 和 debug_event,而不是 debug_tree 和 debug_variable_link

我们在重新梳理交互系统的结构时,决定对 item interaction 的定义和使用方式进行更合理的调整,以提高代码逻辑的清晰度和可维护性:


关键改动与逻辑说明:

  1. 引入更语义化的命名:从 VarLinkInteraction 改为 event interaction

    • 原有的 VarLinkInteraction 命名模糊,难以体现交互对象的实际含义;
    • 改为 event interaction 更贴切,因为我们本质上是在处理某个具体事件对应的 UI 交互。
  2. 构造交互时直接传入 debug_id

    • debug_id 是事件在整个调试系统中的唯一标识;
    • 所以交互构造函数应该直接接收这个 ID,而不是在函数内部重新生成或查找;
    • 这样一来,每个交互实例的来源就变得清晰明确。
  3. 保留传入 event 本体的能力

    • 暂时没有将 stored_event 作为参数直接传入,但逻辑上这样更合理;
    • 当前阶段先保留简单参数,后续可以轻松调整为直接传事件实例,减少对 ID 的依赖操作。
  4. 调用逻辑更简洁

    • 新的方式下,我们只需提供 debug_id 与其他最小必要数据,即可完成交互绑定;
    • 避免了以往“边创建交互边构造 ID”那种重复且混乱的方式。
  5. 统一所有原 VarLinkInteraction 的调用为 event interaction

    • 所有引用点进行了统一替换;
    • 这些交互行为现在都基于事件 ID 运作,并不需要其他额外上下文;
  6. 树状结构无需再为交互生成额外状态

    • 因为交互行为已与具体事件绑定,调试树本身不需要额外逻辑去维护交互状态;
    • 减轻了树遍历与交互系统之间的耦合程度。

结构优势总结:

  • 命名清晰、逻辑分明,交互行为与事件结构直接关联;
  • 事件的唯一性(通过 debug_id)得到全面体现;
  • 事件交互的构造与调用更加轻量、直观;
  • 后续若需要更丰富的交互行为,已具备良好扩展点(如传入 stored_event);
  • 整体设计去除了冗余逻辑、避免状态重复,提升了整体系统健壮性与可维护性。

最终,我们的目标是让每一个事件都可以轻松而精准地绑定到调试交互中,既不依赖外部冗余状态,也无需动态反查,做到清晰、独立、灵活。现在的实现方式朝着这个方向已经迈出一大步。

在这里插入图片描述

由于交互更改使得 debug_tree 在布局结构中变得不必要,已将其移除

在重新审视代码结构后,决定不再需要将某些信息传递到代码的其他部分。这一改动使得代码变得更加简洁和高效。具体来说,避免了将不必要的参数传递到系统的其他部分,这样不仅减少了代码的复杂度,也降低了不必要的耦合度。


主要改动:

  1. 避免不必要的传递:某些信息(如 debug_id)不再需要被传递到代码的其他部分。这意味着在处理某些交互或事件时,已经不需要在其他地方重新生成或获取这些信息。

  2. 简化代码:通过去掉不必要的传递步骤,代码的执行流程变得更加直接,避免了冗余的传递和计算,减少了潜在的错误源。

  3. 提升效率:减少了不必要的依赖,使得代码在执行时可以更加高效。每个模块只关注其核心任务,避免了过多的外部干预。

  4. 提升可维护性:通过消除不必要的传递和依赖,代码结构更加清晰,后续的维护和扩展也变得更加容易。


总结:

通过不再将某些信息传递到系统的其他部分,代码变得更加简洁和高效。这样的结构不仅提高了执行效率,还提升了代码的可维护性。

在这里插入图片描述

完成修复剩余的编译错误

在对代码进行调整后,进行了以下几项主要修改:

  1. 事件处理的更改:不再处理某些事件类型(如体育事件)。本来应处理第三方事件,但是因为某些问题,决定跳过这一部分。目前的修改是,使用存储的事件(stored events)和调试事件(debug event),这样可以保持现有代码的稳定性。

  2. 渲染组的优化:由于调试状态已经包含了渲染组信息,因此不再需要将渲染组作为参数传递到其他部分,简化了代码并避免了不必要的传递。

  3. 开始元素矩形处理:不再需要传递ampersand id,因为之前已经通过指针传递,这样减少了冗余操作。

  4. 视图获取:考虑到有时会多次获取视图,决定优化这部分代码。可以通过创建视图来避免重复查找,从而提高效率。如果需要,也可以将视图传递给相关部分,这样也能避免重复的查找操作。

  5. 交互处理:对于项目交互(item interaction)进行了简化,现在它仅需要传入调试ID(debug id)。不过,目前并不清楚这些交互是否以前做了什么特殊处理,尤其是在处理profile时。因此,决定暂时不处理profile部分,直到有更多的实现细节或需求明确之后再进行调整。

  6. 遗漏的交互处理:目前没有为profile类型的交互实现特定的处理。虽然预计会为profile实现一些选择或交互功能,但因为当前并不清楚是否有特定需求或功能,决定暂时保留这部分,等待后续进一步开发。

总结:

这些修改主要目的是简化代码结构,避免冗余和无效的操作。通过优化事件处理、渲染组传递和视图获取,代码变得更加简洁,同时也为后续可能的扩展或调整保留了灵活性。
在这里插入图片描述

删除现在不再使用的 GetEventFromLink 函数

当前正在重构的部分主要围绕事件的处理方式,进行了以下详细的调整和思考:

  1. 取消 get_event_from_link 的使用:原本的逻辑中存在一个用于从链接中获取事件的函数,但现在认为这种方式不再适合当前的需求。当前的设计更倾向于直接使用“最近的事件”(most recent event)来进行处理,而不是通过链接去查找。因此,决定去除 get_event_from_link 相关的调用和函数本身,简化调用路径。

  2. 函数参数简化:调整后的函数只接收 debug_id 和对应需要绘制的元素(element)作为参数。意图是将其作为基本输入,确保绘制函数能清晰地知道目标。

  3. 关于 element 的命名冲突:在代码中存在两个不同含义的 element,导致当前函数不知道具体应处理哪个,这造成了混淆。这也是重构中发现的一个潜在问题,意味着需要进一步澄清命名,或者进行变量重命名以避免语义上的冲突。

  4. 当前策略:现阶段,采取保守做法,即默认使用“最新事件”作为处理依据,后续可能会根据具体需求增加对其他类型事件的支持。

补充说明:

此次改动是为了让绘制逻辑更加明确和直接,避免无效或多余的中间步骤。通过移除get_event_from_link,可以避免代码中对链式结构的过度依赖,使逻辑更聚焦于“当前需要展示什么”,而不是“在哪里找到它”。

下一步的工作将集中在明确两个 element 的语义差异,并进行命名梳理,使调用逻辑更具可读性和稳定性。
在这里插入图片描述

运行游戏

现在我们已经处于一个可以开始显示调试信息的位置了,因为我们已经重置了相关的代码逻辑。尽管现在时间不多了,已经快到结束的时候,但目前为止我们已经完成了一些关键的准备工作。

我们重构了调试块的处理方式,使其更简洁高效,不再依赖之前复杂的处理流程。通过将调试块与特定元素进行绑定,现在我们可以在后续的事件处理过程中,准确地将数据输出到对应的位置上。这个流程的统一也为我们后续构建更完善的调试系统打下了基础。

总的来说,目前的代码状态已经可以支持我们开始将这些调试数据正确显示出来,后续只需要继续完善显示逻辑即可。尽管今天时间有限,但整体进展良好,接下来可以集中精力处理剩余的细节,让整个调试系统更完整可用。

开始向 DebugDrawElement 函数中添加代码,循环遍历调试数据块,并为其中的每个值调用 DebugDrawEvent

现在我们需要实现的是在绘制调试信息(debug_draw_element 或类似流程)时,能够遍历某个特定数据块(data block)内部的所有内容,并将它们一一绘制出来。

我们想要做的事情是:

  • 对某个调试数据块内部的内容进行循环处理;
  • 每一项都需要进行绘制;
  • 每一项在循环过程中都会有一个不同的调试 ID(debug ID);
  • 因此每次循环处理时,都需要更新调试 ID,以便绘制器能识别每个独立元素;
  • 调试系统应该已经有方法能够返回当前数据项的 debug ID,或者我们可以在遍历数据项时按需生成或关联这些 ID;
  • 总体而言,是想让某个 debug block 成为一种容器,内部可以包含多个调试项,并在绘制时逐一展开处理;
  • 每一项都是对应一次 begin-data-block 调用所生成的内容;
  • 所以整体流程应该是:在处理这个 block 对应元素时,进入子项遍历 → 提取调试数据 → 设置 debug ID → 执行绘制。

这样做的目标是使得调试界面上能清晰展示每个实体或数据项下的调试内容,同时结构清晰、可展开、可管理,最终让调试系统更加直观和易用。接下来,只需要在绘制逻辑中整合这个循环处理流程即可实现上述目标。
在这里插入图片描述

将 DebugIDFromLink 更改为 DebugIDFromStoredEvent

在调试功能中,现在我们希望增加一种新的方式来创建调试视图(例如用于 UI 展示的树结构节点)。这次不再是基于某个特定的链接(link)来创建,而是基于一个具体的已存储事件(stored event)。这意味着我们现在需要传入整个事件树(tree),以便在构建时使用。

基本思路如下:

  • 在已有的调试视图创建方式基础上,添加一种新的路径;
  • 这个新路径通过传入整个事件树(event tree)来初始化;
  • 这种方式的差异点在于,它所绑定的是某个特定的已存储事件,而非像之前一样通过某个静态链接或单点标识;
  • 在实现层面,可能会增加一个新的构造逻辑或分支结构,识别并处理这种基于事件的创建方式;
  • 在数据结构中,也许需要明确标识这类由 stored event 创建的节点,以便区分;
  • 具体怎么处理,还要在实际编码过程中边试边看。

这个改动的最终目的是为了让调试系统更灵活地支持多种数据来源的节点生成方式,使其既可以静态挂载,也能动态响应事件数据,提升整体结构的通用性和适配性。我们暂时把这个构思整理好,接下来在代码中尝试实现这一分支。

在这里插入图片描述

向 DEBUGDrawElement 添加一个 debug_tree 参数,以便将其传递给 DebugIDFromStoredEvent,并完成其数据块处理代码

目前的工作目标是通过遍历事件来输出数据。在此过程中,有几个关键点需要注意:

  1. 树和事件 ID: 在当前设计中,事件的唯一标识是由树结构和存储事件组成的 ID。我们将根据这个 ID 来追踪每个事件的具体内容。

  2. 遍历事件: 我们的目标是遍历事件流中的每个事件,尽管并不是所有事件都会被处理。首先,我们会从最旧的事件开始,然后依次查看后续的事件。

  3. 处理特定事件类型: 在遍历过程中,如果遇到的是“open data block”事件,我们将记录下这个事件作为当前“最后一个打开的块”。这个块是我们接下来处理和显示的关键数据块。

  4. 从最后的打开块开始显示: 通过定位到“最后一个打开的块”,我们可以确定从哪个事件开始输出数据。我们将从这个位置向后遍历,直到所有相关的数据块被处理。

  5. 显示数据: 通过这种方式,最终的目标是将这些事件的数据输出到界面上。每个事件将被处理并显示出来,通常会通过绘制图形或其他可视化方式展现。

总结来说,通过从最旧的事件开始,寻找每个“open data block”事件,并从最后一个打开的块开始遍历,我们将能够逐步输出存储事件的数据。这些步骤基本上构成了当前设计的主要流程。

在这里插入图片描述

不知道哪里有问题
在这里插入图片描述

注意到与 debug_id 每帧变化有关的问题

目前的调试数据显示的是“最后一个数据块”,这本身是符合预期的。但在实际操作过程中,发现了一些细节问题存在逻辑不稳定或混乱的情况,尤其是在处理实体 ID 和可视化时。

主要问题如下:

  1. 存储实体 ID 不稳定:
    当前使用的存储实体(stored entity)ID 是在每一帧中不断循环分配的,这就意味着它并不是一个稳定或持久的标识符。导致的问题是,每次渲染或更新后,同一个实体可能会对应不同的 ID,这会使得调试 ID 无法正确区分不同的数据,尤其是在多帧对比或多次记录时。

  2. 调试 ID 不准确:
    由于使用了不稳定的 ID,造成调试系统在对比两个不同但看起来相似的事件时,无法准确地区分它们。调试 ID 实际上并没有能明确指出每一个数据记录的来源。

  3. 可能的改进方案:
    一种改进思路是考虑使用“事件来源的位置”作为唯一性的一部分。例如,使用打开数据块(open data block)的位置或其 ID,加上来源坐标(或类似的网格/GUID 位置信息),来组合生成一个更稳定的调试 ID。

  4. 额外的层级 ID:
    当前的系统中可能还缺少一个额外层级的调试 ID,即使 open data block 自身有 ID,也难以唯一标识其下属的每一个事件。理论上可以构造一种“父级 ID + 子项索引”的结构来实现层级唯一性识别。

  5. 使用 Debug ID 的范围问题:
    回顾调试 ID 的使用逻辑,发现当前系统中实际上只使用了 debug ID 的前半部分,这意味着当前逻辑还没有充分支持细粒度的事件区分。这也可能是问题出现的根本原因之一。

总结:当前的问题本质上在于调试系统使用了不稳定的 ID 做数据标识,导致事件区分逻辑出现模糊和冲突。解决方向应当是引入更稳定的标识信息,如数据块来源位置、帧数、或构造更完整的调试 ID 层级结构,以便能精确追踪每一个具体的调试数据项。

通过使用事件 GUID 获取数据块值的稳定 debug_id 来解决问题,而不是使用不稳定的 debug_stored_event 地址

当前的思路是在构建调试 ID(debug ID)时,尝试解决事件在可视化过程中难以唯一标识的问题。具体内容包括以下几点:


1. 调试 ID 的组合方式思考

我们目前想要解决的核心问题,是在某些特定情况下,如何为调试数据生成唯一且有意义的调试 ID,以便于后续在调试界面中进行区分和选择。

为此,提出的做法是将两个关键信息合并:

  • 事件自身的信息:例如该事件所在的(GUID)位置,可以理解为事件在可视化数据结构中的位置。
  • 所属的数据块(data block)信息:用于表示该事件是属于哪个数据块中的,给调试 ID 增加一个“父级来源”。

这样合并后生成的调试 ID,就能够明确区分“相同位置但不同数据块的事件”,或者“相同数据块中不同位置的事件”。


2. 实现思路

构建调试 ID 时采用合并的方式:

  • 首先从当前事件中信息(例如 event.GUID)。
  • 然后确定当前事件所属的数据块标识。
  • 将两者组合,生成一个结构上更复杂但语义更清晰的调试 ID。

这避免了当前因为使用的 ID 不稳定而造成调试项错位或混淆的问题。


3. 当前的限制与思考

虽然这种方式能带来更好的唯一性和可区分性,但也有几个点值得注意:

  • 调试 ID 更复杂:因为是组合结构,所以在调试系统中后续使用(如查找、显示、对比)时会比原来略微繁琐,需要处理合成结构。
  • 实现细节需明确:比如合并是拼接字符串、构造结构体,还是其他方式?还需要在实际中进一步规范。
  • 可扩展性问题:未来若加入更多维度信息(如时间戳、帧编号),这个结构可能会继续增长,需要事先考虑扩展策略。

4. 暂定实现方式

当前建议是:

  • 暂时用事件的 GUID 信息作为调试 ID 的一部分。
  • 与当前所在的数据块 ID 进行合并,生成新的复合型调试 ID。
  • 后续如有更多区分维度,可以考虑扩展该结构。

总结

整体思路是:通过将事件的网格位置信息与其所在数据块的标识结合起来,生成具有唯一性和语义清晰的调试 ID,解决目前在可视化调试时事件无法准确标识的问题。这种方式简单有效,是当前阶段较为合理的方案。未来可根据调试需求进一步扩展 ID 结构以增强灵活性与表达力。
在这里插入图片描述

修复一下bug

在这里插入图片描述

在这里插入图片描述

什么是调试合并器?

整个系统的核心思想是构建一个高效的调试架构,用于将游戏运行时的信息从多线程环境中快速、有序地输出并可视化,以便后续分析和调试。

我们实现了一个调试缓冲区(debug buffer),这是一个线程安全的结构,允许多个线程在运行时同时向其中写入数据,例如:

  • 某个时间点开始执行某个函数
  • 某个时间点结束执行某个过程
  • 某个关键变量在某时刻的数值变化

这些信息以原始数据的形式被写入缓冲区,但此时它们没有实际含义,仅仅是被记录下来。

debug collator(调试整合器) 则是关键所在。它的职责是:

  • 将这些分散的、原始的调试数据汇总、归类
  • 按照一定的结构将它们组合成可以处理和展示的格式
  • 为后续调试提供清晰的、结构化的信息基础

这是整个架构中最关键的部分之一。它将混乱的调试数据变成了可以被 UI 显示和交互的内容。

我们现在已经完成了大部分功能的整合,剩下的工作主要是把这些数据做成一个友好的用户界面(UI),让我们可以更方便地进行调试,例如:

  • 可以选择某个实体(Entity)查看它的属性变化
  • 可以看到特定时刻某个 bitmap 的内容
  • 能够以清晰、有条理的方式展示调试信息

最终目标是打造一个稳定可靠、易于操作的调试工具,用于持续地监控和分析游戏运行时的行为,从而提升开发效率与程序稳定性。我们已经非常接近这个目标了,只需要再向前推进一点。

游戏开发人员通常如何处理临时/非发布的音效?

关于临时的、非发布用的音效处理方式,有一些讨论内容主要集中在以下几个方面:

我们通常不会在引擎层面上对“非发布音效”(temporary / non-shipping sound effects)与普通的正式音效做出技术上的区别。从引擎的角度来看,这两者在代码实现和处理机制上是完全一样的,不存在什么特殊路径或逻辑去标识“这是临时用的”音效。

不过,如果问题指向的是更细节的东西,比如音效的授权或法律许可,那么情况就会有所不同。在这种情况下,所谓“非发布音效”可能是指那些暂时用来占位的、尚未获得正式授权的声音文件。它们在开发过程中仅供内部使用,之后在游戏发布前会被正式替换。这种操作通常由内容制作流程或者资源管理系统来控制,而不是引擎本身负责管理。

对于这种情况,团队通常会:

  • 使用明确的命名或路径(如 /temp_sfx/placeholder_*.wav)来标识这些临时音效;
  • 在构建发布版本时,通过资源打包脚本或过滤机制将其排除;
  • 可能在构建工具或版本控制中加上标记,确保这些音效不会被意外包含进最终的交付内容;
  • 在法律层面上,确保临时音效不会被公开传播,以避免版权风险。

简而言之,从技术上来看,引擎并不区分临时音效和正式音效;区别主要出现在内容制作流程、资源管理系统以及发布流程中。是否带有授权、是否用于正式版本,是内容团队和发行流程决定的事情。引擎本身并不会“知道”一个音效是不是临时用的。

你怎么看待在有限资源的约束下工作?我认为这迫使程序员写出更精简的代码,并且迫使他们真正关心并了解自己在做什么…

我们认为在有限资源的约束下进行开发,其实是非常有益的。这种限制迫使我们更认真地对待所编写的代码,避免写出粗糙、低效的实现。它推动我们在每一个细节上都要有所考量,比如内存使用、性能瓶颈、模块结构等,而不是随意堆砌功能。

这种资源紧张的环境,反而提升了代码质量,让我们更关注整体架构的合理性、效率和可维护性。我们必须真正理解自己在做什么,而不是依赖大量抽象层或滥用资源去“掩盖”问题。这种思维方式对成长非常有帮助。

不过,也承认有时某些编程语言本身会妨碍这种做法。它们可能默认引入很多抽象,或者设计上对底层资源控制不够透明,这让我们即使想“在意资源消耗”,也很难做到。这是一种现实存在的技术阻力,但并不改变我们对受限开发环境正面价值的认可。

我们能去掉一个构建错误(关于结构体反射的错误)吗?看起来它并不影响任何东西?

我们希望移除掉 Builder 或者处理结构 introspection 的部分内容,因为这部分看起来并不会产生实质性的影响。理论上是可以实现的,但前提是我们需要修改现有的简单预处理器。

目前的预处理器会扫描代码并识别类似 INTROSPECT 这样的关键字。问题在于,它并不够“聪明”去理解它当前所处的位置——例如,它无法识别自己是否正位于一个 #define 指令内部,也就无法避免在不应该处理的上下文中错误地处理了 introspection 的语法。

比如当预处理器遇到这样的结构:

#define SOMETHING INTROSPECT(MyStruct)

它仍然会尝试解析 INTROSPECT(MyStruct),从而产生了不必要或错误的行为。为了解决这个问题,我们需要让预处理器具备“上下文意识”——也就是说,它必须知道当前是否处于 #define 的定义区域内。如果是,就应该跳过 INTROSPECT 的处理。

我们可以采用一种简单的“取巧”方式来实现这个目标:在进行解析的时候,如果识别到当前 token 是 #define 中的一部分(例如,只是识别到了某种预设符号或大写的关键字),那就跳过后续的 introspection 逻辑。比如:

if (token == "IGNORED") {
    // 跳过 introspection
}

这个方法本质上是用极简的规则硬编码了一种识别手段,不是最优雅的方式,但对于当前的预处理器结构来说,是一个可以接受的权宜之计。

总体上,这整个 introspection 预处理逻辑是一个简化和实验性质的实现,所以对其进行一些临时性调整、绕过或逻辑修补,是可以接受的,尤其是在我们只是想验证或演示基本功能的阶段。最终,如果需要正式采用或优化,还需设计更健壮、上下文感知更强的解析系统。

在这里插入图片描述

在这里插入图片描述

快速制作临时音效的方式是什么?例如,弄清楚它们应该在何时何地播放?

我们在制作临时音效的过程中,一般是为了确认某个音效在什么时间、什么场景下触发,以及其效果是否符合预期。处理这类需求的方式其实很灵活,具体如下:

我们通常会直接自己制作一个临时音效来使用。如果需要临时测试某个行为或交互是否合理,最直接的方法就是自己快速做一个声音用来测试。

如果暂时没有条件自己制作,也可以临时从网上下载一些与目标音效大致相似的素材——不需要完全一致,只要能模拟效果即可。这样做的目的是尽快看到音效播放的时机和感受,而不是追求最终品质。

播放这些临时音效时,我们可以直接把它们插入代码中,绑定在需要测试的事件上,从而观察播放效果是否与行为逻辑匹配。

不过,如果完全没有制作音效的经验,或者不知道该如何处理音效文件、添加到引擎中、触发播放等,那这个流程确实会比较困难。因为这涉及基础的音频处理技能和工具的使用。

所以,总的来说:

  • 我们倾向于自己做一个简易的临时音效;
  • 或者快速找一个网上现成的素材来代替;
  • 核心目的是验证“什么时机播放”以及“播放的感觉是否合理”;
  • 如果不具备音频处理能力,可能就需要依赖他人或工具提供支持。

这些临时音效最终都会被更高质量、正式制作的音效所替换。临时音效的意义主要在于“开发调试”阶段的测试与验证。

不剥离未使用的代码就是《GTA Hot Coffee》事件的原因

我们讨论了一个经典的例子:如何因为“未移除的无用代码”而导致《GTA Hot Coffee》事件的发生。这确实是现实中的案例,也反映出评级制度的某些荒谬之处。

如果仔细想想,这件事本质上很讽刺——在一款游戏中,玩家可以选择与妓女发生关系,甚至之后杀掉她并把钱拿回来,这种行为是允许的,依然可以获得“M(成人)”评级。但当游戏中出现了一个角色与自己女友发生“自愿性行为”的片段,只因为存在了“裸体”,这部分就成了争议焦点,被认为需要更高一级的限制评级。

这就揭示出评级机构(例如ESRB)的评判标准有多么荒唐和双标。他们将“暴力”、“违法行为”视为可接受的娱乐内容,而对“自然、合法的人类性行为”则采取高度敏感甚至敌对的态度。

这不仅是一种道德上的扭曲,更反映出社会在媒体内容上的价值判断严重失衡。只要是暴力的,无论再极端都可能通过;但只要牵涉到真实、自然的人类情感与肉体行为,哪怕是合法且正常的,就可能遭到极端限制。这是整个评级体系根本性的问题。

正因如此,我们非常反感这种制度,它完全不能反映出一个合理、健康、基于道德逻辑的价值观。它让我们看到所谓“监管”在某些方面是多么虚伪和令人厌恶。这也是我们一直反对以MPAA、ESRB等为代表的评级机构原因所在——因为它们的存在只是在进一步扭曲内容创作的自由和道德表达的正当性。

如果没有编程工作,你会做什么工作来谋生?

如果没有程序员这份工作,我们可能会选择从事法律相关的职业,比如成为一名法官。之所以会想做这个工作,是因为可以参与判断和裁决,这个过程具有一定的权威性和逻辑性,符合我们对于理性、公正处理事务的兴趣和倾向。

相比其他职业,法官的角色能够直接参与社会秩序的维护,也具备一定的独立性和尊重感。在这个职位中,可以以更宏观的角度来分析问题、权衡证据、做出判断,这种决策性的工作方式和编程中逻辑严谨、结构清晰的思维模式有相通之处,因此会觉得有吸引力。

所以如果不能继续从事编程类的工作,法律职业,特别是担任法官,会是一个非常有吸引力的备选方向。

你怎么看待 DLC 呢? 😉

我们对DLC(可下载内容)的看法是:如果它是作为游戏的扩展包存在,我们是可以接受的。也就是说,如果DLC是建立在原有完整游戏基础之上的额外内容,扩展了游戏的玩法、世界或内容,这样的形式是合理的,也是一种可持续的资金支持方式,尤其对独立游戏开发来说尤为重要。

但我们不喜欢那种把一个游戏人为地切割成许多小部分,再让玩家分别购买的做法。这种方式太过功利,缺乏艺术性,也不利于创作出具有完整性和深度的优秀作品。它更像是汽车销售那种选配清单式的消费方式,而不是一种艺术创作的呈现方式。

这种做法虽然可能迎合了“功能满足式”的消费需求,比如“我只想要这个角色的终结技,所以我单独买了这个DLC”,但这并不会产生真正有艺术价值的、统一而深入的游戏作品。这样的内容,也许可以像爆米花电影一样提供简单刺激,比如迈克尔·贝的《变形金刚》,但不会带来类似《低俗小说》或《莎翁情史》那样富有深度和艺术张力的体验。

我们更支持那种当游戏本身就设计得可以扩展,而且玩家群体也乐于继续体验这类扩展内容时,通过DLC的方式来增加游戏内容。例如像《以撒的结合》这种类型的游戏,在原始版本发布后,如果反响很好、玩家也希望有更多内容,那么追加一个内容丰富的付费DLC是合理的。这种方式可以在不破坏作品整体性的前提下为游戏提供后续开发资金的来源。

所以,我们并不反对DLC的存在,但不支持将游戏内容高度碎片化的做法。部分DLC形式过于商业化、缺乏创意完整性,这样的发展模式不健康,也不利于游戏作为艺术作品的成长和进化。

你玩过 MMO 吗?如果玩过,你觉得他们的升级方式怎么样?

MMO(大型多人在线游戏)。对于MMO类型的游戏,我没有玩过,所以对这类游戏没有什么看法和经验。没有亲自体验过,就很难谈论它的优缺点。


网站公告

今日签到

点亮在社区的每一天
去签到