美丽的神话 发表于 2025-4-17 17:24:51

游戏引擎学习第219天

游戏运行时的当前状态

目前的工作根本上就是编程,带着一种预期,那就是一切都会很糟糕,而我们必要一个系统来防止它变得更糟。接下来,我们来看看目前的盼望。
扼要分析昨天提到的无限调试信息存储系统

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

现在,我们将尝试将之前分散开发的调试系统的各个部门重新整合在一起。
现在,我们已经有了一个清晰的思路,知道该怎样存储这些信息。起首,我想恢复的是层次布局。我们将使用层次布局来构造信息,因为层次布局能让信息更易于访问,这符合人类的风俗。同时,这也是我们没有其他选择的原因,为了适应这种布局,我们也只能这样做。
开始工作在分层调试事件UI上

我们已经存储了全部这些事件,现在必要做的就是构建一些用户界面元素,将它们按照层次布局放入一个严酷的品级制度中。在这个品级制度中,事件没有任何向上活动的可能性,除非那个无所不能的用户决定干预,将其重新定义并进行调解。
描述调试事件怎样按层次布局进行分组

我们想要做的是,当第一次碰到一个事件时,将其根据来自代码中的位置进行分类。这个过程就像是一种不考虑个体的行为,不管这些事件的内部含义是什么,我们都按照泉源将它们归类。我们对它们的内部特征绝不关心,只根据它们来自那里,把它们放进相应的地方。
当我们创建这些事件元素时,如果它们包罗层次信息,我们盼望将这些事件整合进层次布局中,接下来,我们将生存并尝试进行这些操作,只管这些层次布局可能会有所问题,我们仍然坚持进行这个过程。
将根树的术语更改为根组

起首,决定不再将其强制设定为树布局,而是回到根组的概念。并没有必要坚持称其为树,只必要定义为一个组。这个组的构建方式可以和树布局类似,但并不一定非得是树形布局。可以说,这就是一个组,树形布局可以选择去展示这个组,如果它不想展示,也没有问题。
接着,我们将创建这个根组,就像之前做的一样,直接生成一个根组。这一过程是为了确保组能够按照我们想要的方式构建,而不必要强制规定其为树状布局。
https://i-blog.csdnimg.cn/direct/a6f4bda1e41744b0ab7d9ebe0279af8f.png#pic_center
重新启用新系统的层次布局生成

我们开始重新建立那些可以创建层级布局的部门。根本上全部必要的东西都已经具备,只是必要将它们“复苏”一下,使其适应新的系统布局。我们必要对其进行一种“普鲁克拉斯提安式”的改造——不管这些函数最初是怎样的、不管它们的愿望和抱负是什么,我们都要强行让它们符合我们的设定,就像把人放上普鲁克拉斯提斯的床,锯掉或拉伸肢体直到符合长度。
接下来我们查看两个关键函数:AddVariableToGroup 和 CreateVariableGroup。其中 CreateVariableGroup 相对简单,没有太多内容,它根本上就是一个链表,因此我们认为可以继续沿用之前的方式。变量组本身是静态的、固定的内容,因此可以继续以雷同的方式分配内存。
不过,接下来我们开始考虑未来的改进方向。我们盼望这些布局能够基于“自由列表”进行管理,即内存能够重复利用,而不是每次都新建对象。所以我们开始考虑将这些布局渐渐向自由列表的情势过渡,以便未来更高效地管理资源和内存。
“普鲁克拉斯提安式”确实是从英文 Procrustean 翻译过来的,但中文里常见的译法是:


[*]普罗克鲁斯提式
[*]普鲁克拉斯特式
[*]普洛克鲁斯特式
这个词源于希腊神话中的普罗克鲁斯特斯(Procrustes),他有一张床,会把路人放上去,如果不合尺寸就锯掉或拉长肢体,直到“合适”为止。这个典故引申成了“强行统一尺度、无视个体差别”的意思。
所以更尺度的写法应该是:
普罗克鲁斯特式的改造
不过“普鲁克拉斯提安式”虽然少见,但意思还挺准确,算是一个音译+形容词情势的混淆用法,不算完全错,只是不太尺度。
https://i-blog.csdnimg.cn/direct/8b5a1b36b72241d6842ec4554c067a31.png#pic_center
考虑是否为调试信息添加空闲列表

我们决定暂时将一些关于内存回收的优化推迟处理。虽然这些优化最终可能是有必要的,但目前看来,调试系统本身所涉及的用户交互量非常有限,因此对内存的需求并不会很高。即使不做内存回收,直接使用静态分配的方式也完全可以接受。
从功能完整性的角度来说,未来我们可能照旧盼望能让系统更加健壮,具备主动回收无用内存的能力。但现阶段,我们更倾向于把这部门工作延后,因为它不是当前的重点。就像现实生活中的人们一样,大多数人在不得不回收资源前,并不会真的关心回收利用,而是任由废弃物堆积,直到情况被彻底破坏、地球变得荒芜。
因此,在设计调试系统的内存布局时,我们现在选择了一个更简单直接的实现方式,即不考虑复杂的内存回收逻辑,而是优先保证系统的可用性和开发服从。等到真正碰到资源瓶颈时,再来完善这部门功能也不迟。
更新 AddVariableToGroup,CreateVariableGroup 以便与树布局中的元素一起工作

我们现在开始重新梳理向组中添加变量(现在更准确地说是元素)的逻辑。之前的实现中,是将事件参加某个组中,而现在的模型更接近于添加“元素”,这些元素可以是变量或其他调试信息。我们将原有的“添加变量到组”的函数,渐渐改造为“添加元素到组”,传入的元素就是我们真正盼望处理和存储的内容。根本逻辑和之前类似,不必要做太大改动。
不过我们留意到一个关键问题:当前的组布局无法拥有子组,缺乏层级布局支持。也就是说,变量组没有“父组”或“子组”的概念,它们是孤立存在的。这种布局既没有泉源也无法延续,就像一个永远无法成为父母的孤儿,被剥夺了构建家庭和延续意义的可能,是一种存在但又徒劳的状态。
为了改变这种状况,我们必要在变量组的创建过程中引入对层级布局的支持。也就是说,在创建某个变量组时,可以选择性地传入一个“父组”参数。如果指定了父组,那么这个新建的变量组就应该挂载在那个父组之下,成为它的子组。这样我们就能渐渐构建出完整的层级关系。
同时,在“添加元素到组”的逻辑中,我们也考虑了必要实现“添加子组到组”的功能,以便在调试界面中维护层级关系的可视性。通过为组布局增加对父子关系的支持,我们可以更公道地构造调试信息,使其更符合人类风俗的树状布局展示方式。
下一步就是在构建和维护组的过程中,实现对子组的挂载和遍历机制,使得每个组既可以承载调试元素,又可以作为其他组的容器,真正具备上下文与附属关系。这样,整个调试系统的布局也会变得更加有层次、更加可控。
https://i-blog.csdnimg.cn/direct/042100abe7ba4bddbabd6676c78d0986.png#pic_center
将 AddVariableToGroup 拆分为 AddElementToGroup 和 AddGroupToGroup

我们现在盼望实现的是将一个子组添加到另一个组中,从而建立完整的层级关系。这个过程类似于之前将元素插入组的操作,不过这次插入的是一个子组。逻辑上依旧是通过链接布局完成,将子组作为链表节点插入到父组的“子组列表”中。
具体步骤如下:
起首,为这个子组创建一个链表链接节点,然后将其插入到父组的子组链表中。区别在于,这次不是插入调试元素的链表,而是插入专门用来维护子组的那一份链表布局中。插入完成之后,还必要建立引用关系,让子组知道它的父组是谁,也就是设置“父组”指针。
至于原有一些附加逻辑,比如设置某些状态或元数据,在这种上下级组之间的添加中可能不再适用,因此可以忽略或移除这些逻辑片段。它们在这个上下文中是无意义的。
接下来反思布局设计。我们留意到,在目前的布局中,一个组要么拥有子组,要么拥有调试元素(比如变量、事件等)。也就是说,每个组要么是一个容器,要么是一个叶子节点,不能同时拥有两种内容。这种设计看似公道,但也可能带来一定的限制。例如,如果一个组既想作为分类的父节点,同时又必要记载一些属于它自身的调试信息,就可能碰到设计障碍。
现在暂时接受这种布局,并默认一个组在层级中的脚色是单一的。如果未来发现这种限制拦阻了调试布局的表达力,再考虑引入混淆模式,即允许组同时存在子组和元素。但目前我们采用“单一内容类型”的构造方式,即一个组不是容器就是叶子,布局相对简单清晰。
总结来说,现在的任务是:

[*]实现子组插入到父组的逻辑;
[*]保证链表布局与引用指针的准确性;
[*]忽略对这个上下文无意义的逻辑;
[*]维持组布局的当前设计理念,即“只能容纳子组或元素之一”。
这套机制完成后,我们就可以用它构建起真正的调试层级布局,开始将散落的调试信息系统化地构造在一起。
https://i-blog.csdnimg.cn/direct/dc6ed50603fb4e14927b5e872b24a361.png#pic_center
留意到名称未存储在 debug_variable_group 中,并进行修复

我们意识到当前的调试变量组(debug variable group)虽然已经具备了子组布局,可以支持构建分层体系,但缺少了一个非常关键的属性:名称。每个组虽然有了父子关系的指针和链表布局,但我们并没有为它们提供名称字段,因此在整个系统中,无法辨认一个组“是什么”或“代表什么”。它就像一个无名的存在,仅仅被指针和内存地址标识,而非一个有意义的个体。
这种缺失显现了我们设计中对个体标识的漠视。在这种机制下,调试信息的构造虽然可以形成布局,却无法表现出每一个分组的语义。我们默认全部的组都是匿名的,它们不必要名字、不必要身份,也不必要任何个性化特征,仅仅充当布局容器。正如系统中一段无情的逻辑那样,只关注其作用,而非其存在本身。
从技术上来说,我们提出一个改进方向:为变量组添加一个名称字段。这意味着每个调试变量组在创建时,应当带有一个字符串类型的名字,用于标识其用途或含义。这样,当我们遍历调试信息时,可以清楚地看到每一组数据所属的种别或功能,不再只是看内存地址或布局层级。
我们查抄了当前的布局代码,发现虽然包罗了父节点和子节点的指针,比如 parentSentinel,但没有表现组的“名称”这一核心属性。因此下一步,我们准备在组布局中添加这个字段,并在构造函数或创建逻辑中进行设置。
在完成这个布局扩展后,我们接着还需解决另一个核心问题:**怎样根据调试事件的信息主动归类到准确的组中。**换言之,我们必要一个机制,能根据事件泉源、层级标识或其他元数据,判断它应当归属于哪个组,并主动建立这种归属关系。这一部门是整个分组体系动态运作的关键。
所以当前阶段我们重点完成的是:

[*]为每个变量组引入名称字段,提拔布局可读性与可辨认性;
[*]保留并清晰化父子指针布局,用于构造树状层级;
[*]为后续事件归类准备基础布局,下一步将实现归类逻辑。
这使我们能够开始构建一个有“名字”的分层布局,不再是无名编号的集合,而是一个具备语义和意义的调试体系。
https://i-blog.csdnimg.cn/direct/6180a295a05040cfb83cabb0027c564d.png#pic_center
移除 CreateVariable

我们目前确认了一点,即“创建变量”这部门逻辑已经没有继续保留的必要。因为当前的设计流程中,我们始终是直接添加调试元素(element),而不再单独进行变量的创建操作。原本那段与“创建变量”相干的代码,根本上属于旧的遗留布局,在当前系统布局中已经不再起作用。
回顾其逻辑,我们并没有找到它对现有数据流、事件构造或调试组布局还有实际价值的部门,所以决定将其彻底移除。这样不仅能淘汰无用的冗余代码,还能让整个系统逻辑更清晰、会合于我们真正使用的部门。
我们保留了一个未实现的“开释变量组”的函数,即 free variable group。虽然目前还未涉及主动开释资源的需求,但保留这个接口作为占位符是公道的。它的存在可以确保在未来如果我们开始处理内存回收或盼望支持布局复用时,有一个明确的位置可以接入资源管理逻辑。
至于一些其他布局,比如原先为“创建变量”服务的附属逻辑和数据布局,现在也被判断为无关内容,因此一并删除或废弃。
总结来说,此阶段我们完成了以下几个清算与调解:

[*]删除了“创建变量”相干的旧逻辑,确认为当前系统无用;
[*]保留了“开释变量组”接口,未来可用于资源回收;
[*]整理代码布局,避免遗留逻辑干扰现有系统演进;
[*]将调试系统的核心更加聚焦在“调试元素”和“变量组”之间的构造关系,而非单独变量。
通过这次清算,我们让系统更接近现在的使用场景,也为后续的维护与扩展打下了简便稳定的基础。
https://i-blog.csdnimg.cn/direct/cf61d69af89d44ddb8eb5801b5e1eb47.png#pic_center
在 GetGroupForHierarchicalName 中添加根变量组处理

目前我们只剩下最后一个关键部门必要处理,即补全先前尚未实现的逻辑。其实调用方式是准确的,接口设计也没有问题,只是之前由于忙于其他部门的开发,这部门功能暂时被搁置了。因此,现在要做的就是实现这个函数,让整个系统真正运行起来。
我们再次查抄了之前的实现方式,发现可以继续沿用此前的思路。可以先构建一个暂时布局用于辅助调试信息的构造,然后专注于实现这个核心功能。实现本身不会特别困难,主要是代码输入量稍微大一些,逻辑上是清晰的。
当前团体架构已经搭建得比较完整了,所以接下来我们要做的是在合适的地方调用这些新构建的逻辑。例如,在初始化调试状态(debug state)的时间,就应该调用这些函数来完成初始布局的建立。
在初始化流程中,我们会设置调试系统的基础状态,所以显然在这一步就必要创建调试用的根组,或者相干的布局数据,确保后续可以将全部调试元素有序地插入到准确的位置上。
接下来我们的目标是:

[*]实现之前未完成的核心函数,让它执行预期的布局创建与数据插入;
[*]在调试状态初始化时,调用这个新实现的函数,完成调试布局的起步配置;
[*]确保一切初始化完成之后,后续事件或调试元素的添加操作可以正常工作,布局清晰、层级准确。
总体来说,我们已经完成了调试系统的绝大部门基础架构建设,现在要做的是将各个部门正式毗连起来,使整个系统真正“活”起来,并可以相应实时的调试事件流。
https://i-blog.csdnimg.cn/direct/6ec0c2cefafa4217903b12aa996cd6b3.png#pic_center
创建根变量组

当前的任务是将调试系统中已经建立好的根组与变量组的创建逻辑整合起来,并完成事件的分层存储流程。起首确认在创建变量组时,只必要传入调试状态(debug state)即可完成根组的初始化,因此现在已经拥有一个可用的根组,准备好接收调试信息。
接下来,目标是将系统各部门串联起来,实现调试事件的层级式添加。也就是说,未来能够以分组的情势将调试元素插入到准确的层级中,从而构建出布局清晰、逻辑明确的调试树。
为此,必要查看事件流的处理逻辑,确认应该在那里插入调试元素。颠末查看,发现并非是在 store_event 函数中处理,而是应当定位到 GetElementFromEvent 函数,这是整个事件剖析过程的核心——事件转换为调试元素的实际位置。
因此,接下来的工作包括:

[*]在创建变量组函数中确认只必要传入调试状态作为参数,便可生成根组。
[*]在 GetElementFromEvent 函数中,接收事件,并将其转换为调试元素时,参加与根组的绑定逻辑,使其被添加到准确的层级布局中。
[*]在事件被剖析为调试元素的那一刻,按照事件泉源或布局信息,插入到对应的变量组中,保持层级布局的清晰性。
[*]这将使得调试信息不再是线性、杂乱的集合,而是可视化、有布局的分层内容,便于后续查阅和分析。
团体来说,调试系统的根架构已经完成,只需将事件的剖析效果准确插入到层级布局中,即可完成功能闭环,达到“事件主动分组”的效果。接下来即可动手实现该插入逻辑,并做测试验证其准确性。
https://i-blog.csdnimg.cn/direct/d367085b40f942c9841cfc1846f2e4ab.png#pic_center
在 GetElementFromEvent 中添加层次布局查找

当前处理逻辑聚焦于当接收到一个首次出现、泉源未知的事件时,怎样进行层级查找和布局归类。此时,会触发调试系统的“未知泉源事件”处理分支,尝试为其在哈希表中创建并注册新的调试元素。
我们意识到,这正是引入分层布局查找机制的关键时机。过去的处理方式并未关注事件泉源的布局性,只是将其作为一个独立个体简单存储。而现在,系统将采用带有层级概念的定名剖析方法,比如通过下划线 _ 来辨认路径或布局分段,借此推导出其在分组布局中的位置。
然而,当前项目中并非全部内容都严酷遵循这种定名规范,因此不能完全依靠下划线划分来进行层级推导。原有代码中有 block name 的相干机制用于定名标识,但其布局也并不统一。因此,当前阶段采取的计谋是——统一处理所著名称,不做区分地进行尝试性归类,再在后续整合与测试过程中渐渐优化定名辨认逻辑。
具体处理流程如下:

[*]接收到事件后,起首判断其是否为新泉源。
[*]若为新泉源事件,则为其创建调试元素并注册入哈希表。
[*]在创建调试元素之前,尝试通过名称(如带 _ 的路径)进行分层定位,从根组开始逐级查找或创建中间变量组,以构建该事件所属的完整层级路径。
[*]最后将调试元素挂载在该路径末了的组中,确保整个调试系统保持布局一致性与可视性。
通过这一计谋,调试事件不再是孤立存在,而是被纳入具有上下文与层次的体系中,方便后续逻辑追踪、可视化分析及调试服从提拔。此过程将是实现全系统布局化调试管理的关键一步。
思考怎样使用 BlockName 或 GUID 来创建层次布局

当前讨论的主要问题是怎样将调试数据构造到统一的层级布局中,尤其是在不同的代码位置生成的调试数据被收集时怎样存储和归档。当前使用的定名规则(如 block name)用于对数据进行分组和管理,但随着需求的变革,可能必要做出调解。
关键点总结:


[*] 数据块分组:调试数据的分组使用了名为 block name 的标识符,这个标识符帮助将数据从不同位置进行构造。现在考虑是否保持这种定名方式,或者改用更统一的方式(例如使用 GUID 布局)来分类和归档调试数据。
[*] 层级布局的形成:目前考虑将调试数据按照某种逻辑创建数据块,然后根据这些数据块形成层级布局。此层级布局可能会在未来必要进行进一步的调解和优化,以确保数据可以根据其泉源公道分类。
[*] 调试变量定名:调试变量的定名方式(例如 ground_chunks_checkerboard)目前是基于某些逻辑进行的,这些定名对于理解调试数据是有意义的。然而,关于怎样统一调试数据的定名和存储方式,仍然存在一些待解决的问题。例如,怎样确保全部调试数据都能够在系统内使用统一的概念进行归档。
[*] 数据块管理:当前的调试数据被某些代码块(如 begin data block)困绕,这些数据必要在一个类似的数据块中进行归档。在实现时,必要确保每个数据块都能被准确地创建并归档到相应的层级中。
[*] 进一步优化和思考:只管基础版本的层级归档已经开始构建,但仍然必要进一步思考怎样提拔系统的表现,以便能够更好地处理调试数据并确保它们的层级布局能够满意后续的需求。团体来看,系统的优化不仅仅是简单的实现,还必要关注怎样更好地构造、管理和出现这些数据。
通过这些步骤,系统最终的目标是能够在复杂的调试场景中,清晰地展示数据的层级和归属,使得调试过程更加高效和有条理。
使用 GetGroupForHierarchicalName 在添加新事件时查抄是否有父组,如果有就毗连它们

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

[*] 找到父组:起首必要找到该元素应该添加到的父组。为此,通过传递该元素的“块名”来确定它的父组。块名将用于匹配并定位其父组,从而确定将元素添加到哪个组。
[*] 添加元素到父组:一旦找到了得当的父组,就将该元素添加到父组中。这个步骤的目标是将元素准确地嵌入到已经存在的层级布局里。
[*] 不必要存储链接:对于这一过程,似乎并不必要额外存储链接。直接将元素参加父组即可。
[*] 强制绘制:为了能够看到元素在层级布局中的位置,可能必要强制刷新绘制。通过绘制,可以实时查看我们创建的层级布局,确保元素被准确地添加。
[*] 调试和查看代码:在实现过程中,虽然有些代码可能忘记了具体位置,但没有问题。通过回顾和调试代码,渐渐调解和完善层级布局的功能,确保它按预期工作。
团体上,目标是让元素能够按照特定的逻辑层次,准确地嵌入到其父组中,同时保持可视化效果,以便实时查抄和调解。
https://i-blog.csdnimg.cn/direct/4a9746e507864ba8bc913bdfeffd6ab8.png#pic_center
启用绘制层次布局

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

[*] 简化树的创建:不再必要复杂的树布局。可以通过直接在代码中创建一个树,并将它始终指向根组,这样就能避免之前复杂的操作方式。这样做会让流程更加直观和简便。
[*] 避免复杂性:之前可能会在树布局中做过多的“黑客式”修改,但现在考虑到可以通过更简便的方法来实现,所以不再必要这样做。直接让树布局指向根组是一个更简便且高效的选择。
[*] 树与根组的关系:树的布局仍然存在,只是通过这种简化的方式,根组将作为树的基础,而不必要再进行复杂的操作。
这种方法的主要目标是通过更简便的方式来创建和管理树形布局,并淘汰不必要的复杂性。
https://i-blog.csdnimg.cn/direct/a238a7c2d27848e2b054ff9733f7c7f0.png#pic_center
“黑客式”修改(或“hack”)通常指的是通过不正式、不规范或暂时的手段来解决问题,通常是为了快速实现某个目标,但这种做法可能会导致代码不易维护、可读性差,甚至在久远来看可能引发更多问题。
具体来说,黑客式修改有以下特点:

[*] 暂时解决方案:通常是为了尽快解决某个问题,而不是从根本上解决它。这种做法往往是为了应急,而没有考虑到系统的久远稳定性。
[*] 不规范的代码:黑客式修改往往不遵循最佳编程实践,可能会跳过一些规范的步骤,如代码布局不清晰、注释不足、变量定名不规范等。
[*] 高风险:这种修改可能在短期内看似解决了问题,但它带来的隐患是久远的。它可能会影响后续的维护,导致系统变得更难扩展和调试。
[*] 解决问题的捷径:在处理复杂问题时,开发者可能会采用暂时的或者不完美的方法来应对,效果可能在最终的系统中留下不必要的复杂性。
总体而言,黑客式修改是为了“立刻解决眼前的问题”,但可能会导致久远的维护问题,因此在软件开发中通常不推荐这种做法,除非在非常告急的情况下。
调试崩溃,因为不知道怎样绘制某些调试元素

在这段内容中,开发者正在处理一个代码中的层级布局问题。当前系统中正在添加很多新的元素到这个层级中,但有些元素的绘制无法正常工作,因为系统并不知道如那里理这些新的元素类型。为了处理这个问题,开发者决定增强系统的能力,使其能够处理并表现全部这些新增的元素。
具体来说,开发者发现有些事件类型被标志为“debug value”,并且系统无法辨认这些事件。为了让系统能够准确处理这些事件,开发者决定修改代码中的 debug event to text 函数,使其能够打印出这些事件,并且增加对未知事件类型的处理逻辑。
开发者的思路是,只管当前的系统存在一些不可预见的问题,但通过添加更多的日志和处理机制,可以让系统能够更好地应对这些问题,并渐渐修复它们。
总结来说,这段内容描述了开发者在调试和完善一个层级布局系统时,碰到的一些不可预见的情况,并通过扩展系统的事件处理能力来解决这些问题。同时,开发者也反思了本身的工作进度,并表示时间已经有些紧迫。
https://i-blog.csdnimg.cn/direct/7e7c94c8bab74c6e9513f26eae89f4f2.png#pic_center
为未处理的调试事件添加默认名称以防崩溃

在这段内容中,开发者讨论了怎样公道地传递一个"块名称"(block name)。他们认为将"块名称"作为参数传递是完全公道的,并计划根据这一思路继续开发。在这种情况下,块名称将作为关键数据传递,并且他们将其用于后续的操作和逻辑处理。
总结来说,开发者认为传递块名称作为参数是一种合适的做法,并且计划按照这一思路继续进行相干工作。
运行游戏,现在不会崩溃,但调试变量没有列出

目前,开发者继续推进项目并对当前效果感到惊讶,因为许多预期中的步骤和操作竟然没有做任何额外的工作就顺利运行了。开发者对此感到有些意外,甚至以为这是一种风趣的情况。
https://i-blog.csdnimg.cn/direct/e8fa41bee8394a65a2fc513955c570aa.png#pic_center
“偶然候即使在绝望的深渊中,某些小事情也会对你有利,紧张的是不要让这种情况冲昏头脑。”

即使在深深的绝望中,偶然也会出现一些微小的好运。紧张的是要记着,不要让这些小小的成功冲昏头脑。事情依然如预期的那样糟糕,一个微不足道的胜利并不足以改变对人类痛楚不可避免性的悲观看法。因此,即使有些事情盼望顺利,也不能因此而改变本身对生活的团体看法。
开始查找为什么调试变量没有出现

在查抄调试变量时,发现没有看到预期中的调试变量输出,这让我感到有些困惑。我原本以为会看到一些调试变量的输出,但现在并没有。实际上,我看到了一些数据输出,但没有调试变量,这让我怀疑本身是否没有准确理解这些变量怎样在系统中活动。
在查看代码时,发现在debug variables部门并没有表现出这些变量。这可能是因为我们没有准确地打印它们,或者是某些步骤被跳过了。我想先弄清楚为什么调试变量没有表现出来。
在分析时,我发现调试变量的文本打印部门似乎只会输出某些特定的内容,而没有包括位图ID和线程列表计数器。理论上,任何调试记载应该在collate debug records的过程中被处理并表现出来。所以,我以为应该是系统没有准确地将这些调试变量参加到流中。
我再次查看了代码,特别是涉及调试记载部门,像是begin block和block的相干代码,似乎并没有问题。然而,我没有看到调试变量的打印输出,可能是因为某些环节没有处理到。
进一步调试时,我发现调试值虽然颠末了处理,但却没有块名称。这意味着在处理这些数据时,存在一些不一致或错误。
总之,现在系统在处理调试变量时出现了问题,可能是处理流程中的某个环节没有被准确触发,导致调试数据没有按预期表现出来。这必要进一步查抄和修正。
在 MarkDebugValue 上添加断点,以查看哪些事件通过了

现在我们发现系统确实有收到调试值事件(debug value),但问题在于这些事件的 block name 被设置成了 0,也就是说并没有实际赋值,这显然不是我们期望的行为。
我们查看了一下调试信息的调用栈,发现相干代码路径指向了 game debug interface 的第 293 行左右。这个位置似乎是在向调试系统传输数据的过程中,却没有准确地附带 block name。很显着,这是一个逻辑上的遗漏。
本质上,这意味着我们虽然生成了调试数据并传输到了系统中,但因为缺失了用于构造和归类的 block name,这些调试值无法被归类到准确的层级布局下,从而也不会在最终的调试界面中出现出来。
这个问题的根源可能是我们在写入调试数据时没有显式设置 block name,或者在构建调试记载时跳过了对 block name 的处理步骤。这种疏漏直接导致了调试值孤立地存在,没有上下文关联,最终也就无法被系统有用表现或使用。
这是一件令人失望的事情,因为它意味着我们必要回过头去,查抄和修复这部门逻辑,确保每个调试事件在生成时都准确绑定了所属的 block name,以便后续系统能辨认它们的归属关系,从而建立出准确的层级布局。这个问题虽小,但影响到了调试功能的完整性。
https://i-blog.csdnimg.cn/direct/7d03a08a7d424422a9514b0cc91f4390.png#pic_center
解释了这个 bug,并通过存储 GUID 在事件中进行了部门修复

目前出现的问题在于:由于我们在统一的位置调用了 debug_initialize_value,导致全部调试变量在系统中被记载时,其泉源位置都是同一个代码点。这直接引发了 block name 丢失或统一的问题,使得调试变量在后续分组与层级归类时缺乏区分依据。
这种情况意味着,我们目前的实现方式使得无论变量在哪个上下文中被初始化,它们在系统内部都被视为来自雷同的源头。这使得它们难以被归类到各自所属的逻辑层级中,从而破坏了原本想要构建的分层调试视图布局。
为了解决这个问题,我们必要确保每个调试变量能被绑定到唯一的、具备区分性的标识符(例如 GUID 或 block name)。当前的思路是,可以利用变量名本身作为 GUID 名称来生成唯一的分组信息。这样,即使初始化函数的位置雷同,只要传入的变量名不同,生成的 GUID 分组就能区分它们。
换句话说,可以考虑将调试值初始化宏或函数做进一步封装或修改,使其在生成调试记载时主动附带变量名,并以此变量名为关键构造唯一的调试分组,从而解决统一调用位置带来的归属杂乱问题。
虽然目前这还只是一个设想,但理论上是成立的,并且实现本钱相对较低。后续可以进一步验证此方式是否真正解决了调试变量无法准确分层归类的问题。如果有用,就可以继续扩展完善整个调试层级的逻辑布局。
https://i-blog.csdnimg.cn/direct/50921f7d85584b4798bb58133091bd08.png#pic_center
https://i-blog.csdnimg.cn/direct/e8ebbe6314af4e1c8459a1c4b4a608ff.png#pic_center
重新运行游戏,现在多个事件准确表现,但文本不准确

目前的问题虽然通过将变量名用作 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 表现为层级布局,实现真正清晰的上下文归属感;
[*]同时处理输出重复问题,确保每条调试信息只展示一次有用内容。
继续推进的方向是:**建立并渲染层级布局树,完善调试变量的构造与表现。**这样才华让整个调试系统具备可视性、可导航性和逻辑一致性。
https://i-blog.csdnimg.cn/direct/b59aac4f57c940b3ac47c0ac4d915ec1.png#pic_center
https://i-blog.csdnimg.cn/direct/6cc03efdf7ac4f9f92154464ebe1300f.png#pic_center
解释了怎样使用调试名称来构建层次布局

为了实现我们想要的调试变量层级布局,当前的目标是剖析变量的名称,并利用名称中的下划线 _ 来主动构建层级关系。这个计谋是我们之前就已经确定下来的。
实现层级的核心思路如下:



[*]调试变量名中已经内嵌告终构信息,例如:

[*]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");
目标是让这些名称在调试工具中主动构造成嵌套布局:



[*]剖析每个变量的名称;
[*]按照下划线逐级拆分;
[*]每一级作为一个分组,最终的叶节点是变量本身;
[*]将变量插入到准确的层级节点中,构建出一棵完整的调试树。
实现此逻辑的必要步骤:


[*]在处理变量时读取其完整名称;
[*]使用下划线 _ 将名称拆分为多个部门;
[*]逐层遍历(或递归)插入节点,每一层创建或复用已有的分组;
[*]最后将变量对象挂载到最底层对应节点下;
[*]最终这棵树就代表了变量的构造布局,可以被可视化地展开和欣赏。
目标效果:

通过这样的方式,每个变量不再只是平铺的调试项,而是依据其语义和定名方式,被归入公道的逻辑布局中,例如:
这种布局将极大提拔调试时对变量的理解和管理,尤其在变量数量很多时,能快速定位和构造信息。
总结来说,现在的任务是实现一个基于下划线定名剖析的主动层级布局构建器,从而让调试系统更智能、更清晰。
在 GetGroupForHeirarchicalName 中实现层次布局构建

我们要实现的是一个分层名称的分组逻辑。以往我们总是返回根分组,但现在我们必要根据名称来返回真正准确的分组。具体来说,我们会按照如下步骤进行处理:
起首,我们必要查抄传入的名称字符串,找到其中的第一个下划线(_)。这个下划线将帮助我们确定第一个分层的边界。
我们会从头开始扫描整个名称字符串,一旦碰到第一个下划线,就记载它在字符串中的位置,并制止扫描。这个位置实际上就将名称切分为两部门:前缀部门表示该对象应归属的第一层子分组,而剩下的部门将递归地被进一步处理以归入更深一层的子分组。
接下来,如果没有找到下划线,分析这个名称没有分层布局,此时我们就直接将该对象插入到它所属的父分组中,无需进一步处理。
如果找到了下划线,就分析存在分层布局,必要进一步处理。我们接着查抄在当前父分组下,是否已经存在一个以当前前缀定名的子分组。如果存在,就获取它;如果不存在,就新建一个并插入到父分组中。
然后,我们会递归地调用自身,将剩余部门(即从下划线后一个字符开始的子字符串)作为新的名称继续处理。每次递归都处理名称的一层,直到名称中不再包罗下划线为止。
通过这种方式,我们就能够根据名称中的下划线主动构建并构造一个嵌套的分组布局,实现名称与分组之间的层级映射逻辑。整个过程依靠于字符串的剖析、判断是否存在对应分组、创建新分组、以及递归调用自身来处理更深层的布局。
https://i-blog.csdnimg.cn/direct/19bce9b9097445da922ca02066d01d31.png#pic_center
添加 GetOrCreateGroupWithName 辅助函数

我们接下来的目标是完因素组的查找与创建操作。只要能够顺利完成这部门,团体逻辑就可以正常运行。
当前的输入名称已经不再像从前那样有备用名可以参考,而是只有一个确定长度的名称。这种方式其实更符合我们处理字符串的偏好——按位置索引处理而不是按逻辑规则匹配,虽然在现有系统中我们并不常进行复杂的字符串操作,因此影响不大。
我们的主要任务是在给定父分组的基础上,遍历其部属的分组,查找是否已经存在一个名字与我们目标名称雷同的子分组。这个操作并不必要太高的性能优化,因为这个函数只在调试信息首次进入系统、并且该名称还从未被注册过的时间才会被调用。换句话说,一旦层级关系构建完成,它就不会再被调用。因此它既不影响调试系统团体性能,更不属于系统中关键路径的一部门,这一点也很有利,我们可以不拘泥于性能细节,专注于逻辑准确性。
具体来说,我们会从当前父分组开始,遍历其子节点链表。这个链表从父分组的哨兵节点(sentinel)出发,通过 next 指针不停向后访问每一个子节点,直到再次碰到哨兵节点,表示遍历结束。
在这个过程中,我们查抄每个子节点的名称,如果找到一个名称与当前分组名称匹配的,就分析该分组已经存在,可以直接使用,操作完成。
如果在整个链表中没有找到匹配的分组,那我们就必要新建一个子分组。这就涉及到创建一个新的分层分组对象,并将其插入到父分组的布局中,同时也要更新相应的分组链表。
此外,还要调用一个添加函数,将新建的分组参加到当前父分组的子分组集合中,确保层级布局的完整性。
总之,这一段的核心逻辑是:遍历父分组的子分组列表,尝试查找匹配名称的分组;如果没有找到就新建一个,并插入到准确的位置。整个过程依靠链表遍历、名称比较、新建节点和层级布局维护,是构建调试分组体系中的关键环节。
https://i-blog.csdnimg.cn/direct/2682a2be7b3b40c4adb0eeb9aaaeb975.png#pic_center
将名称放入 debug_variable_group 布局体中,完成层次布局创建代码

我们早就预见到这个问题的出现,可以说这是一种“预言”,当变量组中没著名称时,整个分层布局就无法正常构建。没著名称就无法建立层次关系,即便有,也无法展示,导致只是一堆可以展开但毫无实际意义的节点,这好坏常无用的。
为了解决这个问题,我们决定从根本上支持在变量组中存储名称,并在创建变量组时就传入名称和其长度。这样不仅能确保每个分组有唯一标识,还能用于后续的字符串比较,从而判断分组是否匹配。
具体操作如下:

[*] 变量组定名支持:我们在创建变量组时传入 name 和 name length,并将它们生存在变量组对象中。这样每个变量组都有一个明确的名称信息,便于后续的辨认与层级构建。
[*] 字符串比较逻辑:添加一个字符串比较函数 StringsAreEqual,用于判断两个变量组是否拥有雷同的名称。这个函数通过比较两个字符串及其长度来进行判断,而不是依靠常规的 null 末了字符串方法,以增强性能和准确性。
[*] 改造创建函数:在 CreateVariableGroup 函数中,我们新增对 name 和 name length 的参数支持,使得每个新建的变量组都能在初始化时就绑定其名称。
[*] 遍历和匹配逻辑调解:在遍历子分组时,我们使用 StringsAreEqual 函数进行名称匹配,判断当前遍历到的子项是否就是我们所需的分组。如果匹配,就直接使用它;否则就新建一个新的分组并添加到父级中。
[*] 设置名称长度上限:基于实际需求,我们默认字符串不会超过 400 万个字符。如果真有超过这个长度的字符串,表现上也早已崩溃,不具备处理的意义,因此这一假设完全公道。
[*] 递归入口设置:第一次调用分层名称查找函数 GetGroupForHierarchicalName 时,我们默认从根组(root group)开始,这样整个递归布局就有了明确的起点。
总结来说,我们重新设计了变量组布局,使其支持名称存储、初始化时赋值、以及基于名称的匹配操作。整个分层布局的构建逻辑更严谨,匹配准确,展示也更直观。这些调解都是为了实现健壮的分组体系,确保调试数据以清晰的分层方式显现。
https://i-blog.csdnimg.cn/direct/ace00b2ec87f4e8caef8f5bf020dd5a9.png#pic_center
https://i-blog.csdnimg.cn/direct/9513316579c34dcaa6466829c0214f48.png#pic_center
https://i-blog.csdnimg.cn/direct/545f3172a2f8466095491df9a088b131.png#pic_center
添加一个 StringsAreEqual 函数,具有明确的长度参数

我们现在的目标是实现一个基于长度的字符串比较函数,而不是依靠以空字符末了的传统方式。这个函数可以用于比较任意长度的字符串,只要它们长度雷同,并且在每个字符上的值也一致,就认为是相等的。
以下是具体的实现思路和逻辑流程:

[*] 初始思考与目标设定:
我们盼望构建一个字符串比较函数,它通过传入的字符串内容和对应的长度来判断两个字符串是否相等,而不是依靠 null 末了。这样我们就能比较任意长度的内存区域内的字符串,甚至可以比较非 null 末了的数据块,增强通用性。
[*] 先判断长度是否一致:
起首查抄两个字符串的长度是否一致,这是判断是否可能相等的前提。如果长度不同,则不可能相等,直接返回 false,节省后续计算本钱。
[*] 进入字符逐位比较阶段:
如果长度雷同,分析有比较的必要性。我们定义一个索引变量 index,从 0 开始,循环直到长度上限。因为两个字符串长度雷同,所以只需用一个变量进行循环控制。
[*] 字符比对逻辑:
在循环过程中,我们逐位比较两个字符串的每个字符(a 与 b)。一旦发现某一位字符不一致,立即返回 false 并退出比较过程,分析两个字符串不相等。
[*] 全部字符都一致则返回 true:
如果整个循环过程中没有发生不一致的情况,分析两个字符串完全一致,于是最终返回 true。
[*] 可扩展性与稳定性设计:
虽然我们可以比较非常长的字符串(理论上内存允许范围内任意长度),但一般不会超过几百万字符,表现或使用上也会受到限制。出于稳定性考虑,我们默认字符串不会超过 400 万字符,超过这个值的情况可以视为异常或不公道使用。
这个函数的设计增强了我们对字符串的操作能力,特别得当用于调试信息分组中的名称匹配逻辑。相比传统 null 末了字符串比较,它更机动、安全,也更得当系统底层操作或复杂内存布局处理场景。我们将在层级分组构建中依靠它实现精准匹配,确保名称唯一性和准确性,从而维持分组布局的完整性和可读性。
https://i-blog.csdnimg.cn/direct/c56f110bfe7e446bbc33dbcd546e0fdd.png#pic_center
描述了为了使调试层次布局准确表现必要添加的内容

目前的状态下,虽然我们已经处理了分组和名称比较的逻辑,但存在一个问题,就是在调试时,我们没有实际的方式来表现分组的头部信息。也就是说,我们目前的代码中没有一个完整的框架来绘制或出现分组层级的可视化界面。
具体来说,当我们在调试时,虽然会进入分组处理逻辑,但我们并没有绘制或表现每个分组的“头部”信息。这意味着,只管我们能处理和遍历每个分组(如变量组的链接),但如果这些分组有子节点或层级布局,我们并没有实际的界面或方式去表现它们。
目前,代码中的一些部门确实已经处理了子分组的遍历和链接,比如在 linked children 这块有相应的逻辑判断,但这并不意味着我们在界面上能够展示这些分组或它们的层级布局。这种遍历可能只是单纯的遍历和处理,并没有联合实际的UI或界面进行可视化展示。
接下来,我们的目标是增加一个功能,当碰到具有子节点的分组时,能够以分层的方式进行处理和表现。例如,若某个分组有子节点,我们应该以层级的方式递归处理这些子节点,而不是仅仅按平面布局处理全部项。这样,调试界面才华准确展示每个分组的层次布局。
为此,可能必要引入一些逻辑判断,判断当前的分组是否有子节点,如果有,就按照层级次序来处理这些子节点,进行递归表现。这不仅涉及到对数据布局的处理,还包括怎样在调试界面上出现这些层次关系,让开发者能够更清晰地看到各个分组之间的父子关系。
总结来说,目前最大的挑衅是怎样在调试时准确地绘制分组层级头部,尤其是当分组具有子节点时,怎样以层级方式展示这些分组。通过增强分组遍历逻辑和改进表现框架,应该能够解决这个问题,让调试过程更加直观和易于理解。
https://i-blog.csdnimg.cn/direct/b8b9c0b3cdd54164ac12b54fc5e209b8.png#pic_center
开始分支代码以处理单个元素和层次布局

我们正在处理一个判断是否为“组”的逻辑:如果是组,就走一条处理路径;如果不是组,就走另一条路径。我们决定暂时沿用已有的处理流程,先让团体功能跑通,再进一步处理细节。
在处理调试文本输出时,我们决定不再使用 debug_event_to_text,而是直接通过 debug_text_out 来输出调试信息。我们留意到,目前的实现不支持传入带长度信息的字符串,这一点在调试输出时其实是有些遗憾的。如果能支持直接传入字符串长度,就可以通过索引来遍历并输出每个字符,这样服从更高也更机动。
虽然当前处理方案比较大抵,但考虑到我们还在处理中间状态的其他内容,暂时采用这种方式也可以接受。我们加了一个断言:判断 name_length 是否小于一个预期的最大文本长度,只管在目前的情况下几乎总是成立,但加上这个判断更稳妥一些。
接下来,我们将字符串内容复制到一个文本缓冲区中,复制的长度是 name_length,并且在末了多留一个字符用于空字符终结符。这样可以确保复制后形成一个尺度的字符串。
至于处理组的逻辑,我们回想中好像从前写过类似的代码,但一时没有想起来其时是怎么处理的。于是我们暂时搁置了这部门的深入处理,计划稍后再回顾旧代码来还原这一部门的逻辑。我们找到了深度追踪相干的代码位置,确认目前使用的是那部门逻辑。
我们还移除了一个不再使用的变量,同时发现有个叫 item_color 的东西暂时不知道它的作用,估计是某种暂时启用的内容。
最后,我们采用了新的方式实现当前需求,并推测我们之前肯定写过类似逻辑的代码,后面可以再回头整理。团体来说,当前目标是先把流程跑通,哪怕方式暂时比较粗糙。
https://i-blog.csdnimg.cn/direct/bd610725ee894ec6948f77f57c9cc5f2.png#pic_center
运行游戏,现在事件已按层次布局表现

我们之前可能曾删除过某些逻辑,不过这次在尝试时居然第一次运行就成功了,有点出人料想。只管整个项目主题是关于“深度抑郁”的模拟,这种一次成功反倒让人有些无所适从,但效果确实符合预期。
当前我们已经准确地建立了层级布局模拟的渲染流程。在渲染过程中,层级布局显现得非常清晰,能够表现出两级布局,正如我们预期的一样。比如摄像机的渲染部门也完整地表现出了层级自组装的逻辑,这分析当前模拟流程是准确的。
接下来的目标是进一步完善这个流程:参加可以展开和折叠分组的功能。实际上我们之前已经实现过这部门功能,只必要恢复旧代码即可完成这部门功能。时间方面,我们大概还有七分钟左右,如果顺利的话,这点时间可能足够我们完成这部门的恢复。
我们也意识到当前的代码还有一些可以整理和优化的地方,不过这一部门可以留到下周再处理。现在的重点是先恢复分组展开与折叠的逻辑。
我们发现当前的问题是:原先用于管理分组展开/折叠状态的存储逻辑曾一度被移除。不过好消息是,这部门内容仍然保留在 debug_view 中,并未彻底删除。所以我们必要做的只是重新启用这段代码,确保其在调试视图中生效即可。
进一步查抄代码后,我们发现与展开状态相干的处理逻辑依然存在,只是没有被激活。因此,我们距离完全恢复功能已经非常接近,只必要稍加整理就可以让它重新运行起来。团体盼望顺利,下一步就是启用并验证分组状态的处理逻辑。
https://i-blog.csdnimg.cn/direct/09f51d72da7e44f393f669c132b4485b.png#pic_center
为层次调试事件添加交互支持

我们现在的目标是:实现基于视图的迭代,并支持分组节点的展开与折叠交互。目前的迭代逻辑布局杂乱,特别是关于 stack 的处理方式显得过于复杂和艰涩,不敷直观,未来必要重构以提拔可读性和稳定性。
当前流程中,我们已经能够查抄节点是否是“始终展开”的状态。接下来我们要做的是获取对应的视图信息。只必要从视图系统中取出该节点的视图状态,就可以基于此处理后续逻辑。
一旦获得视图,我们还必要为该节点创建交互行为,使其支持展开与折叠的切换。我们回顾了之前的交互逻辑,发现已有一个叫 toggle_value 的泛用交互行为,它可能曾被用于实现类似的功能。虽然看起来是为其他目标设计的,但它提供了一种机制,可以基于某些事件触发状态改变,这与我们当前需求类似。
不过在当前代码中,我们找不到专门用于“分组”的交互逻辑,这可能意味着我们必要新建一个专用于组展开/折叠的交互操作。我们尝试从已有的 toggle_value 中了解具体的行为,它是怎样在事件发生时检测并处理状态变革的。
我们留意到其中提到了 open_data_block,这可能是用于打开数据块的操作,但这与我们现在想要的功能并不一致。我们盼望实现的是:在点击某个组名时,主动切换该组的展开/折叠状态,而不是展开数据块。
因此,我们的核心需求是:创建一种新的交互事件,能够基于节点信息进行判断,然后更新该节点的展开状态。为此,必要实现一个逻辑更明确的交互绑定流程,确保其能够被当前 UI 框架检测并相应。
总的来说,目前我们:

[*]已经能获取视图并确认展开状态;
[*]正在准备创建一种新交互方式以控制展开/折叠;
[*]考虑从 toggle_value 中复用逻辑;
[*]发现之前的交互行为过于通用,不符合现在的需求;
[*]必要开发更清晰、专用于组操作的交互机制;
[*]后续还需简化当前堆栈式迭代布局,让团体逻辑更易读更易维护。
团体功能已接近完成,剩下的工作会合在交互细节的补全和整理。
https://i-blog.csdnimg.cn/direct/b81ed35d4d0840c7937baeb48c541786.png#pic_center
https://i-blog.csdnimg.cn/direct/7dc80f0fb32b487b9e5ae1e205db2404.png#pic_center
重写怎样展开组

我们现在明确了真正必要的交互类型:debug interaction 中实现“展开或折叠”的功能,也就是“toggle expansion”。原本的一些复杂处理逻辑,例如使用 VarLinkInteraction 之类的方式,其实并不得当当前的需求。我们决定舍弃原先那些不必要的部门,转而采用更直接的方式来实现这个交互。
具体来说,这个交互其实只必要简单的操作逻辑,我们可以直接调用展开/折叠的切换函数,不必要通过冗长的中间流程。因此,不再使用所谓的“VarLinkInteraction”,而是改为普通的 DebugInteraction_ToggleExpansion。
我们已经有了构造视图中 link 的方式,只要基于这个 link 创建一个交互实体即可。交互对象还必要一个名字,便于在调试或界面中辨认。接下来,我们只需将“切换展开状态”的逻辑函数传入这个交互对象中,就能完成我们想要的功能。
总结当前思路:

[*]明确我们真正必要的交互是“切换展开状态”;
[*]扬弃复杂的中间逻辑,采用更直接的调用方式;
[*]使用 DebugInteraction_ToggleExpansion 构建交互行为;
[*]基于树布局的 link 创建对应交互;
[*]为该交互指定名称;
[*]传入用于切换展开状态的处理函数;
[*]最终实现点击节点时即可展开或折叠分组视图。
通过这套逻辑,我们能够用更清晰、更高效的方式恢复并简化原有的分组交互功能,使得团体行为更容易维护和扩展。
https://i-blog.csdnimg.cn/direct/60f8d8ff14734cf4bc44755320ee80ac.png#pic_center
https://i-blog.csdnimg.cn/direct/a262aa3699a84465bfbfbe1ea4b3e7c5.png#pic_center
https://i-blog.csdnimg.cn/direct/348a0997e95540578c958ad4edac08d6.png#pic_center
游戏运行,交互现在正常工作,但高亮表现不准确

目前我们已经让功能正常运行了,这是一个积极的盼望。不过现在出现了一个希奇的问题:某些元素始终被判断为“hot”(即处于交互激活状态),这显然是不符合预期的行为。
具体来说,我们留意到 item_color、native_interaction、tree_link 等相干元素都处于高亮状态,而这些本应是与具体节点相干的交互项。按理说,每个树节点的 link 都是不同的,每一次都应该是独立创建的对象。我们查抄了用于生成交互的 DebugIDFromLink,看起来是准确的,link 也确实应该是每个节点不同的实例。
理论上来说,这种高亮状态应该是和具体的 tree_link 绑定的,意味着只有鼠标悬停在特定节点上时,它才会触发 hot 状态。但实际表现却是全部节点都被判断为 hot。这种现象分析交互判断的条件出现了某种杂乱,可能是由于判断 id 时误用了某个共享对象或者引用。
我们还观察到,debug_id 的构造可能也存在问题。理论上,如果我们用雷同的方式构建 debug_id,而这些 id 本应具有唯一性,却被统一处理了,也有可能导致全体元素都触发同一个交互状态。大概我们应该明确区分每个 tree_link 的 id 实例,确保它们在交互系统中是唯一且独立的。
当前结论如下:

[*]交互系统中全部节点都被错误地判断为 hot;
[*]原因可能在于用于标识的 debug_id 或 link 被错误地复用或共享;
[*]每个树节点应该有独立的 link 和 id,目前可能存在统一引用的问题;
[*]高亮状态的判断应基于鼠标实际悬停的节点,但系统可能在内部将它们视为雷同对象;
[*]必要进一步排查 DebugIDFromLink 的实现细节,确认是否真正实现了唯一性;
[*]可能必要调解 debug_id 的生成逻辑,确保其与 tree_link 唯一绑定。
虽然现在运行功能根本正常,但这种全局“hot”状态显然是不公道的。下一步我们将会合精力排查这一交互判断机制的问题,以确保每个节点行为独立,避免交互逻辑杂乱。
我们发现当前的交互逻辑存在一个核心问题:判断交互是否雷同时,并没有对 debug_id 进行查抄。这意味着多个交互只要其它条件相似(比如布局或类型雷同),就可能被错误地视为同一个交互对象,进而导致全部节点都被错误地标志为“hot”状态。
这显然是不公道的。我们原本设计时,每个交互对象都应该具备唯一的 debug_id,而判断两个交互是否相等时,理应比较它们的 id。但现在这部门逻辑被遗漏了,很可能是在代码的重构或演进过程中不小心漏掉的。
因此我们明确认为,这种行为并不是有意为之,而是疏忽导致的逻辑缺失。更准确地说,判断交互是否相等时,应该包罗:


[*]比较交互类型;
[*]比较其它关键信息(例如 link 或行为);
[*]最紧张的是比较 debug_id 是否雷同。
如果两个交互的 id 不一样,就应该被视为完全不同的交互实例。当前缺失这一步,才导致全部节点使用雷同的交互实例,从而在视觉和逻辑上都出现了显着错误。
我们应该立刻修正这一点,明确在交互相等判断逻辑中参加对 debug_id 的比较:
if (a.debug_id == b.debug_id) {
    // 是同一个交互
}
这一修正将带来以下利益:

[*]每个节点根据唯一 ID 区分交互行为;
[*]“hot” 状态只在对应节点准确触发;
[*]避免全局交互误判;
[*]还原之前设计中各节点交互独立、状态分明的意图;
[*]为后续调试与维护打下更清晰的基础。
总结来说,这是一个由于重构过程中疏忽导致的紧张 bug,我们已经辨认出根因,并清楚怎样修复它。下一步只需在交互判断中补上对 debug_id 的查抄,即可恢复系统的准确行为逻辑。
https://i-blog.csdnimg.cn/direct/b8fc55d08cd44e8b951eb5c8b0cca13c.png#pic_center
一切正常运行

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

我们讨论了关于在多个地方传递字符串长度和数据指针的问题,是否应该封装成一个包罗长度和数据指针的字符串布局体。
我们的结论是:目前来看,这种方式(分开传递)并没有带来显着的麻烦或负担,所以并不以为有必要立刻引入布局体封装。虽然在某些情况下这样做是有原理的,特别是当管理字符串变得复杂、操作频繁时,封装会带来一定的整洁性与安全性,但在当前的上下文中,并没有感觉到剧烈的需求。
我们偶然确实会使用这种布局体情势来构造字符串,但这次的使用场景还没复杂到必须这么做的田地。因此,我们不反对这种做法,但当前并不急于去改。
总结如下:

[*]目前通过传递字符串长度和指针的方式仍然可控;
[*]尚未造成实际的困扰或出错;
[*]可以在未来视复杂度决定是否封装成布局体;
[*]不是一种必须立刻优化的点;
[*]保持当前实现简便也有一定利益;
[*]封装布局体的做法本身是中立的,但没有绝对必要性时不优先考虑。
最近你谈了很多关于编程语言的事,你有看过 Swift 吗?有什么想法?

我们提到最近经常在谈论“层”(layers)这个概念,然后被问到是否看过 Swift,以及对它的看法。
我们回应说完全没有对 Swift 感爱好,也没有花时间去了解它。印象中 Swift 是一种解释型语言,而如果确实如此,那我们就更加不愿意去接触它了。
这种态度背后反映出以下几个核心观点:

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


[*]并未关注或学习 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 不同,但如果编译器主动比较 i 和 f,它并不会知道应该比较哪一个,甚至可能比较出错(比如读取未定义的 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),但他其实更偏数学领域,不算严酷意义上的计算机领域人物。
总结如下:

[*]对香农作为选择给予肯定,认为是个好人物;
[*]提出了本身偏好的历史人物——高斯;
[*]指出高斯虽然并不属于计算机领域,但其在数学上的贡献依然令人钦佩;
[*]透露出对基础理论和数学之美的欣赏;
[*]回答风格随性真实,没有刻意迎合,而是自然表达个人喜好和思考。
问题:你认为英特尔达到了处理器尺寸极限,会导致视频游戏发展停滞吗,照旧他们能继续生存?

我们认为,就算未来硬件发展接近冻结,触及所谓“SAS(半导体面积缩放)”的极限,电子游戏行业依然可以很好地生存下去。
起首,当前电子游戏产业的发展,已经不再严峻依靠硬件性能的持续提拔。我们观察到,现在很多顶级的游戏系列,其技术表现其实并不特别突出。例如最近的《使命召唤》系列作品,画面技术并不先辈,甚至有些显得过期,但这并没有影响它的销量或受欢迎程度。
这分析整个游戏市场对“最新最强硬件”的依靠正在减弱。玩家更关注内容、玩法、外交互动、叙事和品牌影响力,而不是纯粹的图像质量或硬件压榨能力。因此,即便硬件性能不再快速提拔,只要游戏内容有吸引力,市场就仍然会维持活力。
总之,哪怕未来硬件进步趋缓,游戏行业仍有广阔的发展空间,不仅不会崩溃,反而可能更加专注于创造性和可持续的内容生态,而不是太过依靠技术突破来推动迭代。
你怎么看待测试驱动开发?

关于测试驱动开发(TDD),已经回答过很多次了,因此这次就不再具体讨论了。可以说,TDD 的理念和实践已经被广泛讨论过,很多开发者都有各自的看法和经验,因而这个问题已经不再是新的话题。对于它的看法,我之前已经在多个场合中表达过,因此这次就跳过不再重复回答。
你更喜好在 C 代码中做 UI,照旧使用像 HTML 这样的东西?

我们被问到如果要编写代码是更喜好使用 C 语言照旧 HTML 等其他语言。我们回答说,本身始终更偏幸使用 C 语言,或者更具体来说,更喜好编写与 C 语言相干的步伐。
虽然提到更倾向于 C 语言,但依然没有完全排挤其他语言,暗示在某些情况下,使用其他工具或语言也是可以接受的。
总结如下:

[*]更偏向于使用 C 语言编程;
[*]强调对 C 语言的喜好,尤其是在编写步伐时;
[*]仍然保持开放的态度,暗示在特定情境下也能接受其他语言;
[*]显现出对底层编程的偏好以及对 C 语言的理解和认识。
偶然我会拿本身和其他巨大的步伐员做比较,效果感到沮丧,因为我离他们的程度还远——我知道不应该拿本身和别人比较,因为他们发展的情况不同,而且我知道本身只要积极学习,最终能赶上他们,但偶然候就是会以为难过。你有过这样的感觉吗?有什么克服这种感情的建议吗?

在面对编程能力方面的比较时,起首要认识到,每个人的发展情况和天赋不同,所以不应该对比本身和别人。我们每个人的基因和背景不同,天生的编程天赋也不尽雷同,而这些是无法改变的。然而,积极工作和投入时间是可以掌控的,最终决定一个人成就的往往不是天赋,而是付出的积极。
起首,紧张的是不要让这些比较影响本身的感情。每个人的发展路径不同,所以不必对照别人来权衡本身。你必要关注的是本身能否在编程的道路上不停进步,而不是本身是不是天生就是最聪明的步伐员。无论你天生有多大的天赋,关键在于你能否通过不停积极和天天的编程积累,最大化本身的潜力。
别的,编程没有所谓的“编程奥林匹克”,每个人在编程世界中的地位并不紧张。更紧张的是你能做到什么,以及你是否享受这个过程。没有必要因为和他人进行比较而感到沮丧,因为编程的意义更多在于实现本身的目标,解决问题,并从中找到乐趣和成就感。
此外,也没有必要担心本身不敷聪明。只要能够理解编程的根本概念,提出问题并探求解决方案,那么成功的可能性好坏常大的。大多数成功的步伐员并不是天生就具备过人的智力,而是通过不停的实践和积极,才达到了本日的程度。所以,不要担心本身不敷聪明,紧张的是天天写代码,碰到问题就解决问题,积累经验。
最后,编程是一个长期积累的过程,就像学习一门外语一样,不可能在短时间内成为大师。很多人刚开始编程时都碰到过困难,而真正成为优秀的步伐员必要很多年的时间和实践。如果你才编程几年,无法成为顶尖步伐员是很正常的,因为编程不仅仅是技术问题,更是对问题的理解、逻辑头脑的训练和持续的学习。
总之,保持耐心,接受本身的进步过程,并且不停实践和学习,不要急于求成。每个人都有本身的节奏,只要坚韧不拔,最终都会实现本身的目标。
你认为日本没有采用面向对象编程范式是因为他们更聪明,照旧只是因为他们对更换新语言没有爱好?

我们被问到,是否认为日本没有采用面向对象(OO)编程范式是因为他们更聪明,照旧因为对新语言不感爱好。
我们回应表示对这个问题并不知情,甚至不知道日本没有采用面向对象编程范式。我们承认本身对这一点没有任何了解,表示即使知道相干信息,也不认为本身能对此发表什么有见地的看法。
总结如下:

[*]对日本是否采用面向对象编程范式的问题表示不知情;
[*]明确表示本身对这个话题不了解,也未曾听说过类似的情况;
[*]即使了解相干信息,也不认为本身能对此做出有意义的批评;
[*]表现出对该问题的无知与开放态度。
伪代码问题:怎么学习汇编语言?编译器/链接器是用它写的吗?

学习汇编语言的方式有几种方法。起首,必要了解的是,汇编语言在本日的步伐开发中并不是像过去那样常见。过去,尤其是在像凯文64(Commodore 64)或者苹果二(Apple II)这样的早期计算机上,编写整个步伐的代码使用汇编语言好坏常普遍的,而且也非常常见。然而,现现在,如果有人说他用汇编语言编写了整个游戏,别人可能会以为非常希奇,因为现在几乎没有人会用汇编语言编写整个步伐。
现代使用汇编语言通常是对步伐中的一些小部门进行优化,尤其是那些性能要求极高的部门。至于编译器和链接器,它们几乎从未用汇编语言编写。通常,开发者会使用现有的编译器或操作系统来编写新的操作系统或编译器,甚至使用交织编译技术来编写一个可以自我编译的编译器。
如果想学习汇编语言,可以使用调试器来辅助学习。最好的学习方式是通过调试器查看它生成的汇编代码。通过这种方式,可以在编写某段代码后,看到调试器为该代码生成的汇编语言代码,这样可以直观地了解汇编代码是怎样生成的。更进一步,任何用C语言编写的代码,都可以通过调试器直接查看它所对应的汇编代码,这对学习汇编语言非常有帮助。
此外,阅读在线教程也能帮助入门,尤其是那些具体解释怎样从高层语言(如C语言)生成汇编代码的教程。通过这种方式,学习者可以更快速地掌握汇编语言,尤其是通过直接与调试器交互,快速查看和理解生成的汇编代码。
总的来说,学习汇编语言的关键是通过实践和不停查看编译器生成的代码来进步理解,而不是死记硬背汇编指令。
你是怎样获取像 Clang 或 Linux 内核这样的大型代码库的概览的?

对于如那里理像Clang或Linux内核这样的大型代码库的概述,答案是:对于这种任务,我并不是最合适的人选。有人可能会问类似问题,但我并不善于这种类型的代码,像这种问题更得当向某些特定的人请教。比如有一个人非常善于这类工作,他能够应对这种复杂的代码库。
如果有人告诉我必要修复Clang中的一个bug,我会以为本身不想去了解那部门内容,也不愿意去看这些代码。对于我来说,这样的任务就像是避而不见,我甚至不想去接触它。
团体来说,面对一个庞大的项目或代码库时,我不太愿意深入去理解它,因为这种工作确实必要特定的技能和方法,而不是每个人都能快速适应和掌握的。我更愿意把这类任务交给那些更有经验的人。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 游戏引擎学习第219天