回顾并为今天的工作做准备
今天我们,进行一些编码工作。这部分的编码内容对那些对代码架构感兴趣的人非常有帮助,我认为今天的编码内容会很有教育意义,尤其是在展示一些代码转化的过程中,希望大家能够从中获得一些启发。
接下来,我们回顾一下上次的工作进展。我非常高兴我们上次结束时的状态,因为我们已经让调试系统支持同时进行性能分析和数据记录,而且这些信息会在之后汇总处理。这意味着,如果不启用该功能,相关数据就不会存在,完全不会占用资源,你也可以选择将其编译掉。而且,我们还可以查看过去32帧的调试数据,而不仅仅是实时显示在屏幕上。
今天,我们将继续推进项目。我希望能将现在的代码整理得更加稳定和结构化。之前的工作是为了让调试系统可以正常运行,整个过程比较探索性,因为我从未写过像这样的调试系统。在过程中,我一直在思考如何构建代码,才能让这些功能顺利工作。现在,看到代码已经正常工作,我想开始整理现有的代码,并使其更加结构清晰、稳定,这样我能对它的行为和扩展性更加有信心。
对于那些刚入门编程、在架构设计上感到困惑的朋友们,今天的直播将主要讨论如何做出架构决策,如何规划和设计结构良好的代码。
game_platform.h 和 game_debug.h:指出可以压缩掉的重复代码
首先,今天要讨论的一个概念是“压缩导向编程”,这个概念可能是我自己创造的,并不确定别人是否也这么称呼。简单来说,它的意思是让代码在初期能够灵活、快速地进行探索和实验。然后,当我们逐渐弄清楚代码结构应该如何组织时,我们开始寻找那些重复出现的模式,并将其压缩为可以多次使用的代码块。这就像是从一堆重复的代码中找出共通的部分,将其提取并简化,直到只剩下最简洁、最有效的结构。最终,所有的功能都可以集中在一个地方实现,减少重复和冗余。
我们在构建调试系统时,花费了大量的时间和精力,写了很多代码。实际上,这个过程并没有完全从整体上进行设计,而更多的是一种实验性的过程,我们在逐步摸索如何实现我们想要的功能。在这个过程中,虽然有很多不同的代码段,但其中有很多部分是相似的,执行的操作也比较类似。
例如,在我们的游戏代码中,调试系统中有很多地方看起来非常相似,特别是在处理调试服务的代码时。比如,我们有一个“调试变量类型”的结构,这个结构包含了许多类似的成员,比如可能是整数、浮动数据或者字符串等。我们还使用了类似的联合体来存储不同类型的数据,这些结构中有很多相同的元素。回到另一部分的代码中,我们可以看到类似的结构和变量定义。事实上,整个系统中的很多部分是重复的,虽然某些部分有些不同,但大体上我们在两边都写了类似的内容。
这种重复的代码其实是有原因的,因为它们是连接调试系统各个部分的桥梁。虽然在设计时我们并没有预料到会出现这样的重复,但随着代码逐渐演化,它成了一个必要的结构。为了将数据传输到调试系统,所有的数据都会通过这种结构进行传递,不论是性能计数器还是希望查看的某个数据字段,都会通过这个流传送,然后被构建成调试系统所需的层次结构,进行显示或者存储。
需要注意的是,每当我们想要为调试系统添加新的数据类型时,就需要在两个地方同时进行修改。虽然在当前阶段,这种重复的代码可能不会造成太大的问题,但从长远来看,每当我们要增加新类型,都会涉及到两个地方的修改,这无疑增加了很多不必要的工作。这样的重复工作虽然看起来不算太复杂,但它带来的成本远高于实际的收益。
因此,这种重复的代码结构虽然暂时有效,但长时间来看并不是最优的做法。我们需要寻找一种更高效的方法来简化这种重复,避免在以后扩展和维护时出现太多的麻烦。
game_debug.h:让 debug_variable 包含 debug_event 事件
首先,我们打算进行一个简单的代码简化步骤,目的是让两个系统使用相同的代码,而不再需要通过“传递”和“转换”来将一种数据结构转换为另一种。这种通过 switch
语句来转换数据类型的做法是多余的工作,我们希望完全消除这种做法。
我们的目标是将“调试变量”和“调试事件”合并,使得调试变量直接包含调试事件,而不需要额外的转换和重复代码。具体来说,我们计划去掉目前存储数据的方式,直接让调试变量使用调试事件的存储方式。
但这样做有一个问题,就是一些基本类型和工具,如 rectangle2
,并没有在平台代码中定义,而是仅存在于游戏的实际代码中。因此,当前不确定是否要将这些工具移动到平台代码中。不过,我们倾向于将它们移动过去,因为这样做会让访问这些工具的方式更加一致和清晰,更好地反映实际的操作逻辑。通过这种方式,我们不仅能减少冗余代码,还能提高代码的可维护性和可读性。
game_platform.h:将数据从 debug_variable 移到 debug_event
首先,打算进行的步骤是使代码能够有效地运行。为此,将首先把数据块的起始位置从当前的位置移动到新的地方,并且在初步阶段,使其成为镜像结构。这样做的目的是确保两者的数据结构能够一致,并且能够顺利过渡到目标结构。
game_platform.h:将向量和矩形作为基本类型进行访问
接下来的步骤是解决任何可能出现的问题,确保代码能够顺利运行。首先,决定将一些常用的基本类型(如 v
类型、v2
和其他相关内容)视为基础类型,并将它们放入合适的位置。这些基础类型因为在多个地方都用到,所以被认为足够重要,可以放到平台层,让它能够使用,并在接口边界等地方调用。这是一种合理的做法,能够提高代码的可用性和清晰度。
接着,将 v2
和 v3
类型添加进来,并且对矩形类型(rectangle)做同样的处理。矩形类型现在也被放入了数学库中,便于后续在代码中引用。这些改动能够使代码结构更为紧凑,且增强了代码的灵活性。
最终,这些修改使得 debug event
结构中也可以直接包含矩形数据,从而简化了代码的管理和扩展。随着这些更改,可能会出现一些连锁反应,因此还需要处理这些潜在的后果。同时,由于已将 intersection
包含进来,还需要更新预处理器,以确保代码的兼容性和一致性。
game_platform.h:将 DEBUGValueSetEventData 函数移植为使用我们的新基本类型
为了适应新的类型,需要做一些调整,特别是将涉及调试数据的部分(如调试值的存储和处理)迁移到新的结构中。这意味着我们需要修改之前的代码,确保新类型能够正确处理,并且一切能够按照新的方式运行。
首先,需要检查之前使用 std::queue
等数据结构的地方,确保它们现在能够直接使用新的数据类型。这意味着在处理调试信息时,需要将这些新的类型直接分配到合适的位置。这一步需要谨慎操作,确保所有的数据流都能顺利迁移到新系统中。
此外,还需要回顾并确认代码中的一些变量和数据结构是否需要调整,以确保它们与新的类型兼容。对于一些常见的数据类型或结构体,需要特别注意它们的命名和使用方式,确保一致性和准确性。
总之,目的是确保所有相关的地方都能够正确处理新的数据类型,而不影响系统的整体结构和功能。这些调整对于保持代码的清晰和有效性至关重要。
在这一步,我们对代码进行了进一步的简化和优化,特别是关于不同数据类型的处理。之前,某些数据类型(如 vector2
、vector3
和 rectangle
类型)可能被定义在不同的位置,现在我们将它们重新组织,使得这些类型可以更直接地进行赋值和使用。
具体来说,vector2
和 vector3
等类型现在可以直接赋值,而不需要额外的步骤,这让代码变得更加简洁和高效。同样,rectangle2
和 rectangle3
类型也进行了相似的处理,从而减少了潜在的错误和不一致性。
此外,随着这些类型被定义并正确放置,现在有些不再需要存在的代码可以被移除。这意味着,之前在数学库中定义的一些内容,现在可以直接移动到新的位置,避免了不必要的重复和冗余。这一改动使得代码结构更加清晰,也减少了维护的复杂度。
总的来说,这些更改使得代码更加集中和简洁,提高了代码的可读性,并减少了潜在的错误。
编译并修复编译错误
在这一过程中,首先进行了编译并发现了一些问题,例如 next pointer
的处理。这个指针在之前的代码中用于管理某些数据块的打开操作,但目前没有完全定义。由于目前没有特别的需求或者原因要求立即修改这一部分代码,因此决定暂时保留它,未来再根据需要处理。
接着,针对 seconds elapsed
这个值的存储问题,决定将其直接作为一个 arithmetic
类型存储,这样可以简化数据的处理和存储方式。
此外,之前代码中存在的 debug event
的转换也被简化,目标是去除所有不必要的 switch
语句。虽然这一步骤的操作是逐步进行的,为了让新接触架构设计的人能够更清晰地理解每一步,实际操作会较为缓慢。平时可能会一步到位,但为了让观众更好地理解,所以在每一步中都进行了详细的展示。
在代码结构上,introspection
(内部检查)部分的代码被移动到了更合适的位置,即 introspection
相关的代码应该位于相应的位置,这样可以让代码结构更加合理,减少混乱。
最后,所有的修改基本完成,唯一需要注意的部分是 seconds elapsed
的存储方式,之前的存储方式已经不再适用,因此需要按照新的结构进行调整,去除一些特殊用途的代码。
运行游戏,查看我们是否仍然传递了正确的值
现在代码已经恢复到之前的状态,可以看到我们传递的值和之前一样,这正是我们希望达到的结果。接下来,准备删除一些不再需要的代码。
趣事:你做的最有生产力的工作之一就是使用删除键
曾经有一个人,我在微软工作时短暂与他共事,他曾经说过一句话,我认为非常有道理。他说:“你做过的一些最有成效的工作,就是用删除键删除的那些。”这句话很有道理,因为在编程中,删减冗余的代码和简化结构,常常能带来更高效、更清晰的工作成果。
game_debug.h:考虑删除一些代码
我想做的事情是开始删除一些代码。现在我在调试变量中,我希望能够直接使用调试事件结构作为我的类型之一,而不需要在调试变量类型中再添加额外的类型信息。调试变量类型应该仅仅表示它是一个事件,这样就足够了。因此,我不需要再保留多种不同类型的变量,也不需要为每种类型编写不同的解析函数。相反,如果我想添加一个变量,我可以选择将其作为一个简单的事件来处理。
game_debug_variables.h:尽可能使用 #if 0 注释掉这些代码
现在,我想删除一些代码,包括那些开关和相关的部分。我觉得最终可能会以稍微不同的方式来处理这些功能。实际上,由于现在不在处理这个系统的这一部分,我打算暂时完全移除这些代码。我打算让调试变量的相关代码暂停一下,看看是否可以暂时移除大部分代码。虽然我不确定是否能完全去除这些内容,但我会尝试看看能否做到。
让这些代码不再执行
我们决定不再处理这些内容,这部分将不会发生,因此直接去掉它们,完全不再考虑这些部分。接下来,只专注于其他需要处理的部分。
game_debug.cpp:当我们看到 DebugVariableType_Event 时,查看一个二级值
当我们处理一个调试变量事件时,决定将之前打印出来的所有额外项作为一个独立的部分来处理。现在,这些内容将归入调试变量类型事件部分。当遇到一个事件时,我们将通过查看一个次级值来确定该事件的处理方式。这将成为一个共享的系统,不再依赖于其他内容。虽然将来可能会进一步精简,但目前我只专注于这部分的调整。
game_debug.cpp:使用来自 game_platform.h 的 debug_event_types
现在,这意味着之前使用的所有类型都已经不再需要,我们可以直接使用调试系统中已有的类型。这样,类型不再重复,所有类型将共享,这样当我们在一个地方做出更改时,其他地方也会自动更新。只需要在其他系统中添加最小量的代码来支持这些变化,不再需要额外的工作量,因为不喜欢做重复劳动。
在调试事件系统中,尽管暂时并没有在这里使用,我会临时添加它,虽然现在还不使用,预计未来会用到。接下来,添加调试事件的类型,比如调试事件2、调试事件3,以及调试事件4。
此外,还需要处理其他类型,比如矩形2。矩形2将按照特定格式进行处理,包含它的最大和最小X、Y坐标。而矩形3将与矩形2的处理方式相同,只是多一个元素。这样,所有相关类型都得到了处理。
这些类型最终将从不同的位置被取出,并且可以在事件处理中看到,所有这些处理的过程都在持续进行。
game_platform.h:TODO(casey):让元解析器忽略它自己的 #define
目前,系统只支持对结构体进行 introspection(反射)。这其实是一个我们没有做过的事情,虽然它并不影响我们当前的工作,但这个问题还是挺有趣的。我们并没有编写任何代码来忽略那些在进行 introspection 时,如果尝试处理文件时出现的情况。这可能需要做一些改动,譬如需要让预处理器忽略 #define
之类的宏定义。
game_debug.cpp:让 DEBUGBeginInteract 和 DEBUGEndInteract 使用其他信息
在这一阶段,对于DEBUG_MAX_VARIABLE_STACK_DEPTH
的内容,虽然不确定它是否仍然必要,但它至少对于遍历操作是有用的。所以,debug variables
部分会继续保留在外面,这样处理是可以接受的。
接着,在处理变量和交互类型时,系统需要使用其他的信息。这意味着我们需要采取双层次的方式来判断当前的操作是什么,尤其是当我们想处理像debug event
这样的事件时。这允许系统在存储上使用不同的结构,同时保持其他部分不变。基本上,只是让它间接地利用另一套系统来进行存储,这样做可以提高系统的灵活性。
为了确保代码的正常工作,debug event
类型和arthritic
类型的结构已经设置好了。只要将它们传递到合适的地方,我们就能进行对比和处理。这部分的逻辑和之前的一样,并没有太多变化。
至于一些变量的处理,虽然当前尚未完成,但随着系统的逐步清理,相关的代码会被重新整理和优化。
在代码的实现上,debug event interact
部分使用了双层次的逻辑,确保当处理到事件时,系统能够根据需求做出相应的处理。这些变化都是为了保持代码的一致性和结构的简洁。
整体来说,当前的进展是进行最小的代码调整,以便系统能够逐步清晰并顺利运行。
game_debug.cpp:将任何 DebugEvent 捕获为数据块
在这种情况下,之前负责执行翻译的代码现在不再需要做任何处理了。理论上,所有这些代码现在只需要捕获传入的debug event
数据块即可。只要检测到任何debug event
,我们就不需要做额外的处理,只需捕获它。
实际上,考虑到现在不需要编写不同的代码来处理事件,任何我们不理解的事件可以直接保留下来,假设未来某个地方会有人尝试打印它。如果无法打印,就会被标记为“不理解”的事件。
因此,现在的任务就是简化代码,直接将所有事件传递出来,而不需要做额外的翻译处理。只要捕获数据块并简单地传递它们,就可以了。
运行游戏并发现树从未初始化
所有之前的代码和那些不必要的部分都已经去掉了,这样就简化了很多。不过,在这过程中,可能有一些问题出现,比如调试树(debug tree)没有被正确初始化,导致一些问题。
由于我们去除了所有调试树相关的内容,现在的系统中不再存在调试树,因此要确保这一变动不会引发新的问题。尽管这种变动并不一定能直接完成,但为了确保目前的修改是允许的,需要检查是否能顺利通过,确保一切正常运行。
game_debug.cpp:检查 Group 是否存在
在这种情况下,组(group)不能为零,因此需要确保它是被允许的。为了确保这一点,我们会进行检查,如果组存在,就继续执行。接下来,我们会进行推送操作。同时,为了避免调试时中断,不希望设置断点。
运行游戏并查看一切正常
现在,所有的调试数据都通过一种结构体来封装,这样的合并非常好,我对这个结果感到非常满意。通过这种方式,整个过程变得更加简洁清晰,效果也很好。
game_platform.h:回顾更改
现在,在游戏调试系统中,所有的数据都以标准的方式存储在调试系统内部。这意味着,新的值或变量会被添加到这个系统中,并由它自己存储和管理。这样的做法让我感觉更清晰,也更符合直觉,这是一件很好的事情。
game_platform.h:引入 DebugEvent_FirstUIType 来包含 DebugVariableType_ 数据
现在的问题是,是否可以将当前的两层结构简化。当前的结构中,我们有调试变量类型,并且在其中有不同的条目和分组。一个显而易见的问题是,既然这些只是调试数据,而且没有强烈的理由需要维持两层结构,那么我们是否可以进一步简化,直接将这两个枚举类型合并在一起。
因为记住,这些类型仅占用8位,因此完全可以将所有这些条目直接嵌入到一个类型中,这样它们就可以一起工作。具体来说,可以将前255个条目保留给当前的调试数据项,而其他条目,比如计数器线程列表等,可以放在更高的范围内。
可以考虑为这个新的合并类型起个名字,比如“调试类型”或“事件类型”,来表明这些条目是与事件相关的。通过这种方式,就不需要再维护原有的两层结构,而是将所有条目合并进一个类型中,从而简化系统的结构和管理。
game_platform.h:将 DebugEvent 和 DebugEventType 重命名为 DebugType
目前,考虑到没有强烈的理由需要保留两个不同的类型,觉得这种做法显得有些过于复杂。因此,决定将这两个类型合并为一个单一的调试类型(debug type)来简化结构。接下来的步骤是将原来使用这两个类型的地方,全部修改为使用这个新的单一调试类型。
首先,将原本的调试事件类型(DebugEvent_)替换为调试类型(DebugType_),然后遍历代码中所有涉及到这些类型的部分,逐一进行替换。例如,框架标记(frame marker)所使用的调试事件,将改为使用调试类型。接着,逐一检查每个相关的部分,去掉不再需要的类型标记,确保所有地方都统一使用新的调试类型。
通过这种方式,代码将变得更简洁,避免了不必要的复杂性,并且所有地方都指向统一的调试类型,后续维护和扩展也会更加方便。
game_debug.h:将 debug_variable_type 改为 debug_type
在调整代码时,现在可以将调试变量中的类型直接更改为单一的调试类型(debug type)。这样,在添加新类型时,可以直接将事件类型添加进来,而不需要使用二级的切换方法。换句话说,代码将变得更简单,只需直接对类型进行切换,而不再需要复杂的逻辑处理。
通过这种方式,所有的调试事件类型将直接通过单一的调试类型进行处理,简化了代码结构,使得后续的扩展和维护更加方便。
game_debug.cpp:将大量的变量重命名为 DebugType
在进行代码调整时,决定将调试变量类型(debug variable type)移除,因为这个类型已经不再需要。将原本使用调试变量类型的地方改为直接使用调试类型(debug type)。通过这样做,所有的切换语句(switch statements)可以被提升到顶部,避免了原先需要逐步拆解类型的复杂性。
这种调整使得代码变得更加简洁,不再需要额外的层级和步骤,直接在顶层处理类型。这一变化让代码结构更加直观和易于理解。所有涉及调试类型事件的地方也都进行了相应的修改,确保了功能的一致性和代码的简洁性。
运行游戏,查看一切按预期工作
通过这次调整,避免了使用双重切换语句和对调试类型的逐步拆解,简化了代码结构。现在,所有的操作都集中在一个类型上,这使得代码更加清晰和简洁。所有的更改完成后,系统应该仍然能够按预期运行,功能没有问题。
这次修改虽然进展缓慢,但确保了代码的连贯性和思路更加清晰。这使得代码更具一致性,并朝着更合理和经过深思熟虑的方向发展。
game_debug.h:删除 debug_profile_settings
现在,代码看起来简洁多了,尤其是在游戏调试部分。之前有很多不同的情况和多种复杂的操作,现在这些都简化了。然而,仍然有很多工作需要完成,仍然存在很多不同的部分,可能并不是所有的部分都在按预期工作,或者有些部分可能并不必要。
接下来,关注一个更直接的部分。一个实际的调试变量应该有一个类型和名称。而且它可能是一个调试事件,也可能是其他类型的值。在检查调试配置时,发现调试配置设置实际上已经没有意义,因为之前的配置概念已经转移到了调试视图等部分。因此,相关的设置不再需要存在,代码在没有这些设置的情况下仍然能正常编译运行。
game_platform.h:将 BitmapID 和 SoundID 添加到 debug_event
现在,调试位(debug bit)实际上就是一个位ID。如果我们想要显示一个位图ID,可以直接将该位图ID从原有的位置移到调试系统中。这样,位图ID可以直接通过调试系统传递,类似地,声音ID也可以放在其中。
我们可以在代码中看到这些ID,并将它们作为输出内容进行展示。例如,如果我们输出一个位图ID,就能知道它是我们想要显示的内容。因此,不再需要把这些信息放在事件之外,而是可以直接在事件本身内处理这些信息。
接下来,打算进行相应的更改以实现这一点。
game_debug.h:删除 debug_bitmap_diaplay
打算主要去除debug_bitmap_display。将其从代码中移除后,进行编译,看看结果如何。
game_platform.h:添加 bitmap_id 和 sound_id 的类型定义
首先需要将类型定义添加到代码中。虽然这会使代码结构略有变化,但这并不会影响平台层,因为将这些类型移到平台层是可以接受的。
在文件格式中,已经有了不同的ID,比如位图ID(bitmap)、声音ID(sound)等。现在,这些都将被引入到这里,并且它们只是一些基本的包装类型。接下来,需要确保这些类型的推送(push)被正确处理。
为了确保类型的内存布局正确,打算继续使用pragma push
和pragma pack
。虽然在这些类型上可能不需要使用pragma pack
,因为它们只有一个变量,但这样做依然是有意义的,目的是确保这些类型在文件中进出时保持一致的布局。虽然可能看起来无关紧要,但为了明确它们的要求,还是保留这些指令。
game_platform.h 和 game_debug.cpp:传播 BitmapID
首先,现有的获取位图(bitmap)代码不再使用原有的方式,因此需要进行相应的修改。新的做法是将位图ID作为基础类型来处理。这样做之后,可以通过直接修改代码来实现更好的兼容性和简洁性。
在调整的过程中,将位图显示改为使用位图ID(bitmap_id
)。此外,还涉及到了其他类型,比如声音ID(sound_id
)和字体ID(font_id
)。这些类型也可以在类似的结构中进行更新和处理。
接下来,在代码中会添加位图ID相关的处理方式。确保所有类型都可以顺利地被设置和调用。通过这种方式,所有的操作变得更加直观和简洁。如果存在不明之处,可以参考更多文档或说明,来确保理解和使用上的准确性。
在这之后,还需要确保将新类型正确地整合到系统中,包括调整如何输出和显示位图ID等信息。最终,通过这些调整,代码结构将变得更加合理、清晰,并且能够支持更广泛的类型扩展。
game.cpp:将一个位图输出到我们的调试系统
在实际运行代码时,尽管由于一些持久性相关的问题,暂时无法与音乐进行交互,但可以尝试将调试信息输出。目标是将位图ID输出到调试系统,就像之前输出能量存储索引等数据一样。
为了实现这一点,首先需要定位位图ID,然后决定在哪个地方进行输出。代码中可能已经有位图ID,比如英雄角色的位图ID,或者其他的位图ID,虽然目前可能还没有具体定义。如果找到了合适的位图ID,就可以将其输出到调试系统中。
例如,可以检查是否有名为“英雄位图”的内容,如果找到了,便可以通过输出语句将这些位图ID显示在调试信息中。如果没有找到现有的位图ID,也可以尝试添加新的位图ID,并将其通过调试系统输出,以便在调试过程中查看和验证这些位图是否正确显示。
这个过程的目标是增强调试功能,帮助观察和验证调试期间使用的位图信息,从而确保一切按预期运行。
运行游戏并查看我们正在输出一个位图
现在我们可以看到输出的位图显示,虽然目前还无法与其进行交互,但它已经在调试系统中显示出来。问题在于,直到这些位图被删除之前,它们还不能完全交互,但至少现在可以看到它们的存在。
目前,位图显示已经直接输出到调试系统中,就像之前提到的那样。虽然我们能够进行一些交互操作,但这主要取决于如何正确存储有关交互对象的信息。这一部分涉及到如何追踪和管理与谁进行交互的具体数据。
通过逐步回顾代码,整个过程逐渐在脑海中形成了清晰的框架。现在,了解了每个部分如何运作,便可以逐个解释这些部分的功能和作用,确保每个步骤都被清楚地理解,并且可以正常执行。
game_debug.h:注意到 debug_variable 正在消失
现在,调试变量类基本上已经被逐步移除。可以看到它正在变得越来越精简,几乎只剩下两部分内容。这两部分现在几乎完全存在于事件本身中。
唯一不同之处在于,调试变量和调试事件之间的区别在于调试变量可以存储一个组,这意味着它可以显示某些数据,或者可以表示一个组。虽然它现在存储的是一个组,但我们也可以选择将这个概念提升一个层级,让调试变量链接本身承担存储分组的功能,而不再是调试变量。这就意味着调试变量的概念将完全消失,不再存在。
通过这种方式,我们可以简化整个结构,把更多的功能和逻辑直接移到更高层次的系统中,减少不必要的复杂性。
game_debug.h:考虑 debug_variable_link 存储 VarGroup 的可能性
现在要考虑一个问题,就是是否需要将调试变量链接直接集成到调试事件中。这意味着调试变量链接不再是一个独立的结构,而是直接嵌入到调试事件中。这样做的好处是可以进一步简化结构,因为调试事件已经有足够的空间来容纳这种变动,它本身已经支持多个32位值,所以在结构上不会产生太大问题。
不过,也有一些潜在的难点需要考虑。首先,这样做可能会导致动态查找名称的问题。因为这个结构不直接持有名称,它只是有一个可以用来查找名称的索引,这可能会带来一些不便。虽然这种问题在实际应用中可能并不会造成太大影响,但仍然需要考虑。
总的来说,虽然这种方式可能会简化代码,但在最终做决定之前,需要权衡一下可能引发的问题。需要考虑的因素包括是否真的有必要进行这种合并,是否会影响性能,尤其是在需要动态查找名称的情况下。
game_debug.h:向 debug_variable_link 添加 *Children
接下来,考虑到目前的进展,可能需要进一步审视我们如何处理调试变量链接。在当前的系统中,我们在处理这些链接时,似乎可以通过一些改动来简化它们的处理方式。比如说,我们可以让变量链接本身成为一种机制,直接帮助我们管理变量的分组和迭代。具体来说,可以将调试变量链接嵌入到var group
中,这样就能更清晰地定义每个调试变量下的内容。
具体方法是,可以通过创建一个结构体,这个结构体不仅包含了调试变量链接,还能处理它下属的“子内容”或“子变量”。这样,在进行迭代时,当我们遇到一个var group
,就可以直接访问到这些子内容,这样的设计使得变量和它的子项之间的关系变得更加直观和容易管理。
这种做法的核心是在“链接层”进行管理,即在变量链接的层面就处理所有层级的子内容,而不需要在更高层次上进行额外的操作。这种思路能够帮助进一步简化代码结构,使得每个部分的职责更加明确,同时也提升了代码的可扩展性和灵活性。
这种方法看似简单,但需要确保它能够与现有的代码结构无缝集成,因此还需要更多的测试和验证,以确保不会破坏现有功能,同时提供足够的灵活性和易用性。
game_debug.h:从 debug_variable 中移除 VarGroup,并将 *Children 重命名为 *ChildSentinel
如果希望将现有的var group
结构移除,并且将其整合成一种更简洁的结构,基本思路是将调试变量链接直接放入一个新的结构中。然而,面临的一个问题是,当前的调试变量链接被处理为双向链表的形式,这使得在进行调整时,需要特别注意如何处理链表中的“哨兵”节点。哨兵节点通常用来标识链表的起始或结束部分,但在这种简化的结构中,它可能变得难以管理。
如果继续沿用链表结构,那么就需要在链接层动态创建这些哨兵节点,这样会显得有些笨重,而且可能增加了链接层的复杂性,影响代码的整洁性。这种做法虽然可以解决部分问题,但也带来了额外的开销和潜在的管理难题。
一种替代的思路是,将哨兵节点的创建和管理转移到调试事件中。调试事件,如打开和关闭数据块(open data block
和close data block
),本身就可以包含足够的空间来存储这种类型的“环”结构,这样可以避免在变量链接层增加复杂性。
虽然这种方法显得有些不完美且具有一定的复杂度,但考虑到现有的结构和需求,这或许是一个可行的折中方案。由于目前对于哪种方法最优还没有明确的答案,决定继续深入探索,看看在实际操作中哪种方案能够更好地适应需求。这种方式通常是当不确定时采取的做法,通过实际推进来获得更清晰的解决方案。
game_debug.h:引入 debug_variable_group 来包含 Sentinel
在这个过程中,如果决定简化结构并将var group
移除,那么我们需要调整现有的逻辑,使得每次操作时不再依赖原来的var group
。而是通过debug variable link
来处理这些变量。具体来说,当我们处理一个变量时,最开始的步骤是检查debug variable link
,这将成为新的起点,并且通过这种方式来管理数据。
每个变量会包含一个指向其子元素的指针(children
),这实际上和之前的结构非常相似。你可以将每个变量视作一个具有children
指针的对象,这样就可以像处理链表一样去访问和管理这些子元素。
在这个新的结构中,基本的操作变得更直接了。当我们获取某个变量时,不需要再关注var type
,而是检查该变量是否有children
指针。如果存在,则意味着这个变量下可能有更多的子变量需要处理。在这种情况下,便可以继续进行递归操作或进一步的处理。
这个过程的核心在于,虽然变量和变量组的处理方式有所变化,但逻辑并没有发生根本性的改变。每次处理时,我们会检查是否存在子元素,然后决定是否继续深入处理。因此,从功能上看,这个改动并没有引入复杂的变化,只是将结构进行了调整,以便更好地组织数据。
总的来说,这种方式简化了管理层次,将复杂的var group
结构替换为更直接的指针管理模式,并通过debug variable link
来维护变量之间的关系。这种改动虽然需要一定的调整,但整体上是为了提高代码的灵活性和清晰度。
game_debug.cpp:传播这些新结构
在这个过程中,我们希望简化和调整当前的变量组结构。具体来说,关键的目标是重新组织和处理变量组,减少不必要的复杂性,并确保在操作时能够更高效地访问和管理这些变量。
首先,当创建一个变量组时,它不再直接处理所有的操作,而是将其简化为只进行初始化。这意味着,变量组在创建时并不会立即进行额外的操作,只是一个基本的结构,用于容纳变量。唯一的情况下,变量组才会需要额外的变量,是当该组被添加到另一个变量组时。
这个变化带来了一个关键的调整:我们不再直接在创建变量组时处理命名,而是将命名和其他操作延迟到实际的"添加变量到组"的时刻。在这个阶段,我们才会创建一个新的变量,并将它与相应的调试状态关联起来。
为了实现这一点,我们引入了一个新的概念——调试变量链接。这个链接是用于管理变量组之间关系的关键。当我们创建变量时,我们会通过调试变量链接来返回对应的链接,这样就能够在后续操作中使用该链接来访问和修改变量。
此外,在进行变量组和变量的操作时,我们要特别注意在创建时如何获得变量的链接。在创建变量时,我们不仅传递变量的名称和类型,还要传递调试状态,以便在后续处理时能够正确地通过链接来访问变量和其对应的子元素。
最终,当我们创建并添加变量到组时,重要的操作是确保每个变量的链接都被正确地记录下来,并且链接的子指针(children
)指向正确的调试块。这些操作会随着每一步的进行逐渐完善,尽管在操作的过程中可能会出现一些小的复杂性和问题。
总体而言,这个过程的核心目标是通过调试变量链接的机制来优化变量和变量组的管理,减少冗余的操作,并使得每个变量组操作变得更加清晰和高效。
调试器:检查 Iter->Link
在当前的过程中,发现了一些问题,特别是涉及到指针错误的部分。这是一个非常严重的问题,需要立即修复。由于正在进行一系列较为微妙的调整,为了避免第二天无法记起细节或导致更多的混乱,决定现在就进行修复。
当前的主要问题是某个指针出现了错误,显然不应该这样,因此需要进一步修改。具体来说,想要回到代码中,深入分析并修正这个问题,以确保在完成这一步之后,系统能够正常运行。
因为这些变化是较为复杂的,需要一步步细致地调整。如果等到明天再来做,可能会忘记具体的实施步骤,也许会导致更多问题的出现,所以下决心直接继续处理。通过实时修复,可以确保整个调整过程能够顺利进行,避免留下未解决的问题。
总的来说,虽然这些变化看起来是细节问题,但它们对整体的稳定性和功能有重要影响。因此,必须及时解决这些问题,确保整个系统在调整后能够达到预期效果。
game_debug.cpp:阅读例程
在这个过程中,关键的变化是将 debug variable
层次提升到链接层,从而使得 debug variable
可以进一步简化。这是为了优化代码的结构,使得变量组的管理更加清晰和有效。
首先,确保在添加元素到树结构时,它们能够正确地被处理。在创建变量组时,首先创建一个新的 variable group
,然后将变量添加到对应的组内。接着,确保这个变量的子元素(children)指向正确的组,这个过程符合预期。通过这种方式,树结构中的每个元素都能够保持正确的层级关系和组织。
在这一过程中,需要特别注意的是,在关闭数据块时,唯一的操作是将数据块的指针推进到下一个块,并没有修改组的结构或内容。因此,迭代过程中可能涉及到的某些操作,例如对变量组的处理,实际上并没有发生太大变化。
简而言之,所做的主要工作是确保在构建数据结构时,每个变量都能正确归属到其相应的组,并且在后续的处理过程中,能够维持这种正确的组织结构。至于其他部分,数据块的关闭操作仅仅是层级的推进,并不会影响到组本身的内容或结构。
game_debug.cpp:设置 Link->Children
问题出在创建 debug variable link
时,没有正确初始化子指针。虽然在创建 debug variable link
时,已经初始化了其他部分,但子指针(child pointer)没有被设置,这就是导致问题的根本原因。
在这种情况下,必须确保在创建 debug variable link
时,不仅初始化其它字段,还需要正确设置子指针。这是当前问题的核心,缺少了对子指针的初始化,导致了数据结构的不完整,进而引发了一些预期之外的行为。
运行游戏并注意我们离目标更近了
问题解决之后,状态已经接近理想,进一步简化了代码结构。现在,debug variable
的概念可以被完全移除,转而直接使用 debug event
,这样 debug variable
就不再需要,甚至可以把它重命名为 debug event
,直接用 debug event
来处理相关的操作。
接下来,可以开始清理并完善交互部分的功能,确保它能够正常工作并简洁高效。这些工作将在明天继续进行,确保所有的操作都能顺利执行。目前,可以先进入待处理的任务队列,以便继续推进其他部分的工作。
这很正常,我还有更多的工作要做吗?
继续展示其英语语言的掌握能力,进行流畅的语言表达和交流。
为什么叫它 Sentinel 而不是 Head,尽管它是一个 sentinel?
问到为什么将其称为"sentinel"而不是"head"时,解释说之所以叫它"sentinel",是因为它确实是一个哨兵节点。尽管它在某种程度上也可以作为"head"使用,但"sentinel"这个名称更能准确地反映它的功能。
你最喜欢的数据结构是什么?
对于最喜欢的数据结构,回答中提到,尽管现在由于缓存一致性的问题,双向链表可能在性能上不如其他结构,但仍然非常喜欢双向链表,因为它非常简单且能实现许多操作。尽管如此,由于性能问题,现代使用时有些限制,因此很难将其作为最喜欢的数据结构。
另外,单向链表也有着特殊的地位,尤其是它可以通过简单的交换原子操作来实现许多操作,且不需要复杂的检查或原子操作,这使得它在某些情况下非常有效。
虽然哈希表也非常容易实现且功能强大,但对于最喜欢的数据结构来说,还是很难做出选择,因为每种数据结构都有其优点和局限。
我不确定你之前是否在某个直播中提到过,但你打算实现横向卷轴或任何类型的平台游戏吗?
这段话提到,游戏并没有涉及到类似“侧面滚动”或者“平台跳跃”的元素,也没有包含关于这些内容的游戏设计。游戏的视角是自上而下的,主要侧重于这种视角的设计,而不是侧面滚动或者平台跳跃等机制。
《塞尔达传说》有一些伪横向卷轴的房间
提到《塞尔达传说》的设计,特别是原版中的房间布局。某些房间设计感觉不合理,尤其是在玩家获得物品时。有些房间似乎是为了填充空间而设计的,这种做法并不被看作是好的设计选择。
你到目前为止写这个引擎时,最喜欢的事情是什么?
在编写这个引擎的过程中,最喜欢的一件事是软件渲染的运行速度非常快,尽管只做了最基本的优化。看到软件渲染运行得如此迅速,感觉非常有趣,甚至让人觉得有点疯狂。
我有一个可能很傻的想法。我在想一个向量,当删除一个元素时,是否可以留下一个空的地方,迭代时跳过这个空位,这样做会仍然有缓存友好性并且在删除和插入时仍然高效吗?不过这有点超出了我的理解
这个想法基本上是提出了一个向量,在删除元素时,仅留下一个空位,并在迭代时跳过这些空位。这样做的主要问题是,虽然从理论上讲并没有什么根本上的问题,但要实现这个功能会有很多额外的开销。首先,必须有一种方法来标记已删除的元素,可能需要在向量中扩展每个位置,以便标记该位置是否被填充。这样一来,在迭代时,就需要检查每次增量操作时,不仅要检查是否到达末尾,还要判断是否需要跳过某些空位。这就会导致在迭代过程中带来额外的检查负担,降低效率。
虽然这个想法听起来不太理想,但并不是完全没有用。它可能适用于某些特定的情况,特别是当你希望删除操作稳定且不希望频繁复制元素时。在某些特定的应用场景中,这样的设计可能会有其有效性。然而,它不太可能成为一个通用的替代方案,因为这样的方法并不适合所有情况。
你最喜欢的方程是什么?
最喜欢的方程是与线性相关的方程。具体来说,它涉及到一类方程,可能是指在绘图或数学中常见的表达式。提到时,某些特定方程或公式似乎带有个人的特殊意义,可能和图形的描绘有关。例如,有提到想要画一个苹果的面积,这可能是与几何或计算相关的方程。
Blackboard:线性插值
最喜欢的方程是这样的: f ( a , b , t ) = ( 1 − t ) ⋅ a + t ⋅ b f(a, b, t) = (1 - t) \cdot a + t \cdot b f(a,b,t)=(1−t)⋅a+t⋅b。这是一个线性插值的方程,常用于在两个值之间进行插值计算。方程的形式说明了通过调整参数 t t t,可以在 a a a 和 b b b 之间得到不同的值。当 t = 0 t = 0 t=0 时,结果为 a a a;当 t = 1 t = 1 t=1 时,结果为 b b b。这种方程在计算机图形学和动画中经常被使用,特别是在平滑过渡或插值的场景中。
你使用 DirectX 吗?
不使用 DirectX。
你怎么看待 Visual Studio 的 clang C1?
对于Clang的C++编译器,不清楚Clang的C++编译器在Visual Studio中的实现是什么。
如果你在工作中做这些压缩(今天做的那些),你的流程会有多不同?
如果今天工作中进行压缩处理,流程基本相同,只是我会跳过很多步骤。在许多地方,我可以直接跳跃过去,做得更快。通常我会直接决定把某些东西移到新的位置,然后一步到位地完成,而不是像现在这样逐步进行。
但在直播中,这样做其实不太实际。因为一方面,要边讲解边快速完成这些步骤,很容易出错;另一方面,观众很难跟上这样快速的变化,甚至可能完全无法理解我到底做了什么。对于观众来说,如果只是看到“从这个地方到那个地方”的转变,根本无法知道中间发生了什么或为什么这么做。
微软正在为 Visual Studio 提供 clang 的解析 —— 中间表示将作为一种一流的完全支持的公民,计划从11月开始。中间到机器的仍然是微软(C2)
微软正在开发一个完整支持的解析中间表示(IR)系统,计划在11月发布。这将使中间表示能够更好地与机器兼容。对于微软的这种做法,个人认为这是一个合理的想法。虽然对我来说,C++并不是最重要的语言,但我希望微软能做出一个好的编译器。实际上,如果他们专注于开发一个C编译器,而不是在C++编译器上做太多工作,我觉得也无妨。
可能微软现在的做法是,将C++的解析交给Clang来处理,因为他们对解析部分并不关心,而是专注于改进代码生成部分。从这个角度看,这也是一种合理的做法,Clang作为C++的前端非常强大,能够处理大部分C++程序,甚至是一些复杂的用法。虽然我不再是疯狂的C++爱好者,但看起来Clang能够应付各种复杂的情况。
不过,像所有的技术一样,最终的效果如何,还需要实际使用才能评估。老实说,如果幸运的话,可能根本不需要使用微软的编译器,因为我可能会选择用其他语言编程,比如J语言,这样就可以避开微软的编译器了。
你认为多久会有人做出一个能与 Visual Studio 媲美的 Linux 调试器?
要在Linux上开发出能够与Visual Studio竞争的调试器,我认为可能需要极长的时间。到目前为止,已经有成百上千的Linux调试器出现,但它们都不够好。要想有一个真正能够与Visual Studio相抗衡的调试器,可能需要有人投入资金,特别是那些实际进行低层次编程的团队或公司。当前的情况表明,尽管已经有很多调试器被开发出来,但它们并没有显著改进,因此很难说什么时候能够出现一个优秀的替代品。
你怎么看待编程竞赛?你参与过吗?
对于竞赛,个人没有特别的看法,无论是赞成还是反对。没有什么具体的感觉。
你认为软件安全在现代游戏中的作用是什么?是否值得浪费 CPU 周期来防止不同的漏洞?游戏行业在实施安全方面做得怎么样,是否应该做得更多或更少?
在现代游戏中,关于安全性的讨论可以分为两类:竞技游戏和非竞技游戏。对于非竞技游戏,安全性应该完全由操作系统来处理,而游戏开发者不应该负责。每个游戏可能都存在严重的漏洞,但这不应由游戏开发者来解决,因为他们没有足够的资源和专业知识去应对这些问题。操作系统应该把游戏沙箱化,确保即便是恶意代码也无法影响到操作系统或其他程序。这是操作系统的基本责任,而微软至今未能做到这一点,实在令人失望。
对于竞技游戏来说,问题就复杂了。因为在竞技游戏中,游戏本身的完整性至关重要,必须保证游戏版本没有被篡改。这是一个更为复杂的安全问题,可能需要硬件认证和数字签名等措施。这种做法可能会涉及到谁拥有机器、谁能控制软件的问题,这也带来了一系列隐私和信任的问题。对于那些真正关心竞技游戏的人,比如电竞选手,可能需要不同的硬件来保证安全性,但这也意味着他们将失去对软件的完全控制。
总体来说,对于普通玩家来说,不必担心游戏安全性问题,操作系统应该在这一方面提供保障。而对于那些在竞技游戏中追求完美的人,他们需要面对更加复杂的安全问题,这也涉及到更深层次的硬件和软件认证技术。