欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 创投人物 > 游戏引擎学习第219天

游戏引擎学习第219天

2025/4/19 8:32:00 来源:https://blog.csdn.net/TM1695648164/article/details/147161537  浏览:    关键词:游戏引擎学习第219天

游戏运行时的当前状态

目前的工作基本上就是编程,带着一种预期,那就是一切都会很糟糕,而我们需要一个系统来防止它变得更糟。接下来,我们来看看目前的进展。

简要说明昨天提到的无限调试信息存储系统

昨天我们完成了内存管理的工作,现在可以无限制地存储调试信息。当我们用尽存储空间时,可以设置一个最大内存使用量。一旦内存满了,系统会回收旧的内存并丢弃最早存储的信息。这是一个非常实用的功能。

确定今天的目标:将调试系统整合在一起

现在,我们将尝试将之前分散开发的调试系统的各个部分重新整合在一起。

现在,我们已经有了一个清晰的思路,知道该如何存储这些信息。首先,我想恢复的是层次结构。我们将使用层次结构来组织信息,因为层次结构能让信息更易于访问,这符合人类的习惯。同时,这也是我们没有其他选择的原因,为了适应这种结构,我们也只能这样做。

开始工作在分层调试事件UI上

我们已经存储了所有这些事件,现在需要做的就是构建一些用户界面元素,将它们按照层次结构放入一个严格的等级制度中。在这个等级制度中,事件没有任何向上流动的可能性,除非那个无所不能的用户决定干预,将其重新定义并进行调整。

描述调试事件如何按层次结构进行分组

我们想要做的是,当第一次遇到一个事件时,将其根据来自代码中的位置进行分类。这个过程就像是一种不考虑个体的行为,不管这些事件的内部含义是什么,我们都按照来源将它们归类。我们对它们的内部特征毫不关心,只根据它们来自哪里,把它们放进相应的地方。

当我们创建这些事件元素时,如果它们包含层次信息,我们希望将这些事件整合进层次结构中,接下来,我们将保存并尝试进行这些操作,尽管这些层次结构可能会有所问题,我们仍然坚持进行这个过程。

将根树的术语更改为根组

首先,决定不再将其强制设定为树结构,而是回到根组的概念。并没有必要坚持称其为树,只需要定义为一个组。这个组的构建方式可以和树结构类似,但并不一定非得是树形结构。可以说,这就是一个组,树形结构可以选择去展示这个组,如果它不想展示,也没有问题。

接着,我们将创建这个根组,就像之前做的一样,直接生成一个根组。这一过程是为了确保组能够按照我们想要的方式构建,而不需要强制规定其为树状结构。
在这里插入图片描述

重新启用新系统的层次结构生成

我们开始重新建立那些可以创建层级结构的部分。基本上所有需要的东西都已经具备,只是需要将它们“复苏”一下,使其适应新的系统结构。我们需要对其进行一种“普鲁克拉斯提安式”的改造——不管这些函数最初是怎样的、不管它们的愿望和理想是什么,我们都要强行让它们符合我们的设定,就像把人放上普鲁克拉斯提斯的床,锯掉或拉伸肢体直到符合长度。

接下来我们查看两个关键函数:AddVariableToGroupCreateVariableGroup。其中 CreateVariableGroup 相对简单,没有太多内容,它基本上就是一个链表,因此我们认为可以继续沿用之前的方式。变量组本身是静态的、固定的内容,因此可以继续以相同的方式分配内存。

不过,接下来我们开始考虑未来的改进方向。我们希望这些结构能够基于“自由列表”进行管理,即内存能够重复利用,而不是每次都新建对象。所以我们开始考虑将这些结构逐步向自由列表的形式过渡,以便未来更高效地管理资源和内存。

“普鲁克拉斯提安式”确实是从英文 Procrustean 翻译过来的,但中文里常见的译法是:

  • 普罗克鲁斯提式
  • 普鲁克拉斯特式
  • 普洛克鲁斯特式

这个词源于希腊神话中的普罗克鲁斯特斯(Procrustes),他有一张床,会把路人放上去,如果不合尺寸就锯掉或拉长肢体,直到“合适”为止。这个典故引申成了“强行统一标准、无视个体差异”的意思。

所以更标准的写法应该是:

普罗克鲁斯特式的改造

不过“普鲁克拉斯提安式”虽然少见,但意思还挺准确,算是一个音译+形容词形式的混合用法,不算完全错,只是不太标准。
在这里插入图片描述

考虑是否为调试信息添加空闲列表

我们决定暂时将一些关于内存回收的优化推迟处理。虽然这些优化最终可能是有必要的,但目前看来,调试系统本身所涉及的用户交互量非常有限,因此对内存的需求并不会很高。即使不做内存回收,直接使用静态分配的方式也完全可以接受。

从功能完整性的角度来说,未来我们可能还是希望能让系统更加健壮,具备自动回收无用内存的能力。但现阶段,我们更倾向于把这部分工作延后,因为它不是当前的重点。就像现实生活中的人们一样,大多数人在不得不回收资源前,并不会真的关心回收利用,而是任由废弃物堆积,直到环境被彻底破坏、地球变得荒芜。

因此,在设计调试系统的内存结构时,我们现在选择了一个更简单直接的实现方式,即不考虑复杂的内存回收逻辑,而是优先保证系统的可用性和开发效率。等到真正遇到资源瓶颈时,再来完善这部分功能也不迟。

更新 AddVariableToGroup,CreateVariableGroup 以便与树结构中的元素一起工作

我们现在开始重新梳理向组中添加变量(现在更准确地说是元素)的逻辑。之前的实现中,是将事件加入某个组中,而现在的模型更接近于添加“元素”,这些元素可以是变量或其他调试信息。我们将原有的“添加变量到组”的函数,逐步改造为“添加元素到组”,传入的元素就是我们真正希望处理和存储的内容。基本逻辑和之前类似,不需要做太大改动。

不过我们注意到一个关键问题:当前的组结构无法拥有子组,缺乏层级结构支持。也就是说,变量组没有“父组”或“子组”的概念,它们是孤立存在的。这种结构既没有来源也无法延续,就像一个永远无法成为父母的孤儿,被剥夺了构建家庭和延续意义的可能,是一种存在但又徒劳的状态。

为了改变这种状况,我们需要在变量组的创建过程中引入对层级结构的支持。也就是说,在创建某个变量组时,可以选择性地传入一个“父组”参数。如果指定了父组,那么这个新建的变量组就应该挂载在那个父组之下,成为它的子组。这样我们就能逐步构建出完整的层级关系。

同时,在“添加元素到组”的逻辑中,我们也考虑了需要实现“添加子组到组”的功能,以便在调试界面中维护层级关系的可视性。通过为组结构增加对父子关系的支持,我们可以更合理地组织调试信息,使其更符合人类习惯的树状结构展示方式。

下一步就是在构建和维护组的过程中,实现对子组的挂载和遍历机制,使得每个组既可以承载调试元素,又可以作为其他组的容器,真正具备上下文与从属关系。这样,整个调试系统的结构也会变得更加有层次、更加可控。

在这里插入图片描述

将 AddVariableToGroup 拆分为 AddElementToGroup 和 AddGroupToGroup

我们现在希望实现的是将一个子组添加到另一个组中,从而建立完整的层级关系。这个过程类似于之前将元素插入组的操作,不过这次插入的是一个子组。逻辑上依旧是通过链接结构完成,将子组作为链表节点插入到父组的“子组列表”中。

具体步骤如下:
首先,为这个子组创建一个链表链接节点,然后将其插入到父组的子组链表中。区别在于,这次不是插入调试元素的链表,而是插入专门用来维护子组的那一份链表结构中。插入完成之后,还需要建立引用关系,让子组知道它的父组是谁,也就是设置“父组”指针。

至于原有一些附加逻辑,比如设置某些状态或元数据,在这种上下级组之间的添加中可能不再适用,因此可以忽略或移除这些逻辑片段。它们在这个上下文中是无意义的。

接下来反思结构设计。我们注意到,在目前的结构中,一个组要么拥有子组,要么拥有调试元素(比如变量、事件等)。也就是说,每个组要么是一个容器,要么是一个叶子节点,不能同时拥有两种内容。这种设计看似合理,但也可能带来一定的限制。例如,如果一个组既想作为分类的父节点,同时又需要记录一些属于它自身的调试信息,就可能遇到设计障碍。

现在暂时接受这种结构,并默认一个组在层级中的角色是单一的。如果未来发现这种限制阻碍了调试结构的表达力,再考虑引入混合模式,即允许组同时存在子组和元素。但目前我们采用“单一内容类型”的组织方式,即一个组不是容器就是叶子,结构相对简单清晰。

总结来说,现在的任务是:

  1. 实现子组插入到父组的逻辑;
  2. 保证链表结构与引用指针的正确性;
  3. 忽略对这个上下文无意义的逻辑;
  4. 维持组结构的当前设计理念,即“只能容纳子组或元素之一”。

这套机制完成后,我们就可以用它构建起真正的调试层级结构,开始将散落的调试信息系统化地组织在一起。
在这里插入图片描述

注意到名称未存储在 debug_variable_group 中,并进行修复

我们意识到当前的调试变量组(debug variable group)虽然已经具备了子组结构,可以支持构建分层体系,但缺少了一个非常关键的属性:名称。每个组虽然有了父子关系的指针和链表结构,但我们并没有为它们提供名称字段,因此在整个系统中,无法识别一个组“是什么”或“代表什么”。它就像一个无名的存在,仅仅被指针和内存地址标识,而非一个有意义的个体。

这种缺失揭示了我们设计中对个体标识的漠视。在这种机制下,调试信息的组织虽然可以形成结构,却无法体现出每一个分组的语义。我们默认所有的组都是匿名的,它们不需要名字、不需要身份,也不需要任何个性化特征,仅仅充当结构容器。正如系统中一段无情的逻辑那样,只关注其作用,而非其存在本身。

从技术上来说,我们提出一个改进方向:为变量组添加一个名称字段。这意味着每个调试变量组在创建时,应当带有一个字符串类型的名字,用于标识其用途或含义。这样,当我们遍历调试信息时,可以清楚地看到每一组数据所属的类别或功能,不再只是看内存地址或结构层级。

我们检查了当前的结构代码,发现虽然包含了父节点和子节点的指针,比如 parentSentinel,但没有体现组的“名称”这一核心属性。因此下一步,我们准备在组结构中添加这个字段,并在构造函数或创建逻辑中进行设置。

在完成这个结构扩展后,我们接着还需解决另一个核心问题:**如何根据调试事件的信息自动归类到正确的组中。**换言之,我们需要一个机制,能根据事件来源、层级标识或其他元数据,判断它应当归属于哪个组,并自动建立这种归属关系。这一部分是整个分组体系动态运作的关键。

所以当前阶段我们重点完成的是:

  1. 为每个变量组引入名称字段,提升结构可读性与可识别性;
  2. 保留并清晰化父子指针结构,用于组织树状层级;
  3. 为后续事件归类准备基础结构,下一步将实现归类逻辑。

这使我们能够开始构建一个有“名字”的分层结构,不再是无名编号的集合,而是一个具备语义和意义的调试体系。
在这里插入图片描述

移除 CreateVariable

我们目前确认了一点,即“创建变量”这部分逻辑已经没有继续保留的必要。因为当前的设计流程中,我们始终是直接添加调试元素(element),而不再单独进行变量的创建操作。原本那段与“创建变量”相关的代码,基本上属于旧的遗留结构,在当前系统结构中已经不再起作用。

回顾其逻辑,我们并没有找到它对现有数据流、事件组织或调试组结构还有实际价值的部分,所以决定将其彻底移除。这样不仅能减少无用的冗余代码,还能让整个系统逻辑更清晰、集中于我们真正使用的部分。

我们保留了一个未实现的“释放变量组”的函数,即 free variable group。虽然目前还未涉及主动释放资源的需求,但保留这个接口作为占位符是合理的。它的存在可以确保在未来如果我们开始处理内存回收或希望支持结构复用时,有一个明确的位置可以接入资源管理逻辑。

至于一些其他结构,比如原先为“创建变量”服务的附属逻辑和数据结构,现在也被判断为无关内容,因此一并删除或废弃。

总结来说,此阶段我们完成了以下几个清理与调整:

  1. 删除了“创建变量”相关的旧逻辑,确认为当前系统无用;
  2. 保留了“释放变量组”接口,未来可用于资源回收;
  3. 整理代码结构,避免遗留逻辑干扰现有系统演进;
  4. 将调试系统的焦点更加聚焦在“调试元素”和“变量组”之间的组织关系,而非单独变量。

通过这次清理,我们让系统更接近现在的使用场景,也为后续的维护与扩展打下了简洁稳定的基础。
在这里插入图片描述

在 GetGroupForHierarchicalName 中添加根变量组处理

目前我们只剩下最后一个关键部分需要处理,即补全先前尚未实现的逻辑。其实调用方式是正确的,接口设计也没有问题,只是之前由于忙于其他部分的开发,这部分功能暂时被搁置了。因此,现在要做的就是实现这个函数,让整个系统真正运行起来。

我们再次检查了之前的实现方式,发现可以继续沿用此前的思路。可以先构建一个临时结构用于辅助调试信息的组织,然后专注于实现这个核心功能。实现本身不会特别困难,主要是代码输入量稍微大一些,逻辑上是清晰的。

当前整体架构已经搭建得比较完整了,所以接下来我们要做的是在合适的地方调用这些新构建的逻辑。例如,在初始化调试状态(debug state)的时候,就应该调用这些函数来完成初始结构的建立。

在初始化流程中,我们会设置调试系统的基础状态,所以显然在这一步就需要创建调试用的根组,或者相关的结构数据,确保后续可以将所有调试元素有序地插入到正确的位置上。

接下来我们的目标是:

  1. 实现之前未完成的核心函数,让它执行预期的结构创建与数据插入;
  2. 在调试状态初始化时,调用这个新实现的函数,完成调试结构的起步配置;
  3. 确保一切初始化完成之后,后续事件或调试元素的添加操作可以正常工作,结构清晰、层级正确。

总体来说,我们已经完成了调试系统的绝大部分基础架构建设,现在要做的是将各个部分正式连接起来,使整个系统真正“活”起来,并可以响应实时的调试事件流。
在这里插入图片描述

创建根变量组

当前的任务是将调试系统中已经建立好的根组与变量组的创建逻辑整合起来,并完成事件的分层存储流程。首先确认在创建变量组时,只需要传入调试状态(debug state)即可完成根组的初始化,因此现在已经拥有一个可用的根组,准备好接收调试信息。

接下来,目标是将系统各部分串联起来,实现调试事件的层级式添加。也就是说,未来能够以分组的形式将调试元素插入到正确的层级中,从而构建出结构清晰、逻辑明确的调试树。

为此,需要查看事件流的处理逻辑,确认应该在何处插入调试元素。经过查看,发现并非是在 store_event 函数中处理,而是应当定位到 GetElementFromEvent 函数,这是整个事件解析过程的核心——事件转换为调试元素的实际位置。

因此,接下来的工作包括:

  1. 在创建变量组函数中确认只需要传入调试状态作为参数,便可生成根组。
  2. GetElementFromEvent 函数中,接收事件,并将其转换为调试元素时,加入与根组的绑定逻辑,使其被添加到正确的层级结构中。
  3. 在事件被解析为调试元素的那一刻,按照事件来源或结构信息,插入到对应的变量组中,保持层级结构的清晰性。
  4. 这将使得调试信息不再是线性、杂乱的集合,而是可视化、有结构的分层内容,便于后续查阅和分析。

整体来说,调试系统的根架构已经完成,只需将事件的解析结果正确插入到层级结构中,即可完成功能闭环,达到“事件自动分组”的效果。接下来即可着手实现该插入逻辑,并做测试验证其正确性。
在这里插入图片描述

在 GetElementFromEvent 中添加层次结构查找

当前处理逻辑聚焦于当接收到一个首次出现、来源未知的事件时,如何进行层级查找和结构归类。此时,会触发调试系统的“未知来源事件”处理分支,尝试为其在哈希表中创建并注册新的调试元素。

我们意识到,这正是引入分层结构查找机制的关键时机。过去的处理方式并未关注事件来源的结构性,只是将其作为一个独立个体简单存储。而现在,系统将采用带有层级概念的命名解析方法,比如通过下划线 _ 来识别路径或结构分段,借此推导出其在分组结构中的位置。

然而,当前项目中并非所有内容都严格遵循这种命名规范,因此不能完全依赖下划线划分来进行层级推导。原有代码中有 block name 的相关机制用于命名标识,但其结构也并不统一。因此,当前阶段采取的策略是——统一处理所有名称,不做区分地进行尝试性归类,再在后续整合与测试过程中逐步优化命名识别逻辑。

具体处理流程如下:

  1. 接收到事件后,首先判断其是否为新来源。
  2. 若为新来源事件,则为其创建调试元素并注册入哈希表。
  3. 在创建调试元素之前,尝试通过名称(如带 _ 的路径)进行分层定位,从根组开始逐级查找或创建中间变量组,以构建该事件所属的完整层级路径。
  4. 最后将调试元素挂载在该路径末端的组中,确保整个调试系统保持结构一致性与可视性。

通过这一策略,调试事件不再是孤立存在,而是被纳入具有上下文与层次的体系中,方便后续逻辑追踪、可视化分析及调试效率提升。此过程将是实现全系统结构化调试管理的关键一步。

思考如何使用 BlockName 或 GUID 来创建层次结构

当前讨论的主要问题是如何将调试数据组织到统一的层级结构中,尤其是在不同的代码位置生成的调试数据被收集时如何存储和归档。当前使用的命名规则(如 block name)用于对数据进行分组和管理,但随着需求的变化,可能需要做出调整。

关键点总结:

  1. 数据块分组:调试数据的分组使用了名为 block name 的标识符,这个标识符帮助将数据从不同位置进行组织。现在考虑是否保持这种命名方式,或者改用更统一的方式(例如使用 GUID 结构)来分类和归档调试数据。

  2. 层级结构的形成:目前考虑将调试数据按照某种逻辑创建数据块,然后根据这些数据块形成层级结构。此层级结构可能会在未来需要进行进一步的调整和优化,以确保数据可以根据其来源合理分类。

  3. 调试变量命名:调试变量的命名方式(例如 ground_chunks_checkerboard)目前是基于某些逻辑进行的,这些命名对于理解调试数据是有意义的。然而,关于如何统一调试数据的命名和存储方式,仍然存在一些待解决的问题。例如,如何确保所有调试数据都能够在系统内使用统一的概念进行归档。

  4. 数据块管理:当前的调试数据被某些代码块(如 begin data block)包围,这些数据需要在一个类似的数据块中进行归档。在实现时,需要确保每个数据块都能被正确地创建并归档到相应的层级中。

  5. 进一步优化和思考:尽管基础版本的层级归档已经开始构建,但仍然需要进一步思考如何提升系统的表现,以便能够更好地处理调试数据并确保它们的层级结构能够满足后续的需求。整体来看,系统的优化不仅仅是简单的实现,还需要关注如何更好地组织、管理和呈现这些数据。

通过这些步骤,系统最终的目标是能够在复杂的调试场景中,清晰地展示数据的层级和归属,使得调试过程更加高效和有条理。

使用 GetGroupForHierarchicalName 在添加新事件时检查是否有父组,如果有就连接它们

当前的目标是将元素正确地添加到层级结构中,具体步骤如下:

  1. 找到父组:首先需要找到该元素应该添加到的父组。为此,通过传递该元素的“块名”来确定它的父组。块名将用于匹配并定位其父组,从而确定将元素添加到哪个组。

  2. 添加元素到父组:一旦找到了适当的父组,就将该元素添加到父组中。这个步骤的目的是将元素正确地嵌入到已经存在的层级结构里。

  3. 不需要存储链接:对于这一过程,似乎并不需要额外存储链接。直接将元素加入父组即可。

  4. 强制绘制:为了能够看到元素在层级结构中的位置,可能需要强制刷新绘制。通过绘制,可以实时查看我们创建的层级结构,确保元素被正确地添加。

  5. 调试和查看代码:在实现过程中,虽然有些代码可能忘记了具体位置,但没有问题。通过回顾和调试代码,逐步调整和完善层级结构的功能,确保它按预期工作。

整体上,目的是让元素能够按照特定的逻辑层次,正确地嵌入到其父组中,同时保持可视化效果,以便实时检查和调整。
在这里插入图片描述

启用绘制层次结构

在当前的工作中,讨论的重点是如何简化和优化创建层次结构的过程。具体的步骤如下:

  1. 简化树的创建:不再需要复杂的树结构。可以通过直接在代码中创建一个树,并将它始终指向根组,这样就能避免之前复杂的操作方式。这样做会让流程更加直观和简洁。

  2. 避免复杂性:之前可能会在树结构中做过多的“黑客式”修改,但现在考虑到可以通过更简洁的方法来实现,所以不再需要这样做。直接让树结构指向根组是一个更简便且高效的选择。

  3. 树与根组的关系:树的结构仍然存在,只是通过这种简化的方式,根组将作为树的基础,而不需要再进行复杂的操作。

这种方法的主要目的是通过更简洁的方式来创建和管理树形结构,并减少不必要的复杂性。
在这里插入图片描述

“黑客式”修改(或“hack”)通常指的是通过不正式、不规范或临时的手段来解决问题,通常是为了快速实现某个目标,但这种做法可能会导致代码不易维护、可读性差,甚至在长远来看可能引发更多问题。

具体来说,黑客式修改有以下特点:

  1. 临时解决方案:通常是为了尽快解决某个问题,而不是从根本上解决它。这种做法往往是为了应急,而没有考虑到系统的长远稳定性。

  2. 不规范的代码:黑客式修改往往不遵循最佳编程实践,可能会跳过一些规范的步骤,如代码结构不清晰、注释不足、变量命名不规范等。

  3. 高风险:这种修改可能在短期内看似解决了问题,但它带来的隐患是长远的。它可能会影响后续的维护,导致系统变得更难扩展和调试。

  4. 解决问题的捷径:在处理复杂问题时,开发者可能会采用临时的或者不完美的方法来应对,结果可能在最终的系统中留下不必要的复杂性。

总体而言,黑客式修改是为了“立刻解决眼前的问题”,但可能会导致长远的维护问题,因此在软件开发中通常不推荐这种做法,除非在非常紧急的情况下。

调试崩溃,因为不知道如何绘制某些调试元素

在这段内容中,开发者正在处理一个代码中的层级结构问题。当前系统中正在添加很多新的元素到这个层级中,但有些元素的绘制无法正常工作,因为系统并不知道如何处理这些新的元素类型。为了处理这个问题,开发者决定增强系统的能力,使其能够处理并显示所有这些新增的元素。

具体来说,开发者发现有些事件类型被标记为“debug value”,并且系统无法识别这些事件。为了让系统能够正确处理这些事件,开发者决定修改代码中的 debug event to text 函数,使其能够打印出这些事件,并且增加对未知事件类型的处理逻辑。

开发者的思路是,尽管当前的系统存在一些不可预见的问题,但通过添加更多的日志和处理机制,可以让系统能够更好地应对这些问题,并逐步修复它们。

总结来说,这段内容描述了开发者在调试和完善一个层级结构系统时,遇到的一些不可预见的情况,并通过扩展系统的事件处理能力来解决这些问题。同时,开发者也反思了自己的工作进度,并表示时间已经有些紧迫。
在这里插入图片描述

为未处理的调试事件添加默认名称以防崩溃

在这段内容中,开发者讨论了如何合理地传递一个"块名称"(block name)。他们认为将"块名称"作为参数传递是完全合理的,并计划根据这一思路继续开发。在这种情况下,块名称将作为关键数据传递,并且他们将其用于后续的操作和逻辑处理。

总结来说,开发者认为传递块名称作为参数是一种合适的做法,并且计划按照这一思路继续进行相关工作。

运行游戏,现在不会崩溃,但调试变量没有列出

目前,开发者继续推进项目并对当前结果感到惊讶,因为许多预期中的步骤和操作竟然没有做任何额外的工作就顺利运行了。开发者对此感到有些意外,甚至觉得这是一种有趣的情况。
在这里插入图片描述

“有时候即使在绝望的深渊中,某些小事情也会对你有利,重要的是不要让这种情况冲昏头脑。”

即使在深深的绝望中,有时也会出现一些微小的好运。重要的是要记住,不要让这些小小的成功冲昏头脑。事情依然如预期的那样糟糕,一个微不足道的胜利并不足以改变对人类痛苦不可避免性的悲观看法。因此,即使有些事情进展顺利,也不能因此而改变自己对生活的整体看法。

开始查找为什么调试变量没有出现

在检查调试变量时,发现没有看到预期中的调试变量输出,这让我感到有些困惑。我原本以为会看到一些调试变量的输出,但现在并没有。实际上,我看到了一些数据输出,但没有调试变量,这让我怀疑自己是否没有正确理解这些变量如何在系统中流动。

在查看代码时,发现在debug variables部分并没有显示出这些变量。这可能是因为我们没有正确地打印它们,或者是某些步骤被跳过了。我想先弄清楚为什么调试变量没有显示出来。

在分析时,我发现调试变量的文本打印部分似乎只会输出某些特定的内容,而没有包括位图ID和线程列表计数器。理论上,任何调试记录应该在collate debug records的过程中被处理并显示出来。所以,我觉得应该是系统没有正确地将这些调试变量加入到流中。

我再次查看了代码,特别是涉及调试记录部分,像是begin blockblock的相关代码,似乎并没有问题。然而,我没有看到调试变量的打印输出,可能是因为某些环节没有处理到。

进一步调试时,我发现调试值虽然经过了处理,但却没有块名称。这意味着在处理这些数据时,存在一些不一致或错误。

总之,现在系统在处理调试变量时出现了问题,可能是处理流程中的某个环节没有被正确触发,导致调试数据没有按预期显示出来。这需要进一步检查和修正。

在 MarkDebugValue 上添加断点,以查看哪些事件通过了

现在我们发现系统确实有收到调试值事件(debug value),但问题在于这些事件的 block name 被设置成了 0,也就是说并没有实际赋值,这显然不是我们期望的行为。

我们查看了一下调试信息的调用栈,发现相关代码路径指向了 game debug interface 的第 293 行左右。这个位置似乎是在向调试系统传输数据的过程中,却没有正确地附带 block name。很明显,这是一个逻辑上的遗漏。

本质上,这意味着我们虽然生成了调试数据并传输到了系统中,但因为缺失了用于组织和归类的 block name,这些调试值无法被归类到正确的层级结构下,从而也不会在最终的调试界面中呈现出来。

这个问题的根源可能是我们在写入调试数据时没有显式设置 block name,或者在构建调试记录时跳过了对 block name 的处理步骤。这种疏漏直接导致了调试值孤立地存在,没有上下文关联,最终也就无法被系统有效显示或使用。

这是一件令人失望的事情,因为它意味着我们需要回过头去,检查和修复这部分逻辑,确保每个调试事件在生成时都正确绑定了所属的 block name,以便后续系统能识别它们的归属关系,从而建立出正确的层级结构。这个问题虽小,但影响到了调试功能的完整性。

在这里插入图片描述

解释了这个 bug,并通过存储 GUID 在事件中进行了部分修复

目前出现的问题在于:由于我们在统一的位置调用了 debug_initialize_value,导致所有调试变量在系统中被记录时,其来源位置都是同一个代码点。这直接引发了 block name 丢失或统一的问题,使得调试变量在后续分组与层级归类时缺乏区分依据。

这种情况意味着,我们目前的实现方式使得无论变量在哪个上下文中被初始化,它们在系统内部都被视为来自相同的源头。这使得它们难以被归类到各自所属的逻辑层级中,从而破坏了原本想要构建的分层调试视图结构。

为了解决这个问题,我们需要确保每个调试变量能被绑定到唯一的、具备区分性的标识符(例如 GUID 或 block name)。当前的思路是,可以利用变量名本身作为 GUID 名称来生成唯一的分组信息。这样,即使初始化函数的位置相同,只要传入的变量名不同,生成的 GUID 分组就能区分它们。

换句话说,可以考虑将调试值初始化宏或函数做进一步封装或修改,使其在生成调试记录时自动附带变量名,并以此变量名为关键构造唯一的调试分组,从而解决统一调用位置带来的归属混乱问题。

虽然目前这还只是一个设想,但理论上是成立的,并且实现成本相对较低。后续可以进一步验证此方式是否真正解决了调试变量无法正确分层归类的问题。如果有效,就可以继续扩展完善整个调试层级的逻辑结构。

在这里插入图片描述

在这里插入图片描述

重新运行游戏,现在多个事件正确显示,但文本不正确

目前的问题虽然通过将变量名用作 GUID 名称得到一定程度的解决,从而使调试变量在层级结构中得以区分,但这样做仍有明显的不足——因为事件本身并未携带 block name 信息。

这意味着,尽管我们在分组阶段依赖变量名来建立区分,但在事件传播和调试信息显示等后续流程中,仍然缺乏一个统一且可靠的标识,来指明该变量所属的逻辑块或上下文位置。

因此,下一步就必须将 block name 也存储到事件对象中。这一点非常关键:

  • 确保事件数据完整性:通过在事件中记录 block name,可以在调试界面或日志中清晰显示变量的归属;
  • 增强层级结构可维护性:未来在进一步实现复杂的层级显示、筛选、搜索时,有明确的 block name 会极大简化处理逻辑;
  • 避免依赖代码位置识别上下文:否则所有事件都像是从同一位置生成的一样,丢失语义信息;
  • 提高数据追踪能力:哪怕多个变量初始化发生在同一行代码中,只要 block name 不同,依然可以有效分辨。

综上,我们的策略应当调整为:不仅以变量名构造唯一 GUID 名称,还应显式地将 block name 记录在调试事件对象中,作为该调试数据的“身份标签”。这样才能确保系统从数据生成、传输,到展示的每一个环节中都保持一致的上下文信息,从而让调试层级系统更加准确和健壮。

将 BlockName 也存储到事件中,使调试显示稍微更正确

目前的调试变量输出情况比之前更合理了,我们已经能够看到预期中的大部分变量,说明数据的传递和处理逻辑基本是通畅的。但在进一步观察中,也发现了一些细节问题需要处理与优化。

现在的流程中,我们确实已经实现了事件与其所属 GUID 的关联,部分变量通过 block name 显示出来,也能看到对应的调试输出。但出现了两个主要问题:


1. 事件显示重复(例如 unknown 前后重复)

出现多个相似条目,初步判断是由于:

  • 在调试信息输出的过程中,不仅输出了变量对应的值,还输出了事件中携带的 block name 名称;
  • 这导致一些信息在逻辑上被重复展示,显得多余。

2. 虽然结构更清晰,但层级关系尚未建立

目前只是将变量展示出来,但它们仍然是平铺状态,没有按照 block name 或其他规则组织成层级结构。接下来应该做的事情包括:

  • 基于 block name 构建分组关系:例如如果 block name 是 ground_chunks_checkerboard,就应该拆分为 ground_chunks -> checkerboard 的层级结构;
  • 通过名称中的下划线或其他分隔符 来确定父子结构;
  • 建立并维护一个层级结构树,将每个 debug 变量插入到正确的位置;
  • 在渲染(或调试面板中显示)时,也根据这套结构来组织和展开数据。

3. 我们确实曾试图处理这些问题,只是早先思路未能彻底实现

之前已经考虑过通过变量名生成 GUID,或者通过宏来插入更细粒度的标识,只是由于实现上的小疏漏(例如没有在事件中存储 block name)而导致数据归类不完整。


当前的推进成果总结:

  • 已经能成功获取调试变量;
  • 变量名也得以正确传递;
  • 下一步是将这些变量按照 block name 显示为层级结构,实现真正清晰的上下文归属感;
  • 同时处理输出重复问题,确保每条调试信息只展示一次有效内容。

继续推进的方向是:**建立并渲染层级结构树,完善调试变量的组织与显示。**这样才能让整个调试系统具备可视性、可导航性和逻辑一致性。
在这里插入图片描述

在这里插入图片描述

解释了如何使用调试名称来构建层次结构

为了实现我们想要的调试变量层级结构,当前的目标是解析变量的名称,并利用名称中的下划线 _ 来自动构建层级关系。这个策略是我们之前就已经确定下来的。


实现层级的核心思路如下:

  • 调试变量名中已经内嵌了结构信息,例如:
    • DEBUG_IF(Simulation_FamiliarFollowsHero)
static debug_event DebugValueSimulation_FamiliarFollowsHero = DebugInitializeValue(((DebugValueSimulation_FamiliarFollowsHero.Value_bool32 = 1), DebugType_bool32),&DebugValueSimulation_FamiliarFollowsHero, "Simulation_FamiliarFollowsHero","c:\\Users\\16956\\Documents\\game\\day219\\game\\game\\game.cpp""(""2886"").""4");

目标是让这些名称在调试工具中自动组织成嵌套结构:

  • 解析每个变量的名称;
  • 按照下划线逐级拆分;
  • 每一级作为一个分组,最终的叶节点是变量本身;
  • 将变量插入到正确的层级节点中,构建出一棵完整的调试树。

实现此逻辑的必要步骤:

  1. 在处理变量时读取其完整名称;
  2. 使用下划线 _ 将名称拆分为多个部分;
  3. 逐层遍历(或递归)插入节点,每一层创建或复用已有的分组;
  4. 最后将变量对象挂载到最底层对应节点下;
  5. 最终这棵树就代表了变量的组织结构,可以被可视化地展开和浏览。

目标效果:

通过这样的方式,每个变量不再只是平铺的调试项,而是依据其语义和命名方式,被归入合理的逻辑结构中,例如:
这种结构将极大提升调试时对变量的理解和管理,尤其在变量数量很多时,能快速定位和组织信息。


总结来说,现在的任务是实现一个基于下划线命名解析的自动层级结构构建器,从而让调试系统更智能、更清晰。

在 GetGroupForHeirarchicalName 中实现层次结构构建

我们要实现的是一个分层名称的分组逻辑。以往我们总是返回根分组,但现在我们需要根据名称来返回真正正确的分组。具体来说,我们会按照如下步骤进行处理:

首先,我们需要检查传入的名称字符串,找到其中的第一个下划线(_)。这个下划线将帮助我们确定第一个分层的边界。

我们会从头开始扫描整个名称字符串,一旦遇到第一个下划线,就记录它在字符串中的位置,并终止扫描。这个位置实际上就将名称切分为两部分:前缀部分表示该对象应归属的第一层子分组,而剩下的部分将递归地被进一步处理以归入更深一层的子分组。

接下来,如果没有找到下划线,说明这个名称没有分层结构,此时我们就直接将该对象插入到它所属的父分组中,无需进一步处理。

如果找到了下划线,就说明存在分层结构,需要进一步处理。我们接着检查在当前父分组下,是否已经存在一个以当前前缀命名的子分组。如果存在,就获取它;如果不存在,就新建一个并插入到父分组中。

然后,我们会递归地调用自身,将剩余部分(即从下划线后一个字符开始的子字符串)作为新的名称继续处理。每次递归都处理名称的一层,直到名称中不再包含下划线为止。

通过这种方式,我们就能够根据名称中的下划线自动构建并组织一个嵌套的分组结构,实现名称与分组之间的层级映射逻辑。整个过程依赖于字符串的解析、判断是否存在对应分组、创建新分组、以及递归调用自身来处理更深层的结构。
在这里插入图片描述

添加 GetOrCreateGroupWithName 辅助函数

我们接下来的目标是完成分组的查找与创建操作。只要能够顺利完成这部分,整体逻辑就可以正常运行。

当前的输入名称已经不再像以前那样有备用名可以参考,而是只有一个确定长度的名称。这种方式其实更符合我们处理字符串的偏好——按位置索引处理而不是按逻辑规则匹配,虽然在现有系统中我们并不常进行复杂的字符串操作,因此影响不大。

我们的主要任务是在给定父分组的基础上,遍历其下属的分组,查找是否已经存在一个名字与我们目标名称相同的子分组。这个操作并不需要太高的性能优化,因为这个函数只在调试信息首次进入系统、并且该名称还从未被注册过的时候才会被调用。换句话说,一旦层级关系构建完成,它就不会再被调用。因此它既不影响调试系统整体性能,更不属于系统中关键路径的一部分,这一点也很有利,我们可以不拘泥于性能细节,专注于逻辑正确性。

具体来说,我们会从当前父分组开始,遍历其子节点链表。这个链表从父分组的哨兵节点(sentinel)出发,通过 next 指针不断向后访问每一个子节点,直到再次遇到哨兵节点,表示遍历结束。

在这个过程中,我们检查每个子节点的名称,如果找到一个名称与当前分组名称匹配的,就说明该分组已经存在,可以直接使用,操作完成。

如果在整个链表中没有找到匹配的分组,那我们就需要新建一个子分组。这就涉及到创建一个新的分层分组对象,并将其插入到父分组的结构中,同时也要更新相应的分组链表。

此外,还要调用一个添加函数,将新建的分组加入到当前父分组的子分组集合中,确保层级结构的完整性。

总之,这一段的核心逻辑是:遍历父分组的子分组列表,尝试查找匹配名称的分组;如果没有找到就新建一个,并插入到正确的位置。整个过程依赖链表遍历、名称比较、新建节点和层级结构维护,是构建调试分组体系中的关键环节。
在这里插入图片描述

将名称放入 debug_variable_group 结构体中,完成层次结构创建代码

我们早就预见到这个问题的出现,可以说这是一种“预言”,当变量组中没有名称时,整个分层结构就无法正常构建。没有名称就无法建立层次关系,即便有,也无法展示,导致只是一堆可以展开但毫无实际意义的节点,这是非常无用的。

为了解决这个问题,我们决定从根本上支持在变量组中存储名称,并在创建变量组时就传入名称和其长度。这样不仅能确保每个分组有唯一标识,还能用于后续的字符串比较,从而判断分组是否匹配。

具体操作如下:

  1. 变量组命名支持:我们在创建变量组时传入 namename length,并将它们保存在变量组对象中。这样每个变量组都有一个明确的名称信息,便于后续的识别与层级构建。

  2. 字符串比较逻辑:添加一个字符串比较函数 StringsAreEqual,用于判断两个变量组是否拥有相同的名称。这个函数通过比较两个字符串及其长度来进行判断,而不是依赖常规的 null 结尾字符串方法,以增强性能和准确性。

  3. 改造创建函数:在 CreateVariableGroup 函数中,我们新增对 namename length 的参数支持,使得每个新建的变量组都能在初始化时就绑定其名称。

  4. 遍历和匹配逻辑调整:在遍历子分组时,我们使用 StringsAreEqual 函数进行名称匹配,判断当前遍历到的子项是否就是我们所需的分组。如果匹配,就直接使用它;否则就新建一个新的分组并添加到父级中。

  5. 设置名称长度上限:基于实际需求,我们默认字符串不会超过 400 万个字符。如果真有超过这个长度的字符串,显示上也早已崩溃,不具备处理的意义,因此这一假设完全合理。

  6. 递归入口设置:第一次调用分层名称查找函数 GetGroupForHierarchicalName 时,我们默认从根组(root group)开始,这样整个递归结构就有了明确的起点。

总结来说,我们重新设计了变量组结构,使其支持名称存储、初始化时赋值、以及基于名称的匹配操作。整个分层结构的构建逻辑更严谨,匹配准确,展示也更直观。这些调整都是为了实现健壮的分组体系,确保调试数据以清晰的分层方式展现。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

添加一个 StringsAreEqual 函数,具有明确的长度参数

我们现在的目标是实现一个基于长度的字符串比较函数,而不是依赖以空字符结尾的传统方式。这个函数可以用于比较任意长度的字符串,只要它们长度相同,并且在每个字符上的值也一致,就认为是相等的。

以下是具体的实现思路和逻辑流程:

  1. 初始思考与目标设定
    我们希望构建一个字符串比较函数,它通过传入的字符串内容和对应的长度来判断两个字符串是否相等,而不是依赖 null 结尾。这样我们就能比较任意长度的内存区域内的字符串,甚至可以比较非 null 结尾的数据块,增强通用性。

  2. 先判断长度是否一致
    首先检查两个字符串的长度是否一致,这是判断是否可能相等的前提。如果长度不同,则不可能相等,直接返回 false,节省后续计算成本。

  3. 进入字符逐位比较阶段
    如果长度相同,说明有比较的必要性。我们定义一个索引变量 index,从 0 开始,循环直到长度上限。因为两个字符串长度相同,所以只需用一个变量进行循环控制。

  4. 字符比对逻辑
    在循环过程中,我们逐位比较两个字符串的每个字符(a[index]b[index])。一旦发现某一位字符不一致,立即返回 false 并退出比较过程,说明两个字符串不相等。

  5. 全部字符都一致则返回 true
    如果整个循环过程中没有发生不一致的情况,说明两个字符串完全一致,于是最终返回 true

  6. 可扩展性与稳定性设计
    虽然我们可以比较非常长的字符串(理论上内存允许范围内任意长度),但一般不会超过几百万字符,显示或使用上也会受到限制。出于稳定性考虑,我们默认字符串不会超过 400 万字符,超过这个值的情况可以视为异常或不合理使用。

这个函数的设计增强了我们对字符串的操作能力,特别适合用于调试信息分组中的名称匹配逻辑。相比传统 null 结尾字符串比较,它更灵活、安全,也更适合系统底层操作或复杂内存结构处理场景。我们将在层级分组构建中依赖它实现精准匹配,确保名称唯一性和准确性,从而维持分组结构的完整性和可读性。
在这里插入图片描述

描述了为了使调试层次结构正确显示需要添加的内容

目前的状态下,虽然我们已经处理了分组和名称比较的逻辑,但存在一个问题,就是在调试时,我们没有实际的方式来显示分组的头部信息。也就是说,我们目前的代码中没有一个完整的框架来绘制或呈现分组层级的可视化界面。

具体来说,当我们在调试时,虽然会进入分组处理逻辑,但我们并没有绘制或显示每个分组的“头部”信息。这意味着,尽管我们能处理和遍历每个分组(如变量组的链接),但如果这些分组有子节点或层级结构,我们并没有实际的界面或方式去显示它们。

目前,代码中的一些部分确实已经处理了子分组的遍历和链接,比如在 linked children 这块有相应的逻辑判断,但这并不意味着我们在界面上能够展示这些分组或它们的层级结构。这种遍历可能只是单纯的遍历和处理,并没有结合实际的UI或界面进行可视化展示。

接下来,我们的目标是增加一个功能,当遇到具有子节点的分组时,能够以分层的方式进行处理和显示。例如,若某个分组有子节点,我们应该以层级的方式递归处理这些子节点,而不是仅仅按平面结构处理所有项。这样,调试界面才能正确展示每个分组的层次结构。

为此,可能需要引入一些逻辑判断,判断当前的分组是否有子节点,如果有,就按照层级顺序来处理这些子节点,进行递归显示。这不仅涉及到对数据结构的处理,还包括如何在调试界面上呈现这些层次关系,让开发者能够更清晰地看到各个分组之间的父子关系。

总结来说,目前最大的挑战是如何在调试时正确地绘制分组层级头部,尤其是当分组具有子节点时,如何以层级方式展示这些分组。通过增强分组遍历逻辑和改进显示框架,应该能够解决这个问题,让调试过程更加直观和易于理解。
在这里插入图片描述

开始分支代码以处理单个元素和层次结构

我们正在处理一个判断是否为“组”的逻辑:如果是组,就走一条处理路径;如果不是组,就走另一条路径。我们决定暂时沿用已有的处理流程,先让整体功能跑通,再进一步处理细节。

在处理调试文本输出时,我们决定不再使用 debug_event_to_text,而是直接通过 debug_text_out 来输出调试信息。我们注意到,目前的实现不支持传入带长度信息的字符串,这一点在调试输出时其实是有些遗憾的。如果能支持直接传入字符串长度,就可以通过索引来遍历并输出每个字符,这样效率更高也更灵活。

虽然当前处理方案比较简陋,但考虑到我们还在处理中间状态的其他内容,暂时采用这种方式也可以接受。我们加了一个断言:判断 name_length 是否小于一个预期的最大文本长度,尽管在目前的情况下几乎总是成立,但加上这个判断更稳妥一些。

接下来,我们将字符串内容复制到一个文本缓冲区中,复制的长度是 name_length,并且在末尾多留一个字符用于空字符终结符。这样可以确保复制后形成一个标准的字符串。

至于处理组的逻辑,我们回忆中好像以前写过类似的代码,但一时没有想起来当时是怎么处理的。于是我们暂时搁置了这部分的深入处理,计划稍后再回顾旧代码来还原这一部分的逻辑。我们找到了深度追踪相关的代码位置,确认目前使用的是那部分逻辑。

我们还移除了一个不再使用的变量,同时发现有个叫 item_color 的东西暂时不知道它的作用,估计是某种临时启用的内容。

最后,我们采用了新的方式实现当前需求,并推测我们之前肯定写过类似逻辑的代码,后面可以再回头整理。整体来说,当前目标是先把流程跑通,哪怕方式暂时比较粗糙。
在这里插入图片描述

运行游戏,现在事件已按层次结构显示

我们之前可能曾删除过某些逻辑,不过这次在尝试时居然第一次运行就成功了,有点出人意料。尽管整个项目主题是关于“深度抑郁”的模拟,这种一次成功反倒让人有些无所适从,但结果确实符合预期。

当前我们已经正确地建立了层级结构模拟的渲染流程。在渲染过程中,层级结构展现得非常清晰,能够显示出两级结构,正如我们预期的一样。比如摄像机的渲染部分也完整地表现出了层级自组装的逻辑,这说明当前模拟流程是正确的。

接下来的目标是进一步完善这个流程:加入可以展开和折叠分组的功能。实际上我们之前已经实现过这部分功能,只需要恢复旧代码即可完成这部分功能。时间方面,我们大概还有七分钟左右,如果顺利的话,这点时间可能足够我们完成这部分的恢复。

我们也意识到当前的代码还有一些可以整理和优化的地方,不过这一部分可以留到下周再处理。现在的重点是先恢复分组展开与折叠的逻辑。

我们发现当前的问题是:原先用于管理分组展开/折叠状态的存储逻辑曾一度被移除。不过好消息是,这部分内容仍然保留在 debug_view 中,并未彻底删除。所以我们需要做的只是重新启用这段代码,确保其在调试视图中生效即可。

进一步检查代码后,我们发现与展开状态相关的处理逻辑依然存在,只是没有被激活。因此,我们距离完全恢复功能已经非常接近,只需要稍加整理就可以让它重新运行起来。整体进展顺利,下一步就是启用并验证分组状态的处理逻辑。
在这里插入图片描述

为层次调试事件添加交互支持

我们现在的目标是:实现基于视图的迭代,并支持分组节点的展开与折叠交互。目前的迭代逻辑结构混乱,特别是关于 stack 的处理方式显得过于复杂和晦涩,不够直观,未来需要重构以提升可读性和稳定性。

当前流程中,我们已经能够检查节点是否是“始终展开”的状态。接下来我们要做的是获取对应的视图信息。只需要从视图系统中取出该节点的视图状态,就可以基于此处理后续逻辑。

一旦获得视图,我们还需要为该节点创建交互行为,使其支持展开与折叠的切换。我们回顾了之前的交互逻辑,发现已有一个叫 toggle_value 的泛用交互行为,它可能曾被用于实现类似的功能。虽然看起来是为其他目的设计的,但它提供了一种机制,可以基于某些事件触发状态改变,这与我们当前需求类似。

不过在当前代码中,我们找不到专门用于“分组”的交互逻辑,这可能意味着我们需要新建一个专用于组展开/折叠的交互操作。我们尝试从已有的 toggle_value 中了解具体的行为,它是如何在事件发生时检测并处理状态变化的。

我们注意到其中提到了 open_data_block,这可能是用于打开数据块的操作,但这与我们现在想要的功能并不一致。我们希望实现的是:在点击某个组名时,自动切换该组的展开/折叠状态,而不是展开数据块。

因此,我们的核心需求是:创建一种新的交互事件,能够基于节点信息进行判断,然后更新该节点的展开状态。为此,需要实现一个逻辑更明确的交互绑定流程,确保其能够被当前 UI 框架检测并响应。

总的来说,目前我们:

  1. 已经能获取视图并确认展开状态;
  2. 正在准备创建一种新交互方式以控制展开/折叠;
  3. 考虑从 toggle_value 中复用逻辑;
  4. 发现之前的交互行为过于通用,不符合现在的需求;
  5. 需要开发更清晰、专用于组操作的交互机制;
  6. 后续还需简化当前堆栈式迭代结构,让整体逻辑更易读更易维护。

整体功能已接近完成,剩下的工作集中在交互细节的补全和整理。
在这里插入图片描述

在这里插入图片描述

重写如何展开组

我们现在明确了真正需要的交互类型:debug interaction 中实现“展开或折叠”的功能,也就是“toggle expansion”。原本的一些复杂处理逻辑,例如使用 VarLinkInteraction 之类的方式,其实并不适合当前的需求。我们决定舍弃原先那些不必要的部分,转而采用更直接的方式来实现这个交互。

具体来说,这个交互其实只需要简单的操作逻辑,我们可以直接调用展开/折叠的切换函数,不需要通过冗长的中间流程。因此,不再使用所谓的“VarLinkInteraction”,而是改为普通的 DebugInteraction_ToggleExpansion

我们已经有了构造视图中 link 的方式,只要基于这个 link 创建一个交互实体即可。交互对象还需要一个名字,便于在调试或界面中识别。接下来,我们只需将“切换展开状态”的逻辑函数传入这个交互对象中,就能完成我们想要的功能。
总结当前思路:

  1. 明确我们真正需要的交互是“切换展开状态”;
  2. 抛弃复杂的中间逻辑,采用更直接的调用方式;
  3. 使用 DebugInteraction_ToggleExpansion 构建交互行为;
  4. 基于树结构的 link 创建对应交互;
  5. 为该交互指定名称;
  6. 传入用于切换展开状态的处理函数;
  7. 最终实现点击节点时即可展开或折叠分组视图。

通过这套逻辑,我们能够用更清晰、更高效的方式恢复并简化原有的分组交互功能,使得整体行为更容易维护和扩展。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

游戏运行,交互现在正常工作,但高亮显示不正确

目前我们已经让功能正常运行了,这是一个积极的进展。不过现在出现了一个奇怪的问题:某些元素始终被判定为“hot”(即处于交互激活状态),这显然是不符合预期的行为。

具体来说,我们注意到 item_colornative_interactiontree_link 等相关元素都处于高亮状态,而这些本应是与具体节点相关的交互项。按理说,每个树节点的 link 都是不同的,每一次都应该是独立创建的对象。我们检查了用于生成交互的 DebugIDFromLink,看起来是正确的,link 也确实应该是每个节点不同的实例。

理论上来说,这种高亮状态应该是和具体的 tree_link 绑定的,意味着只有鼠标悬停在特定节点上时,它才会触发 hot 状态。但实际表现却是所有节点都被判定为 hot。这种现象说明交互判定的条件出现了某种混乱,可能是由于判断 id 时误用了某个共享对象或者引用。

我们还观察到,debug_id 的构造可能也存在问题。理论上,如果我们用相同的方式构建 debug_id,而这些 id 本应具有唯一性,却被统一处理了,也有可能导致全体元素都触发同一个交互状态。也许我们应该明确区分每个 tree_linkid 实例,确保它们在交互系统中是唯一且独立的。

当前结论如下:

  1. 交互系统中所有节点都被错误地判定为 hot;
  2. 原因可能在于用于标识的 debug_idlink 被错误地复用或共享;
  3. 每个树节点应该有独立的 linkid,目前可能存在统一引用的问题;
  4. 高亮状态的判定应基于鼠标实际悬停的节点,但系统可能在内部将它们视为相同对象;
  5. 需要进一步排查 DebugIDFromLink 的实现细节,确认是否真正实现了唯一性;
  6. 可能需要调整 debug_id 的生成逻辑,确保其与 tree_link 唯一绑定。

虽然现在运行功能基本正常,但这种全局“hot”状态显然是不合理的。下一步我们将集中精力排查这一交互判定机制的问题,以确保每个节点行为独立,避免交互逻辑混乱。

我们发现当前的交互逻辑存在一个核心问题:判断交互是否相同时,并没有对 debug_id 进行检查。这意味着多个交互只要其它条件相似(比如结构或类型相同),就可能被错误地视为同一个交互对象,进而导致所有节点都被错误地标记为“hot”状态。

这显然是不合理的。我们原本设计时,每个交互对象都应该具备唯一的 debug_id,而判断两个交互是否相等时,理应比较它们的 id。但现在这部分逻辑被遗漏了,很可能是在代码的重构或演进过程中不小心漏掉的。

因此我们明确认为,这种行为并不是有意为之,而是疏忽导致的逻辑缺失。更准确地说,判断交互是否相等时,应该包含:

  • 比较交互类型;
  • 比较其它关键信息(例如 link 或行为);
  • 最重要的是比较 debug_id 是否相同。

如果两个交互的 id 不一样,就应该被视为完全不同的交互实例。当前缺失这一步,才导致所有节点使用相同的交互实例,从而在视觉和逻辑上都出现了明显错误。

我们应该立刻修正这一点,明确在交互相等判断逻辑中加入对 debug_id 的比较:

if (a.debug_id == b.debug_id) {// 是同一个交互
}

这一修正将带来以下好处:

  1. 每个节点根据唯一 ID 区分交互行为;
  2. “hot” 状态只在对应节点正确触发;
  3. 避免全局交互误判;
  4. 还原之前设计中各节点交互独立、状态分明的意图;
  5. 为后续调试与维护打下更清晰的基础。

总结来说,这是一个由于重构过程中疏忽导致的重要 bug,我们已经识别出根因,并清楚如何修复它。下一步只需在交互判断中补上对 debug_id 的检查,即可恢复系统的正确行为逻辑。
在这里插入图片描述

一切正常运行

你在多个地方传递字符串长度和字符串数据的指针。你认为创建一个包含长度和数据指针的字符串结构体会更好吗?

我们讨论了关于在多个地方传递字符串长度和数据指针的问题,是否应该封装成一个包含长度和数据指针的字符串结构体。

我们的结论是:目前来看,这种方式(分开传递)并没有带来明显的麻烦或负担,所以并不觉得有必要马上引入结构体封装。虽然在某些情况下这样做是有道理的,特别是当管理字符串变得复杂、操作频繁时,封装会带来一定的整洁性与安全性,但在当前的上下文中,并没有感觉到强烈的需求。

我们有时确实会使用这种结构体形式来组织字符串,但这次的使用场景还没复杂到必须这么做的地步。因此,我们不反对这种做法,但当前并不急于去改。

总结如下:

  1. 目前通过传递字符串长度和指针的方式仍然可控;
  2. 尚未造成实际的困扰或出错;
  3. 可以在未来视复杂度决定是否封装成结构体;
  4. 不是一种必须立刻优化的点;
  5. 保持当前实现简洁也有一定好处;
  6. 封装结构体的做法本身是中立的,但没有绝对必要性时不优先考虑。

最近你谈了很多关于编程语言的事,你有看过 Swift 吗?有什么想法?

我们提到最近经常在谈论“层”(layers)这个概念,然后被问到是否看过 Swift,以及对它的看法。

我们回应说完全没有对 Swift 感兴趣,也没有花时间去了解它。印象中 Swift 是一种解释型语言,而如果确实如此,那我们就更加不愿意去接触它了。

这种态度背后反映出以下几个核心观点:

  1. 对 Swift 没有主动的兴趣或需求;
  2. 对解释型语言本身存在一定的偏见或排斥,可能更偏好静态编译型语言;
  3. 开发风格更偏向底层控制和高性能,不太倾向于使用高级抽象较多的现代语言;
  4. 没有被 Swift 的特性或生态吸引,因此也没有探索的动机;
  5. 总体态度是:“不是必要,就完全不碰”。

总结如下:

  • 并未关注或学习 Swift;
  • 认为它是解释型语言,因此更加抗拒;
  • 更倾向底层、可控、高性能的语言和开发方式;
  • 没有兴趣深入了解,也不打算采纳;
  • 对于引入“层”这一类现代抽象,也持有一定保留态度。

你认为在 C++ 中,调试交互是否会通过编译器生成的比较操作符被捕获?

我们在讨论调试系统中的 interactions are equal 函数是否能通过编译器自动生成的比较操作符来发现 bug。首先,我们不清楚 C++ 有自动生成比较操作符的功能,这在过去并不是语言特性的一部分。这个机制似乎是后来新增的一个特性,让结构体或类可以默认生成成员之间的比较逻辑。

尽管如此,这种自动生成的比较操作符在当前情况下并不能真正解决我们的问题。原因在于我们使用了联合体(union),而自动生成的比较操作符通常无法处理 union 类型中的不确定性。因为 union 只能存储一个活跃成员,编译器在没有明确上下文的情况下,无法准确判断该比较哪个成员,也就无法生成合理的比较逻辑。

因此,即便有这种自动生成的特性,它也无法智能地为包含 union 的结构体生成正确的比较行为。这会导致比较结果不可靠,进而无法帮助我们发现 interactions are equal 中的潜在问题。

总结来说,即使 C++ 现在有了自动生成比较操作符的机制,它也无法应用到我们这种涉及 union 的复杂数据结构中。这种自动机制的智能程度不够,面对结构中存在条件判断或变体选择时,就显得过于“笨拙”,无法代替手动实现的逻辑。因此,这类 bug 还是需要我们通过手动编写逻辑、理解数据结构语义的方式去识别和处理。

当然可以,下面通过一个具体的例子来说明为什么 C++ 的自动生成比较操作符在处理包含 union 的结构时无法帮我们发现 interactions are equal 函数中的 bug。


示例结构体

假设我们有一个结构体 Interaction,它内部使用了 union 来存储不同类型的数据:

#include <string>struct Interaction {enum class Type {IntType,FloatType} type;union {int i;float f;};std::string name;// 我们可能想比较两个 Interaction 是否“相等”// 所以需要自定义 operator==,或者试图使用编译器自动生成的版本
};

C++ 自动生成的比较操作符(C++20)

C++20 开始,C++ 引入了 三路比较运算符(即 <=>,也称为“宇宙飞船运算符”),并允许你通过加 = default 的方式自动生成比较逻辑:

auto operator<=>(const Interaction&) const = default;

这段代码的作用是让编译器自动生成成员比较逻辑,就像自动生成构造函数一样。


问题所在:union 的比较不可行

但是这个自动生成逻辑无法处理 union,因为 union 只能存储一个活动成员,编译器并不知道应该比较哪个字段,也不能推断出当前 union 正在使用哪种类型。

比如以下两个对象:

Interaction a;
a.type = Interaction::Type::IntType;
a.i = 42;
a.name = "foo";Interaction b;
b.type = Interaction::Type::FloatType;
b.f = 42.0f;
b.name = "foo";

这两个对象的 type 不同,但如果编译器自动比较 if,它并不会知道应该比较哪一个,甚至可能比较出错(比如读取未定义的 union 成员),更不可能发现这类 bug。

而我们人工写的比较逻辑会清楚地按照 type 来判断当前使用的是哪一个 union 成员,并据此做出正确的比较:

bool interactions_are_equal(const Interaction& a, const Interaction& b) {if (a.type != b.type || a.name != b.name) return false;switch (a.type) {case Interaction::Type::IntType: return a.i == b.i;case Interaction::Type::FloatType: return a.f == b.f;}return false;
}

总结

自动生成的比较运算符适合于结构简单、字段全都是 POD(plain old data)类型、没有 union 的场景。而我们这里的结构中用了 union,又依赖于 enum 来判断当前活跃成员,所以自动生成的方式无法准确处理逻辑,也就无法发现或避免我们实际代码中的 bug。

这种情况下,必须自己手动写比较逻辑,以确保比较行为是符合语义的、不会触发未定义行为的。

你最喜欢的计算机领域人物是谁(无论生死)?目前我最喜欢的是 Claude Shannon

我们被问到在计算机领域中最喜欢的人物是谁(无论是去世的还是在世的),对方表示自己最喜欢的是克劳德·香农(Claude Shannon)。我们回应说这是个不错的选择。

接着我们表达了自己的想法,说我们喜欢高斯(Gauss),但他其实更偏数学领域,不算严格意义上的计算机领域人物。

总结如下:

  1. 对香农作为选择给予肯定,认为是个好人物;
  2. 提出了自己偏好的历史人物——高斯;
  3. 指出高斯虽然并不属于计算机领域,但其在数学上的贡献依然令人钦佩;
  4. 透露出对基础理论和数学之美的欣赏;
  5. 回答风格随性真实,没有刻意迎合,而是自然表达个人喜好和思考。

问题:你认为英特尔达到了处理器尺寸极限,会导致视频游戏发展停滞吗,还是他们能继续生存?

我们认为,就算未来硬件发展接近冻结,触及所谓“SAS(半导体面积缩放)”的极限,电子游戏行业依然可以很好地生存下去。

首先,当前电子游戏产业的发展,已经不再严重依赖硬件性能的持续提升。我们观察到,如今很多顶级的游戏系列,其技术表现其实并不特别突出。例如最近的《使命召唤》系列作品,画面技术并不先进,甚至有些显得过时,但这并没有影响它的销量或受欢迎程度。

这说明整个游戏市场对“最新最强硬件”的依赖正在减弱。玩家更关注内容、玩法、社交互动、叙事和品牌影响力,而不是纯粹的图像质量或硬件压榨能力。因此,即便硬件性能不再快速提升,只要游戏内容有吸引力,市场就仍然会维持活力。

总之,哪怕未来硬件进步趋缓,游戏行业仍有广阔的发展空间,不仅不会崩溃,反而可能更加专注于创造性和可持续的内容生态,而不是过度依赖技术突破来推动迭代。

你怎么看待测试驱动开发?

关于测试驱动开发(TDD),已经回答过很多次了,因此这次就不再详细讨论了。可以说,TDD 的理念和实践已经被广泛讨论过,很多开发者都有各自的看法和经验,因而这个问题已经不再是新的话题。对于它的看法,我之前已经在多个场合中表达过,因此这次就跳过不再重复回答。

你更喜欢在 C 代码中做 UI,还是使用像 HTML 这样的东西?

我们被问到如果要编写代码是更喜欢使用 C 语言还是 HTML 等其他语言。我们回答说,自己始终更偏爱使用 C 语言,或者更具体来说,更喜欢编写与 C 语言相关的程序。

虽然提到更倾向于 C 语言,但依然没有完全排斥其他语言,暗示在某些情况下,使用其他工具或语言也是可以接受的。

总结如下:

  1. 更偏向于使用 C 语言编程;
  2. 强调对 C 语言的喜好,尤其是在编写程序时;
  3. 仍然保持开放的态度,暗示在特定情境下也能接受其他语言;
  4. 展现出对底层编程的偏好以及对 C 语言的理解和熟悉。

有时我会拿自己和其他伟大的程序员做比较,结果感到沮丧,因为我离他们的水平还远——我知道不应该拿自己和别人比较,因为他们成长的环境不同,而且我知道自己只要努力学习,最终能赶上他们,但有时候就是会觉得难过。你有过这样的感觉吗?有什么克服这种情绪的建议吗?

在面对编程能力方面的比较时,首先要认识到,每个人的成长环境和天赋不同,所以不应该对比自己和别人。我们每个人的基因和背景不同,天生的编程天赋也不尽相同,而这些是无法改变的。然而,努力工作和投入时间是可以掌控的,最终决定一个人成就的往往不是天赋,而是付出的努力。

首先,重要的是不要让这些比较影响自己的情绪。每个人的成长路径不同,所以不必对照别人来衡量自己。你需要关注的是自己能否在编程的道路上不断进步,而不是自己是不是天生就是最聪明的程序员。无论你天生有多大的天赋,关键在于你能否通过不断努力和每天的编程积累,最大化自己的潜力。

另外,编程没有所谓的“编程奥林匹克”,每个人在编程世界中的地位并不重要。更重要的是你能做到什么,以及你是否享受这个过程。没有必要因为和他人进行比较而感到沮丧,因为编程的意义更多在于实现自己的目标,解决问题,并从中找到乐趣和成就感。

此外,也没有必要担心自己不够聪明。只要能够理解编程的基本概念,提出问题并寻找解决方案,那么成功的可能性是非常大的。大多数成功的程序员并不是天生就具备过人的智力,而是通过不断的实践和努力,才达到了今天的水平。所以,不要担心自己不够聪明,重要的是每天写代码,遇到问题就解决问题,积累经验。

最后,编程是一个长期积累的过程,就像学习一门外语一样,不可能在短时间内成为大师。很多人刚开始编程时都遇到过困难,而真正成为优秀的程序员需要很多年的时间和实践。如果你才编程几年,无法成为顶尖程序员是很正常的,因为编程不仅仅是技术问题,更是对问题的理解、逻辑思维的训练和持续的学习。

总之,保持耐心,接受自己的进步过程,并且不断实践和学习,不要急于求成。每个人都有自己的节奏,只要坚持不懈,最终都会实现自己的目标。

你认为日本没有采用面向对象编程范式是因为他们更聪明,还是只是因为他们对更换新语言没有兴趣?

我们被问到,是否认为日本没有采用面向对象(OO)编程范式是因为他们更聪明,还是因为对新语言不感兴趣。

我们回应表示对这个问题并不知情,甚至不知道日本没有采用面向对象编程范式。我们承认自己对这一点没有任何了解,表示即使知道相关信息,也不认为自己能对此发表什么有见地的看法。

总结如下:

  1. 对日本是否采用面向对象编程范式的问题表示不知情;
  2. 明确表示自己对这个话题不了解,也未曾听说过类似的情况;
  3. 即使了解相关信息,也不认为自己能对此做出有意义的评论;
  4. 表现出对该问题的无知与开放态度。

伪代码问题:怎么学习汇编语言?编译器/链接器是用它写的吗?

学习汇编语言的方式有几种方法。首先,需要了解的是,汇编语言在今天的程序开发中并不是像过去那样常见。过去,尤其是在像凯文64(Commodore 64)或者苹果二(Apple II)这样的早期计算机上,编写整个程序的代码使用汇编语言是非常普遍的,而且也非常常见。然而,现如今,如果有人说他用汇编语言编写了整个游戏,别人可能会觉得非常奇怪,因为如今几乎没有人会用汇编语言编写整个程序。

现代使用汇编语言通常是对程序中的一些小部分进行优化,尤其是那些性能要求极高的部分。至于编译器和链接器,它们几乎从未用汇编语言编写。通常,开发者会使用现有的编译器或操作系统来编写新的操作系统或编译器,甚至使用交叉编译技术来编写一个可以自我编译的编译器。

如果想学习汇编语言,可以使用调试器来辅助学习。最好的学习方式是通过调试器查看它生成的汇编代码。通过这种方式,可以在编写某段代码后,看到调试器为该代码生成的汇编语言代码,这样可以直观地了解汇编代码是如何生成的。更进一步,任何用C语言编写的代码,都可以通过调试器直接查看它所对应的汇编代码,这对学习汇编语言非常有帮助。

此外,阅读在线教程也能帮助入门,尤其是那些详细解释如何从高层语言(如C语言)生成汇编代码的教程。通过这种方式,学习者可以更快速地掌握汇编语言,尤其是通过直接与调试器交互,快速查看和理解生成的汇编代码。

总的来说,学习汇编语言的关键是通过实践和不断查看编译器生成的代码来提高理解,而不是死记硬背汇编指令。

你是如何获取像 Clang 或 Linux 内核这样的大型代码库的概览的?

对于如何处理像Clang或Linux内核这样的大型代码库的概述,答案是:对于这种任务,我并不是最合适的人选。有人可能会问类似问题,但我并不擅长这种类型的代码,像这种问题更适合向某些特定的人请教。比如有一个人非常擅长这类工作,他能够应对这种复杂的代码库。

如果有人告诉我需要修复Clang中的一个bug,我会觉得自己不想去了解那部分内容,也不愿意去看这些代码。对于我来说,这样的任务就像是避而不见,我甚至不想去接触它。

整体来说,面对一个庞大的项目或代码库时,我不太愿意深入去理解它,因为这种工作确实需要特定的技能和方法,而不是每个人都能快速适应和掌握的。我更愿意把这类任务交给那些更有经验的人。

版权声明:

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

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

热搜词