游戏引擎学习第269天:清理菜单绘制
回顾并为今天的工作设定目标
昨天我们对调试系统中的菜单处理做了一些清理工作,今天我想继续对它们的展示和使用方式进行一些打磨和改进。今天的计划还不完全确定,我还没有完全决定要做什么,但是我希望能够完成这部分工作,所以我打算回顾一下,看看是否还有什么小问题或者细节需要处理。
当前我们已经有了很多功能,菜单和界面基本已经可以正常工作。昨天我提到过,我并不完全清楚今天要做什么,有些东西我还不确定,譬如我们目前可以把一些内容分开处理,但似乎在某些情况下我们无法复制一个不是“组”的对象,我也不太明白为什么会这样。理论上我们应该能够抓取其中的某些内容并进行复制,这本该是可行的,但目前好像没有实现,可能需要检查一下这个问题。
总的来说,大部分预期的功能都已经实现了,所以接下来可能就是稍微推进一下,看看能不能进一步改进这些交互方式。当前的拖动方式有点奇怪,操作起来有点不够直观。另外,当前的菜单和界面并不能很好地折叠,可能需要在这方面进行一些改进。我觉得如果所有这些内容能够被包含在一个大菜单中,并且这个菜单可以展开和折叠,那会更好。事实上,这个功能已经基本具备,只是我们还没有将其作为一个可以展开和折叠的菜单来展示。因此,我认为我们可以从这个问题开始着手改进。
我的想法是,当我们在运行游戏时,顶部应该有一个快速入口,可能是一个小按钮,只有在查看调试数据时才显示出来,这样就不会在不需要的时候显示大量内容。所以,如果这样能理解的话,我们可以先从这个方面着手。至于具体的操作方式,我还不确定怎么做,但我们可以慢慢找出最佳方法。
game_debug.cpp: 将 Group 传递给 DrawTreeLink 调用
现在,我们加载项目并进行构建。接下来,我会切换到键盘工作系统。当我们绘制主菜单时,你们应该记得,或者我希望你们能记得,现在它不再被称为那个名字了。现在的做法是,我们有一个想法,能够调用这三件事中的任何一个。这些内容实际上是我们之前所说的那些可以移动的元素,这些元素不会再像树一样显示。每一个元素像是创建了一个布局并绘制一些内容。
我们可以看到,当前的做法是,我们实际上并没有直接调用 draw_tree_link
在这个组上,而是遍历了它的子项。我的想法是,由于我们昨天已经统一了组和链接的处理方法,所以现在可能不再需要进行这种遍历了。如果我们传递一个组给它,那么应该能够直接显示根菜单作为一个实际的项。
前面怎么有一个UNHANDLED
查看分析器中的 Root 菜单
现在的效果正是我们所期望的,可以看到根菜单现在整体上是可折叠的,这正是我们想要的行为。这样当我们不希望它显示的时候,可以更加明确地将其收起。同时,如果我们想让某个内容变成一个单独的菜单,也完全可以实现。我们也可以把这些菜单放在任何我们想要的位置,如果希望它们一直显示在那里也可以,但不会再出现一长串菜单沿着界面边缘排列的情况。
从这个角度来说,这种改进是完全更优的,是一种直接的提升。
不过我们对一个点还有些顾虑,就是现在这个帧滑动区域(frame metering bit)还不是可调尺寸的。我们可能需要让这个区域可以根据需要调整大小,而且初始状态下有一个默认的宽度。这样一来,就能更加具体地控制它的显示方式。
要实现这一点其实也非常容易,考虑到我们设计的 UI 系统本身就支持这样的功能。我们已经有了必要的机制来做这些调整,因此对我们来说这并不困难。
game_debug.cpp: 使 FrameSlider 可调整大小
我们现在有一个帧滑块(frame slider),其尺寸已经定义好了。如果我们希望它具备更具体的可视调整功能,只需要做一些非常简单的修改。
当前的布局中已经有一个维度信息结构,而我们要实现的目标,是使这个滑块区域具备可调整尺寸的能力。我们不需要引入任何额外的机制,只需使用现有的“inline block”(内联块)结构即可。内联块中本身就包含了一个表示尺寸的 dim
结构,我们可以直接利用它。
我们所要做的只是从内联块中获取维度信息,比如 a 和 b 这两个维度值,然后让这部分保持之前的行为逻辑不变,同时让其变成可调整的区域。这样用户就可以根据需要调整帧滑块的显示尺寸,比如让它更小或更大。
我们在实现时,只需要在 UI 系统中添加一个标记,明确告诉系统这个元素是可调整大小的,否则系统不会让它变得可调。因此,还需要调用一个“标记元素为可调整”的函数来启用这一行为。
没有理由不让这部分变成可调的,这样做是更合理的,也能让用户根据实际需求更灵活地使用调试工具。整体实现非常直接,也符合我们 UI 系统的设计思路。
尝试调整 FrameSlider 的大小
现在我们已经把这块区域设置成可调整大小,因此现在会出现一个可以拖动调整的结构控制点,让我们可以自由控制这个区域的尺寸。
我们现在可以自由地改变帧滑块的大小。如果我们想让它变得更紧凑一点也是可以的,当然缩小之后点击每一帧会变得困难一些,但仍然是可以操作的。我们可以考虑一种逻辑,比如当这个区域被缩小到一定尺寸以下时,只显示最近的几帧,以减少拥挤或误点的情况。当然这是一个后续可以改进的方向,目前暂时保持现状也没问题。
另外,事实上我们在调试时其实也不一定需要查看这么多帧的历史数据,向后滚动那么多可能并不必要,这也是一个值得进一步优化的点。
总的来说,这部分的可调整功能已经实现得不错,界面看起来也比较合理,整体功能表现令人满意。
接下来我们还想对性能分析器的视图做一个小优化,目前它还不支持绘制下一级事件的信息。我们打算让它支持再次向下绘制一层事件数据,恢复之前未实现的部分。
我们现在正处于一种整理和收尾的状态,思路不是非常明确,就是看到哪里需要改就顺手改一改,目标是把整个系统的边边角角都处理干净、稍微打磨一下,确保整体使用起来更加顺畅、实用。
game_debug.cpp: 使 DrawProfileBars 中的线程视图更美观
现在我们正查看 DrawProfiledBars
函数,尝试改进其视觉表现。在这个函数中,我们已经在使用 PushRectOutline
来绘制轮廓矩形,但我们注意到代码缩进出现了一些问题,可能是4Coder工具(Four-Quarter)对缩进不满意,之后我们有计划修复这个问题。我们已经拥有该工具的源代码分支,有能力去修复它,但由于它需要 Visual Studio 2015 来编译,目前我们测试机上未安装,所以暂时还未动手。不过,如果需要,也可以在其他机器上进行操作。
回到绘制逻辑的改进部分,为了让性能视图的显示更美观,我们想将 PushRect
和 PushRectOutline
分开使用。想法是先绘制一层黑色的边框,再在其下方绘制实心矩形,这样在视觉上可以更加清晰和层次分明。我们思考的核心点是:先在背景中绘制一个基础的实心块,然后在其上加一个边框,让不同的事件块看起来更容易分辨,也更有界限感。
我们也意识到其实在调试这些功能时没必要频繁地重启程序,可以直接让程序保持运行状态,在修改完渲染代码后即时查看效果,这样效率会更高一些。
目前这个阶段的主要任务就是继续完善和美化调试工具的各个细节,让显示更加清晰、界面更具可读性,从而提升整个调试体验。
查看线程视图并注意到悬浮文本被截断
现在我们查看的是线程视图,发现使用实心矩形之后,可视化效果确实更清晰了一些,信息块更容易分辨。尤其是在观察更复杂的调用结构时,比如跳转到游戏更新过程(game update procedures),可以更直观地看到其中的各个部分是如何被调用的。
在这个过程中,我们注意到一个问题:当鼠标悬停在某个事件块上时,提示信息(hover text)会被裁剪掉。这并不是我们希望看到的效果,提示信息本应该完整显示,因此这是一个值得修复的问题。虽然以前没有注意到这个细节,但现在看到后明确这是一个需要改进的地方。
进一步进入暂停模式后,可以清晰地看到当前调用栈中各种函数的执行情况。例如,能看到正在调用的函数中包含了处理实体重叠(entities overlap)的逻辑,还有其他具体的功能模块。这种结构化的可视化可以帮助快速理解运行时状态,对于调试复杂逻辑非常有帮助。
我们还想到可以添加一些功能,例如在界面上明确列出当前所处的函数或模块名称,这样在分析性能或调试问题时会更直观。同时也意识到,有必要整理一份待优化或增强的功能列表,以便后续统一处理这些细节。总的来说,现在的视图更清晰易用,但仍有一些边角问题需要继续打磨。
game_debug.cpp: 使 DrawProfileBars 绘制多个事件级别
目前我们对性能分析视图进行了一些改进,目标是恢复原本递归绘制时的一层深度显示。具体思路是添加一个“剩余深度”(depth remaining)变量,用来控制当前绘制是否继续递归到更深层级。
实现上,我们在绘制 profile bar 时,增加判断:如果剩余深度大于 0,就递归调用绘制函数,并传入剩余深度减一。这样可以实现层级展开,使得在主调用显示的基础上,再深入一层显示子函数的调用和耗时。
为了让这些层级在视觉上更好区分,我们还引入了 z 值的概念。通过设定一个基础 z 值(basic z),根据剩余深度的不同进行偏移,使得每一层的绘制顺序都不会相互遮挡。例如使用公式 basic_z = 100 - depth_remaining * 10
,通过 z 值控制渲染层次,让越深层的内容位于更上层,从而确保正确显示和覆盖。
我们在实际传入绘制函数时使用了这个 z 值,比如绘制矩形使用的是 basic_z
,绘制边框时则使用 basic_z + 1
,保证边框浮于背景之上。
但当前运行时发现,这一套逻辑并未如预期工作,视图中没有看到递归绘制出来的子层级内容。初步排查:
- 节点遍历逻辑是正确的,确实从 root 开始;
- 剩余深度控制也传入了;
- z 值的计算符合预期,从深度减少中反推更高的 z 值。
因此更可能是绘制函数中某些条件判断或递归部分逻辑存在遗漏,可能递归函数未被正确触发,或者子节点的可见性等属性未设置正确,导致没有绘制出来。
后续需要针对这个问题进行具体调试,确认每一层级的绘制是否真正执行,以及是否被 z 值或其它裁剪逻辑遮挡掉。整体思路是合理的,只需要细节打磨到位,即可实现更完整、清晰的分层性能分析视图。
查看分析器中的多个事件级别
为了进一步确认当前的问题出在哪里,我们临时去掉了性能分析视图中矩形区域的实心填充,目的是验证是否存在绘制行为以及绘制是否受到 Z 值影响。
通过这一调整,我们发现子项的绘制确实存在。例如,在某些函数内部可以清楚看到子项的边框被正确绘制出来,像是 BeginSample
等函数内的子调用清晰可见,说明递归逻辑运行正常,数据结构也没有问题。这说明并不是递归绘制本身失败了,而是之前因为实心填充的问题导致子项被遮挡了。
初步结论是:绘制确实发生了,但 Z 值并没有按照预期排序,导致后绘制的矩形将前面的遮挡了。也就是说,虽然我们设置了类似 basic_z = 100 - depth_remaining * 10
这样的逻辑来确保深度较小的内容排在更前面(Z 值更高),但排序机制在实际渲染中似乎没有被正确解析或生效。
问题本质更可能出在 Z 值的使用上,比如:
- 实际用于排序的 Z 值参数在渲染管线中未被正确采纳;
- 或者 Z 值的范围设置不合理,导致不同层级之间没有明显的区分;
- 甚至可能是某些绘制函数(如 PushRect、PushRectOutline)内部使用了固定或忽略 Z 值的默认行为,导致逻辑层面设置的 Z 值没有传递到最终渲染阶段。
接下来的关键步骤是深入检查渲染函数内部,确保传入的 Z 值被用于排序或绘制顺序中,必要时可以打印调试信息或者使用更加极端的 Z 差距来测试排序效果是否生效,比如让每层之间相差 100 或更大,观察是否能够区分开来,从而锁定问题点。整体逻辑框架是正确的,问题集中在 Z 值渲染排序未生效上。
game_debug.cpp: 正确进行事件的 Z 排序
我们目前面临的问题是:虽然在绘制时传入了用于层级排序的 Z 值,但实际渲染出来的内容并没有表现出预期的层级顺序,因此怀疑排序系统并未使用我们传入的 Z 值。
为此,我们进行了一些深入排查。首先确认当前的排序逻辑依赖于所谓的“投影 Z 值”(OriginalP.Z,即 pz
),也就是 OriginalP.z
被用于决定图形元素的绘制顺序,而不是我们手动传入的“排序 Z 值”参数。
接着进一步核实渲染系统中的排序机制,发现确实并没有使用我们通过接口传入的排序 Z 值,而是完全依赖内部计算出来的 OriginalP.z
。也就是说,如果我们只是修改了排序 Z 而没有对 pz
做出相应处理,那么渲染系统就不会体现我们期望的前后层次。
从中得出关键结论:
- 传入的排序 Z 值并不会直接影响排序,排序是根据
OriginalP.z
来决定的; - 如果希望实现多层次可视化效果(比如递归多层的分析视图),就需要显式影响
OriginalP.z
的值; - 目前的排序逻辑对
OriginalP.z
过于依赖,可能导致上层逻辑传入的深度信息无效,必须调整或补充渲染接口或排序键构造的实现; - 如果不愿修改现有排序机制,也可以尝试通过控制实体在投影空间中的 Z 坐标来间接影响
OriginalP.z
,达到排序目的。
接下来需要的步骤是修改绘制接口,使其生成排序键时能够包含我们期望的深度信息,或者直接修改投影逻辑,让其反映手动设置的 Z 值。只有这样,深度递归渲染才能正确显示层次关系,解决当前子项被遮挡的问题。
查看正确排序的事件
我们解决了之前的问题,现在可以更清楚地看到各个绘制元素之间的层级关系,并且能有效地区分不同深度的内容显示。通过调整,我们已经实现了一个更直观的方式来观察和理解不同部分的绘制差异,这样在查看具体内容时更加清晰。
进入某些特定功能区时,现在能更好地可视化其中的调用流程或资源使用情况,这对于调试或性能分析都非常有帮助。整体上,这样的层级递归展示方式是一种更好地呈现信息的手段。
不过,在调试过程中也出现了一些小插曲,比如触发了不该触发的行为,导致某些内容重复执行或界面异常反应,需要及时中止并避免再次发生。这种突发问题提示我们要加强对行为触发条件的控制,防止误操作引起不必要的问题。
目前整体来看,系统已基本达到了预期目标,层级递归渲染功能得以恢复和优化,信息展示效果得到显著提升。接下来可进一步完善细节,提升稳定性与交互体验。
game_debug.cpp: 使 DrawFrameBars 也绘制多个级别
我们决定将 frames 窗口也按照相同方式进行改进,使其视觉效果更加清晰一致。在 frames 窗口中绘制条形图时,我们采取了相同的处理策略:使用 push_rect
来控制绘制区域,并先将其实现为基础的版本,不再依赖过多的额外装饰或样式处理,从而简化初步实现。
这种方式的好处在于,绘制流程更加直接,视觉上也更具层次感。就像之前在其他窗口中所做的那样,这种简化处理能够为后续扩展和美化打下良好基础,同时提升用户对界面逻辑结构的理解能力。
整体来看,frames 窗口的更新有助于在性能分析与调试过程中提供更直观的数据反馈,也增强了界面的一致性和可用性。接下来可逐步根据需要增加细节处理,比如边框、颜色区分、递归层级等,使其进一步贴合使用场景。
查看多个级别的 Frames
现在,frame bars 的显示效果已经变得更清晰了,这样可以更好地查看它们的内容。我们可以在这些条形图上进行操作,查看细节,并且它的视觉效果已经比之前更好了。
然而,当数据量增多时,性能上可能会出现一些问题,特别是在渲染大量数据时。虽然界面展示看起来已经很不错,但当数据量增加时,处理效率可能会降低。具体来说,可能是因为我们在处理大量元素时的渲染效率不高,尤其是对于大量图形的更新处理来说,当前的设计可能不够高效。
为了提高性能,需要首先确认性能瓶颈是否出现在渲染阶段,然后再采取措施优化渲染过程。可能的优化方法包括调整渲染策略、优化数据处理流程等。
总的来说,当前的实现已经能够满足大部分需求,界面的视觉效果和功能性也得到了提升。接下来,如果要进一步提升性能,我们需要进行更多的测试和分析,以确保在处理大量数据时,系统仍然能够高效运行。
game_debug.cpp: 使 DrawFrameBars 突出显示当前查看的帧
为了提升当前视图的可用性和交互性,可以添加一个功能,使当前正在查看的帧得到高亮显示。这样,在众多帧中能够快速定位当前的帧,提高可操作性和可视性。
实现方法是在绘制每个帧时,检查该帧的索引是否与当前正在查看的帧索引相匹配。如果匹配,就对该帧进行特殊的绘制,例如在其周围加上黄色的边框,确保这个帧显示在最上层,不被其他元素覆盖。为了避免覆盖正在查看的帧,可以为其添加适当的圆角,使其看起来更加突出。
此外,还可以通过对帧的颜色进行调暗来表示不同的状态。当当前帧被选中时,颜色会变得更加鲜明,未选中的帧则颜色较暗,这种变化可以帮助更清晰地区分当前帧与其他帧。
通过这种方式,用户可以更容易地在大量帧中找到当前查看的帧,同时也能通过颜色和边框来区分不同状态的帧,从而提高整体的使用体验和界面的直观性。
查看帧视图并考虑改进工具提示
为了提高当前视图的可用性,可以进一步增强帧的高亮显示,使其更加明显。例如,当暂停时,高亮的帧可能还不够明显,颜色可以调整得更加醒目,以确保在所有帧中能够清晰地识别出当前帧。这对于用户在多个帧之间切换时有很大帮助,可以直观地看到当前操作的帧。
此外,考虑到用户交互的便捷性,可以加入点击设置帧的功能,这样用户就可以直接通过点击来设置当前帧,这样更加直观和易用。同时,考虑到目前可能存在两种不同的显示方式,虽然它们可以合并成一个显示,但不打算在当前阶段过度修改设计,重点是确保现有功能的稳定性和效率。
另外,有些界面元素在切换模式时可能会被剪切掉,这种情况可能会影响用户体验。针对工具提示的显示问题,可以考虑将工具提示的显示作为一个系统化的功能,让所有控件都能够使用这个功能,而不需要为每个控件单独实现。这种通用性可以使工具提示的管理更加高效。
同时,关于一些显示信息(例如框架的运行时间、帧处理时间等),可以考虑将其作为常规的头部信息显示出来,并提供一种机制,允许根据需要动态更新这些信息。这样可以更灵活地展示性能数据,帮助用户更好地了解系统状态。
总的来说,通过改进高亮显示、增强交互功能、优化工具提示的显示机制以及动态更新信息,能够提升界面的可操作性和信息呈现的清晰度,从而提供更好的用户体验。
game_debug_ui.cpp: 引入 AddTooltip
为了使工具提示的显示更为系统化,需要对其进行重构,当前的工具提示通常是手动添加的,并且没有统一的管理机制。为了提升系统的通用性,可以考虑将工具提示的管理放到一个单独的系统中,这样任何控件都可以使用这个工具提示功能,而不必每次都为每个控件单独编写代码。具体来说,可以创建一个函数或方法来注册和显示工具提示,将它整合到系统内,便于维护和扩展。
在这个过程中,还需要处理一些细节问题。例如,工具提示的显示顺序(如排序和z值的管理)。目前,工具提示没有使用z值进行排序,可能会导致显示时被其他元素覆盖,因此可以考虑为每个工具提示指定一个z值,用于控制它们的显示顺序。为了保证正确的排序,可以让z值默认为不影响排序,但如果需要排序,则允许显式设置z值。
此外,在布局和交互方面也需要改进。例如,可以通过鼠标的位置来确定显示的文本区域,并确保工具提示的位置和样式正确无误。在实现时,可以将工具提示的显示方式与布局系统结合,使其能够更灵活地处理缩进和其他布局问题,从而提升用户交互体验。
为了避免工具提示过于复杂,减少不必要的交互,可以将工具提示设计成只在特定条件下显示,比如用户点击或悬停时。这样可以避免过多的信息干扰,提高界面的整洁度和可用性。
最后,对于调试和文本显示的信息,可以考虑将这些信息作为调试状态的一部分,放入统一的管理系统中,动态地更新显示内容,避免将这些信息散布在多个位置。通过集中管理这些调试信息,可以在调试过程中更高效地获取反馈和数据,提升开发效率。
总结来说,通过系统化管理工具提示、改进交互与布局、引入z值排序以及优化调试信息的显示,能够显著提升系统的可操作性、灵活性和用户体验。
game_debug.cpp: 引入 MouseTextLayout
为了实现鼠标悬停显示工具提示的功能,首先需要创建一个布局系统来处理这些工具提示。我们使用begin layout
来启动布局过程,并在其中设置鼠标文本的布局。布局系统需要知道一些关键信息,比如调试状态、鼠标的位置以及布局的左上角坐标(这里是鼠标的位置)。在布局过程中,添加了适当的工具提示。
具体步骤是通过使用add tilt
来注册每个工具提示的位置,确保工具提示能够正确显示。然后,通过mouse text stack
来存储鼠标相关信息。通过这种方式,系统在绘制界面元素时能够知道鼠标的具体位置,进而决定是否显示工具提示。
然而,在实际操作中,发现一些组件并没有正确使用相同的路径来处理工具提示,导致部分组件没有显示工具提示。因此,需要进一步检查draw frames slider
的绘制过程,以确保所有界面元素都按照统一的方式处理工具提示显示。
此外,鼠标位置相关的细节在系统初始化时已经设置好,可以在绘制时访问这些信息,无需在每次绘制时都进行计算。因此,初始化鼠标位置和布局信息时,只需确保相关参数正确传递,并在实际绘制时使用这些信息。
通过上述改进,系统能够在鼠标悬停时动态显示工具提示,并且为每个界面元素提供合适的交互反馈。
game_debug.cpp: 使 DrawFrameSlider 调用 AddTooltip
我们发现 frame slider 的工具提示功能实际上已经正常工作并打印输出了,只是由于其他文本输出方式(如 text_out_at
)并未使用统一的鼠标文本系统,因此未能正确检测并显示工具提示。尽管如此,现在已经明确这些差异的来源,接下来只需确保所有需要显示工具提示的部分都统一走相同的代码路径。
为了解决这个问题,我们将 text_out_at
这类原本未通过鼠标系统的组件也纳入统一的处理流程,使它们也使用新建立的鼠标文本布局逻辑。这一步能确保未来无论是哪一类界面元素,只要想要显示悬停提示文字,都能通过一致的机制实现,并具备统一的行为表现。
这样一来,原本在工具提示显示方面存在差异的部分都被归整到了同一个逻辑中。特别是当纹理下载失败时,工具提示现在也不会再莫名其妙地显示出来。系统因此变得更稳定、易维护,用户交互体验也更加一致。
game_debug_ui.cpp: 使 AddTooltip 功能正常
我们现在的任务是让 add_tooltip
函数真正完成文本的绘制工作,重新实现悬停提示的显示逻辑,并使其在新的统一布局系统中正常工作。基本目标是将原本各自独立、临时实现的工具提示系统,整合进通用的布局逻辑中去,以便维护和扩展。
首先,我们在每次添加工具提示的时候,不再是直接画出一个文本,而是将其作为一个普通的文本元素加入已有的鼠标文本布局(mouse text layout)中。这意味着我们要通过类似 begin_element
的流程为工具提示创建一个布局节点,并进行文本的布局和绘制。我们不需要复杂的框线或背景,仅仅是裸文本行的绘制即可。
我们为此从基本文本元素中复制了绘制逻辑的核心部分,但省略了交互性和其他复杂设置,因为工具提示是非交互式的元素。在布局中我们加了 non_interactive
的标记,确保这些文本不会与鼠标发生交互,也不会触发命中检测。虽然当前并没有系统化的 flag 字段来标识布局元素属性,但为了简单起见,我们临时以布尔变量的方式在元素结构中加了这个控制。
接着,为了避免在每次处理布局时都要手动设置 z
值(即绘制层级),我们计划引入一套统一的层级定义。由于绘制顺序会影响可视效果,比如工具提示应该在所有 UI 上层显示,我们希望统一管理这些 z
值,避免每次手动指定。当前的状态是绘制层级设置过低,导致工具提示无法覆盖其它界面元素,这是接下来要修复的问题之一。
我们还处理了一些布局初始化相关的问题,比如确保鼠标位置能正确初始化并传入布局系统,以及确保即使没有调用 default_interaction
,非交互元素也不会参与交互逻辑。通过这些变动,我们实现了让所有的工具提示都走统一的布局和绘制流程,并将逻辑集中化、可维护化。
最终,系统的工具提示功能得以恢复,并具备更强的可扩展性、可维护性,也为后续加入更复杂的 UI 元素(比如含边框、含图标的提示)打下了良好的基础。
查看工具提示并发现它仍然被截断
目前我们已经完成了工具提示绘制功能的重构,使其统一进入了布局系统,具备良好的可维护性和交互隔离性。接下来要解决的问题是:工具提示在窗口边界内被裁剪。这是由于当前绘制流程中,每个窗口绘制时都会推入一个剪裁矩形(clip rect),导致所有在该窗口内绘制的内容都被限制在这个矩形之中。
为了让工具提示不再被裁剪,我们的目标是在绘制工具提示时临时禁用剪裁区域。也就是说,在绘制主窗口内容时仍然启用剪裁,而在绘制工具提示之前,我们需要撤销当前的裁剪设置,使其可以“悬浮”在任意位置,不受窗口边界限制。
具体思路如下:
-
分析问题原因:当前窗口每次在开始绘制时都会调用
PushClipRect
,这会在绘制栈中加入一个新的裁剪区域,限定所有后续绘制的内容只能显示在这个区域之内。工具提示作为窗口内部的最后绘制项,自然也被限制住了。 -
设计解决方案:
- 在绘制工具提示前,手动撤销当前的 clip rect,即调用相应的
PopClipRect
(或等效的 clip stack 操作); - 或者在绘制工具提示时单独使用一个不受 clip 限制的绘制层,例如在 UI 系统中为其设计一个专用的“浮动层”或“overlay 层”;
- 工具提示也可以绘制在所有 UI 元素之后,作为单独 pass 的一部分,确保它们处于最顶层,并且不受任何局部窗口裁剪影响。
- 在绘制工具提示前,手动撤销当前的 clip rect,即调用相应的
-
实现细节处理:
- 在绘制工具提示之前,从栈中手动移除或忽略当前的剪裁矩形;
- 在必要时,绘制完毕后恢复原有 clip rect(如果系统对 clip 状态有依赖);
- 确保不会对其他正常 UI 元素造成影响,clip rect 的修改是临时的,仅针对 tooltip 有效;
- 如果布局和绘制流程是解耦的,还可以在布局时保留 clip 状态信息,到绘制时再根据内容是否为 tooltip 判断是否需要忽略 clip。
-
验证调整效果:
- 确保工具提示在所有窗口边缘都可以完整显示,不被裁剪;
- 验证双层排序、Z 值仍然有效,tooltip 仍然显示在最顶层;
- 验证其他窗口和 UI 不受到 clip rect 的更改影响,仍然正确绘制并裁剪。
总结来说,我们的目标是让工具提示跳出当前窗口的绘制裁剪限制,实现悬浮式显示。为此,通过临时撤销裁剪区域或引入独立的浮动绘制通道,确保工具提示内容完整展示,并与主 UI 系统解耦,不相互干扰。这个改动将显著改善调试工具的可用性和可视性。
game_debug_ui.cpp: 使 AddTooltip 忽略图表的 ClipRects
发现了一个问题:原本修改裁剪区域的位置是在错误的函数里,导致结果不如预期。之前设置的逻辑是“所有文本都不会被裁剪”,结果当然就出现了文本始终完全显示的现象,而不是我们希望的“仅在特定位置解除裁剪”。
现在已经确认,实际想修改裁剪区域的地方应该是在另一个正确的函数中。定位到正确的函数后,问题自然就解决了。这个位置才是绘制流程中真正作用于工具提示、需要临时移除裁剪区域的地方。
在定位正确函数之后,主要的操作包括:
1. 修复裁剪区域设置逻辑
- 将原本放错位置的“设置为默认裁剪区域”的逻辑移动到当前函数中;
- 此处是实际进行工具提示绘制的位置,因此在这里修改才有效;
- 修正后,裁剪行为符合预期:只有绘制工具提示时才移除裁剪,其它 UI 保持原有限制。
2. 恢复默认裁剪行为
- 因为之前的设置等于是取消所有裁剪(“所有文本都不裁剪”),导致效果失控;
- 改回仅在特定区域内禁用裁剪,使整个界面行为更可控,功能模块互不干扰。
3. 绑定到渲染组逻辑
- 希望工具提示依附于某个渲染组(render group);
- 调用了与之前一致的接口来处理绘制,例如
GetRenderGroup()
或类似函数; - 渲染组负责组织绘制命令,因此将 tooltip 的裁剪行为附加在这里可以统一管理;
4. 整体结果改善
- 现在,裁剪区域只在工具提示显示时临时失效;
- 工具提示不再被窗口遮挡;
- 主界面 UI 的裁剪规则保持稳定;
- 实现目标更简洁清晰,没有引入额外复杂结构;
总结:通过确认修改裁剪区域的正确位置,成功解决了之前文本总是不裁剪的问题。现在,工具提示可以在不被遮挡的前提下显示出来,而整个界面系统仍然保持原有逻辑。整个实现更加合理清晰。
查看分析器并发现我们的截断恢复到正常状态
裁剪系统现在已经恢复正常工作,所有应该被裁剪的内容被正确裁剪,而需要显示出来的内容(如工具提示)则不会被不当裁剪。整个裁剪逻辑现在可以精确控制,在需要时关闭,在其他时候恢复,非常灵活。这一点令人满意。
目前的状态是:
裁剪行为修复完毕:
- 工具提示(tooltip)在需要时能够跳脱原始窗口的裁剪限制;
- 原来的裁剪区域设定仍然保留并且生效,主 UI 元素不会被误剪;
- 成功地让部分 UI 暂时不受裁剪影响,而不会干扰整个渲染流程;
- 行为一致性恢复,渲染与交互都表现正常。
当前整体状态良好:
- 工具提示路径统一为一个调用通路;
- 文字渲染逻辑已经重新接入布局系统;
- 高度(Z 值)设置逻辑已提升,确保工具提示位于前层;
- 交互标志也已考虑周全,非交互区域不再阻挡鼠标事件;
- 渲染组结构明确,功能结构干净,裁剪/非裁剪状态分离清晰。
还剩少量开发时间:
- 当前进度接近尾声,剩余时间不多;
- 正在尝试回忆最后还计划进行的某项工作;
- 暂时进入思考阶段,梳理下一步目标或遗漏;
总结:裁剪系统已经成功修复并实现按需控制,同时配套的布局、渲染、交互设置也都对接完毕,系统状态稳定、清晰,当前任务已接近完成,剩余时间将用于收尾和补充遗漏部分。
game_debug.cpp: 使 Root 打印任意信息
我们希望让 Root 能够打印任意内容,即使在 Debug 系统完全收缩、没有实际操作的状态下,也可以有一个方式输出我们想看的调试信息。为此我们决定把这个逻辑独立出来,提取成一段通用的处理逻辑,便于后续在不同上下文中使用和修改。
我们思考了一种实现方式:构建一个调试用的缓冲区,将原先 Root 打印的内容替换成这个缓冲区里的数据。这个缓冲区不依赖于已有的真实数据结构,而是手动分配一块内存,将指针伪装成原先的 Root 名称字段。通过这种方式,我们可以完全控制输出的内容,不再依赖系统提供的数据。
我们在 debug arena 中申请了一段内存作为临时打印区域,并用 _snprintf_s
之类的格式化函数往里写调试信息。在使用这些函数时,我们遇到了一些问题,比如 _snprintf_s
的参数签名和用法不一致、C runtime 的复杂性和不稳定行为、函数重载的迷惑行为等。多次尝试后,发现必须明确传递字符缓冲区和其大小,有些情况下还得进行强制类型转换,才能正确写入格式化字符串。
我们最终成功将调试输出绑定到了这块新建的内存区域中,构建了一个名为 Root_info
的打印缓冲,并将其长度 Root_info_size
一起存储下来。每次需要打印时,只要往这块区域写入字符串就可以了。
完成这些准备后,我们将这段调试逻辑添加到每一帧渲染开始时,让它自动执行并显示上次帧的信息作为默认数据,同时为后续修改留出了接口。现在这段逻辑本质上就成为了一个小型菜单或信息面板,用于展示我们真正关心的状态信息,取代了原先的 Root 输出机制。
我们还对原有的冗余代码进行了清理,让流程更加清晰,并且为将来扩展更多自定义调试信息留出了空间。整体而言,现在这套调试系统已经达到了我们希望的灵活性和控制力。
_snprintf_s
其实是两个重载的函数
char test[256] = {};
_snprintf_s(test,sizeof(test),"test");
char *test2 = test;
_snprintf_s(test2,sizeof(test),sizeof(test),"test");
查看菜单中的新信息
目前,我们已经完成了一个功能,能够在程序中展示一个清晰的进度条或状态栏,来实时显示程序的运行状态。这项功能的目的是帮助我们在系统处于完全崩溃或暂停状态时,依然能够清晰地看到程序的当前状态,获取一些调试信息。
我们通过一系列的步骤,首先提取了相关的代码逻辑,并将其封装成一个新的模块,用来打印任意的调试信息。这个信息缓冲区在程序运行时被动态分配,可以随时更新和打印。然后,我们调整了打印的内容,使得每次调试时,所需的信息都能够清晰输出,而不是让系统处于“无响应”状态时看不清任何信息。
经过这些修改,现在系统的状态条能够实时显示各种信息,用户可以清晰地了解每一步的进展。虽然项目已经完成了当前的目标,但由于时间关系,决定在此作为一个合理的中止点,等待后续可能的优化或功能扩展。
总的来说,这个调整大大提升了调试过程中的可视化效果,有助于开发人员在程序崩溃或暂停时依然能够获取到系统的运行状态,方便后续的调试与修复工作。
Q&A
internal语法的目的是什么?
internal语法中的 static
关键字被重新定义为“internal”。这是一种自定义的做法,目的是为了更方便地跟踪变量的位置,尤其是全局变量和局部持久性变量的位置。在传统的编程语言中,static
有多个不同的含义,这可能会让管理和理解这些变量变得困难。因此,采用这种方式重新定义 static
,目的是为了能更清晰地标识和跟踪这些变量,避免混淆并提高代码的可读性和可维护性。通过这种方式,开发者可以更有效地管理代码中的不同类型的变量,尤其是在大型系统或复杂项目中,能更好地组织和追踪变量的作用域和生命周期。
我讨厌编译器告诉你参数不匹配,而不是列出你输入的参数和实际的参数。至少它可以尽量提供一些帮助!这是计算机科学中未解决的问题吗?
对于计算机在匹配参数时,只告诉用户“有一个前提匹配”而没有提供实际的参数列表以及它试图匹配的内容,感到非常不满。希望计算机至少能更有帮助,明确列出哪些参数正确,哪些是输入的内容。这似乎是一个尚未完全解决的问题。
提到某些工具,它会正确地显示匹配信息。如果记得没错,它会展示“这是你传入的内容”和“这是我试图匹配的内容”,并列出所有的选项。然而,对于一些工具如MSNBC(可能指某个特定工具或环境),这种匹配信息的展示可能并不完全。它在参数数量正确但类型不匹配时会显示,但如果参数数量不对,它却不显示。这种行为似乎并没有明确的解释。
新菜单标题中的缩写是什么?
在新的菜单头部中,出现了一些缩写和数值。首先,展示的是每一帧的持续时间,单位为毫秒。这表示从一个交换缓冲区的调用到下一个交换缓冲区调用所花费的时间。由于垂直同步(v-sync)开启,理论上每一帧的时间应该是16毫秒,也就是每秒60帧。但是实际情况是,我们看到的可能会有一些等待时间,这部分并没有做任何操作。
其次,展示了一些事件和数据块的统计信息。例如,“fit”表示一些与事件相关的节点,数字“30”和“34”代表节点的数量。而“dy”表示数据块的传输数量(即多少数据块被丢弃)。这些信息对于调试并不十分重要,但它们能够提供一些有用的背景数据,比如与实体数据的处理有关。这些实体数据通常会被标记和高亮显示,用于显示当前正在处理的内容。
人们以前问过你对函数式语言的看法,但我好奇你对它们强调的构造的看法,尤其是那些其他语言没有的构造,例如闭包和部分应用(比如 foo 接受 x, y, z,被调用时返回一个接受 z 的闭包)
对于函数式编程语言中强调的构造,像闭包和部分应用等,整体上是喜欢的。闭包允许通过在函数外部捕获变量并返回新的函数来封装功能,部分应用则通过预先绑定部分参数来简化函数的调用过程,这些都是非常有用的功能。虽然喜欢这些特性,但对于函数式编程语言的一些问题也有看法。
主要的缺点是,函数式语言通常离硬件较远,执行效率上可能不如直接操作硬件的语言。这些语言往往抽象层次较高,缺乏直接操作硬件的能力。像ML这样的纯函数式语言,或者一些现代的语言(如OCaml)虽然支持函数式编程的特性,但在底层操作上可能不如C语言那样直接和高效。
然而,若是使用能够同时兼顾C语言的底层操作和函数式编程特性的现代语言,会觉得很不错。虽然没有大量的实践经验来深入评估像柯里化等功能的实际重要性,但总体上这些功能看起来非常有用,并且在合适的实现环境下非常值得使用。
如果在像C++这样的语言中能够方便地实现这些功能,那将是很酷的,但现阶段在C++中实现这些功能并不方便,有时根本不值得去做。如果有更好的支持,函数式编程的这些特性会是非常有用的工具。
调试位图查看器在选择实体时现在能正常工作了吗?
目前调试位图(debug bitmap)在选择实体时是否有效并没有被特别关注或修复过。对于这个问题,虽然不确定具体情况,但有可能已经尝试过解决或暂时移除了相关功能。如果需要修复这个问题,其实并不复杂,应该可以相对容易地解决。
game_debug.cpp: 在 DEBUGDrawElement 中绘制位图
目前的问题可能是需要确保某个元素能够显示在最上层。首先,需要检查一下当前的显示状态,确认是否正确输出了位图ID。需要确认的是,是否在正确的地方进行了输出,或者说我们是否确实在输出位图ID。这个问题仍然不完全清楚,因此需要仔细检查程序中的相关部分,看看是否存在显示不正常或者输出错误的情况。
game_world_mode.cpp: 提供使用 BID 绘制调试位图的功能
目前的问题主要是如何正确地在调试时输出位图ID。首先,发现我们之前输出的是“躯干”,这对于调试没有太大帮助。因此,我们需要调整代码,确保正确地输出调试信息。具体来说,在循环遍历时,我们需要检查并输出正确的调试ID,同时确保该过程能够按预期执行。
为了实现这一点,代码中应避免自动调用调试原生功能,而是手动控制调试数据块的开启与结束。这样可以更精确地输出所需的调试信息。输出时,使用类似“调试开始数据块”的指令,并在调试结束时关闭数据块。
在输出信息时,应该清晰标明调试值,确保每个数据的输出都是有意义的。例如,在添加到墙体时,需要输出位图ID,帮助识别绘制使用的具体位图。过程中需要确保输出的调试信息符合预期,并且代码中存在一些不必要的调试信息应当清理,避免冗余。
此外,调试代码还应进行一定的清理工作,以确保数据的输出不会因为调试语句位置不同而产生不同的结果。比如,可以改进当前的调试值输出方式,确保它们在不同位置的一致性。
在代码实现上,还需要注意调试数据的状态保持问题。特别是调试信息的缓存和刷新,防止在重启或者更新时出现问题。最后,对于事件处理和元素的调试输出,还需要进一步检查,确保正确访问和打印事件的元素内容,而不是直接输出错误的事件数据。
运行游戏并查看我们能够可靠地写入 bitmap_id
目前我们已经能够可靠地写入调试数据,这部分已经处理得比较理想,没有问题。接下来需要关注的是选择机制的改进。当前的系统只使用了高亮功能来表示目标元素,而对实际的选择操作并未进行处理或响应,这在逻辑上并不合理。
因此需要进一步完善选择机制,确保用户在进行交互操作时,系统能够准确响应选择行为,而不仅仅是通过高亮反馈。这意味着需要将选择状态和高亮状态区分开来,并在渲染或调试输出过程中对两者做出不同的处理。
在调试流程上,我们已经回到向量数据的处理部分,这说明基础的数据结构处理逻辑仍在正常运行中,但围绕用户交互部分,尤其是调试界面对交互反馈的支持,还有改进空间。
为了整体调试系统更加清晰和一致,应该将高亮与选择的处理逻辑明确分离,分别记录状态、渲染输出并控制调试数据的写入,提升系统调试的准确性和可维护性。
game_debug.cpp: 确保位图绘制在最上层
我们在 device.cpp
中处理位图绘制的部分时,针对绘制位图所使用的 bitmap_id
,进行了调整,目的是为了更好地处理图像的图层叠加问题。
在绘制逻辑中,我们希望确保这些图像的绘制是基于适当的变换矩阵进行的。我们检查了当前用于个人效果(personal effects)处理的变换是否为背景变换(backing transform),并确认这就是我们真正需要使用的变换方式。因此,我们将原先设置为 no transform
的部分,替换成了 backing transform
,确保图像渲染时能正确地贴合在应有的位置上。
此外,所有这些绘制操作都需要被明确地写入一个特定的图层,这样可以避免这些图像被处于前景的其他内容覆盖掉。通过使用一个专门的背景图层来承载这些绘制内容,我们能有效地将不同逻辑层的图像数据隔离开,防止出现渲染混乱的问题。
完成这些设置后,整体渲染逻辑就变得清晰和一致,确保了背景图像的稳定呈现,并避免与前景内容发生冲突。最终验证结果也表明这样修改后达到了预期效果。
我们是否会制作一个内存的位图,显示已用/未用的内存,并在选择一个单元时显示它在内存区域中的位置?
在处理 (可能指代某种内存分配器或相关系统)时,我们可以选择是否将内存的使用情况显示得更加详细,例如展示哪些内存块已被使用、哪些未被使用,以及当我们选择一个单元时,它在内存 arena(内存区)中的具体位置。这种功能在实现上并不复杂,是完全可以做的。
但是否值得去做就要看实际的需求了。从当前的判断来看,没有明确的使用场景或实际效益,因此暂时还没有去做。不过如果有需要的话,是可以很容易地实现的,因为技术上这并不是一个有挑战性的功能。
总体来说,内存使用信息的可视化虽然实现不难,但是否添加进来要取决于它是否能带来实际的好处,或者是否有清晰的使用需求。我们现在对这个问题的态度是开放的:不是不能做,只是目前没有特别充分的理由去做。
game_debug_interface.h: 添加 DebugType_ArenaOccupancy
如果我们想要展示某种“内存区域”(arena)的信息,比如内存使用情况的可视化,那么在调试接口中就需要有一种机制,用于将这些区域标记为可以查看的“arena”。
可以在调试系统中添加一个像 “arena occupancy”(内存占用情况) 的视图项,表示该视图用于展示特定 arena 的使用情况。当进行绘制时,可以像绘制其他调试图形(例如条形图等)那样,添加一个类似“绘制 arena 占用情况”的绘制路径,并处理这些数据。
这个绘制模块的结构大致和已有的调试绘图代码类似,比如在现有的绘制流程中添加对 arena 图形的支持。可以为 arena 占用视图专门定义一个数据结构,例如 DebugArenaOccupancy
,记录与该图相关的所有必要信息。
在这个过程中,也许会添加一些按钮或交互控件以增强可视化操作,而绘制本身则是通过类似的代码流程完成,例如创建一个 DrawArenaOccupancy
的函数,通过传入绘图所需的基础参数(如调试绘图上下文、目标区域等)来进行绘制。
绘制方式也可以根据需要分为多种,比如不同的视图模式(紧凑视图、详细视图等),可以通过 switch
语句在绘图过程中切换不同展示方式。
至于如何获取并管理这些 arena,最直接的方式是在调试接口初始化时,传入 MemoryArena
的指针,在数据结构中保留对这些 arena 的引用。由于这些 arena 通常是稳定存在的,它们不会在程序运行中频繁创建或销毁,所以也就不需要做太多额外的逻辑去跟踪它们的生命周期。
总的来说,整个实现思路是:
- 在调试系统中注册要监控的 arena。
- 为 arena 定义专门的调试视图结构。
- 添加一个绘制流程,用于展示内存使用状态。
- 利用已有的调试绘图机制做适配,逻辑上保持一致性。
- 提供交互和切换视图的可能性,增强调试体验。
这个系统并不复杂,设计上和现有调试绘制框架兼容度很高,是可以方便扩展的内容。
game_debug.cpp: 引入 DrawArenaOccupancy
我们可以通过绘制内存区域的占用情况来实现对 memory arena 的可视化。在绘制 arena 占用图时,首先需要获取目标 arena 的指针,并确保当前我们正处于的调试元素(event)中,存在可用的 arena 信息。我们会在该事件中提取出对应的 memory_arena
,并在此基础上进行可视化展示。
绘制过程的起点是设定一个用于显示的矩形区域(frame_rect),该矩形描述了当前绘图区域的边界,包含 min_x、min_y、max_x、max_y 等四个顶点值。然后我们会定义两个子矩形区域,分别表示已使用部分和未使用部分的内存。
绘图方式上,我们通过计算一个分界点(split_point)将 frame_rect 拆分成两部分。split_point 是通过插值(lurp)计算得出的,它介于 frame_rect 的 min_x 和 max_x 之间,插值比例基于当前 arena 的使用比例(used / size)。其中,used
表示当前 arena 已使用的内存量,size
表示 arena 的总容量。
然后,我们创建两个矩形区域:
- 第一个矩形(used_rect)代表已使用的内存区域,绘制为黄色或橙色。
- 第二个矩形(unused_rect)代表未使用的内存区域,绘制为绿色。
这些矩形的绘制位置分别由 split_point 决定:used_rect 的 max_x 设置为 split_point,unused_rect 的 min_x 设置为 split_point,从而使两个矩形在视觉上连贯地表示内存占用比例。
绘图过程依赖于已有的数学库,如线性插值函数 lurp
,并最终将计算出的数值转化为绘图所需的格式(如 Vector3 结构体等)。
在绘制逻辑中,我们还需为这些 arena graph 创建一个专门的存储结构,以支持可能的后续编辑或交互功能。尽管当前可能尚未实现这些交互,但已经为未来的可扩展性打下了基础。
此外,在添加 arena 图形元素时,我们会在调试系统中注册一个新的可视图(arena graph),以便可以被系统正确地识别和处理。同时,要确保在遍历调试元素时,能够获取当前对应的 viewing_element
,它通常来自于当前处理的调试元素实例(element),并由其提供上下文信息。
总之,这一过程实现了一个动态可视化的 memory arena 占用图,自动根据当前内存使用情况生成,并在调试界面中以直观的方式展示内存分配状态,为后续的调试与优化提供了重要工具支持。
game.cpp: 为三个 Arena 添加内存 DEBUG_DATA_BLOCK
现在我们已经可以在调试界面中插入一个用于显示内存 arena 占用情况的元素。我们首先在调试系统的 UI 中添加了一个新的 UI 元素,用于显示 arena 的可视化信息。这个 UI 元素会作为一个新的数据块存在,其中的类型为 arena
,并指向一个具体的 memory arena。
为了让 UI 元素能正确地关联到一个具体的 memory arena,我们需要将一个 debug_value
类型的数据传递进来,这个值会包含指向 memory arena 的指针。在实际实现过程中,我们发现必须使用指针类型(memory_arena*
),否则类型转换会失败。因此我们对类型定义做了一些调整,确保 debug_value
能接受指针类型的 arena,并支持通过自动类型推导机制传递下去。
在初始化阶段之后,我们会将几个关键的 arena 输出到调试界面中,比如:
- transient_arena(临时内存区域)
- game_state_arena(用于游戏状态)
- moat_arena 和 audio_arena(用于特定系统或子模块)
这些 arena 是系统中持续存在的内存块,因此可以在调试过程中持续追踪它们的状态。
在将 arena 输出到调试数据结构时,我们必须确保传递的是其地址,否则调试系统无法识别和处理指针类型的值。我们也发现,如果在表达式中直接取地址,编译器可能会因临时值不满足 lvalue 要求而报错。为此,我们采用了一些工作区变量或中间对象进行包装,避免编译器拒绝取地址操作。
整个过程中,我们还面对了一些 C++ 特有的语法和类型推导问题。由于调试系统需要和宏系统或模板系统配合使用,这会引发一些令人烦恼的语言局限性。使用模板通常在最后阶段会因为类型匹配问题而失败;而使用宏虽然更可靠,但代码风格会变得更丑陋。
通过这一系列调整后,我们最终能够在调试 UI 中正确显示 arena 指针的状态。系统可以将这些指针包装为 debug_value 类型,嵌入到事件系统中,并在 UI 中以图形方式展示内存的使用情况。这为我们后续分析和优化内存管理提供了重要的可视化支持。
game_debug.cpp: 提供切换内存区域占用图表的功能
当我们在绘制 arena 占用情况时,这是作为某个 UI 元素的默认显示内容。我们可以参考之前实现的某些绘制逻辑,比如某段 bar graph(条形图)的实现方式,从中获取一些启发,并借鉴其布局和风格。
基本的想法是:我们希望通过可视化图形,展示当前 arena 的使用情况,比如已用和未用的内存比例。为此,我们定义了用于绘制的基本元素和框架,其中包括整体绘制区域(frame rect),并在该区域中根据使用情况计算出一个“分界点”,将区域划分为“已用”和“未用”两部分。
绘制时的关键逻辑包括:
-
设定绘制区域:我们设定了一个用于显示的矩形区域
frame_rect
,这个矩形定义了 arena 可视化图表的空间范围。 -
计算分界线位置:通过 arena 的
used
字段和size
字段,计算出一个百分比,然后用线性插值函数(Lerp
)来得出已使用区域在整个矩形中的边界位置。这个位置就是已用和未用之间的“split point”。 -
划分矩形区域:
-
将
frame_rect
拆分为两个子矩形:- 左边或上方的矩形表示已用内存(used_rect)
- 右边或下方的矩形表示未用内存(unused_rect)
-
两个矩形的边界通过
split point
精确计算得出。
-
-
绘制两个区域:
- 使用颜色区分:比如将已用区域绘制为黄色或橙色,未用区域绘制为绿色。
- 这些颜色选择是为了清晰地表达内存的占用状态。
-
数据绑定和交互准备:
- 每个 arena 可视化图表都通过 UI 系统关联到一个具体的
memory_arena*
。 - UI 系统需要能够识别这个 arena 并提供必要的值(如 size 和 used)。
- 当前可能还没有为这些 UI 元素添加交互控件,但这部分可以之后补充。
- 每个 arena 可视化图表都通过 UI 系统关联到一个具体的
-
结构设计与扩展性:
- 为每个 arena 创建一个结构体
arena_graph
或类似类型,用于保存绘图所需的数据。 - 之后可以通过添加
switch
分支实现多种绘制风格,例如:按字节分块、历史记录、使用率曲线等。 - 这些结构将统一放入 UI 的元素系统中,并配合调试信息展示。
- 为每个 arena 创建一个结构体
这个绘制机制为后续内存可视化分析打下了良好的基础,后面可以继续扩展功能,比如增加鼠标悬停提示、添加标签文字或显示分配记录等。整个实现中,我们在逻辑、数据结构与绘图之间保持了良好的解耦,使得调试和维护更加方便。
运行游戏并查看我们的新图表
我们现在理论上已经实现了一个具有内存功能的系统,其中包括了多个“arena”(内存区块/区域)。这部分功能已经建立,这是可以接受的。接下来,我们尝试查看某个对象或元素的内存占用情况,并且现在确实可以看到相关数据了。
不过,我们发现目前还没有打印出这些“arena”的名称,因此在观察时不太容易了解具体发生了什么。为了更清晰地呈现信息,应该增加名称输出,以便能够识别和追踪每个“arena”的状态和变化。
尽管存在这个小问题,我们已经搭建了绘制一个“arena”的基础框架。虽然时间已经超出了原本的预期,但整体上已经实现了构建并展示内存“arena”的基本流程,证明了这一方案的可行性。
game_debug.cpp: 为这些占用图表添加标签
在某个特定的 arena(内存区域)中,其实我们未必需要按照当前的方式来处理,也许还有其他更合适的方法。需要认真思考下处理的方式,也许可以留到明天再继续研究这个问题,特别是在这个功能是否真的有实际价值方面也需要再观察。
关于 debug 模式下的 arena 绘制,目标是希望能够清楚看到每个 arena 的名称,方便辨识。当我们在某个具体位置处理 arena 绘制时,需要增加一个标签,展示该 arena 的名称。我们可以通过元素的名称来进行这个操作,像是使用类似 getName()
的函数获取名字。
接下来可以仿照已有的布尔按钮代码实现一个标签控件,但这个控件不会有任何交互功能,纯粹用于显示信息。它不会响应点击或者其他事件,唯一的作用就是将 arena 的名称以文本形式展示出来。
具体实现时,这个标签会作为一个基本文本元素存在,不包含其他多余的样式或行为。文本颜色保持为白色,不随状态变化,不涉及高亮、悬浮反馈等交互特性,始终以静态方式显示。这种方式能够清晰地标示出每个 arena 的身份,从而更方便进行调试和观察。
运行游戏并查看我们添加标签后的占用图表
现在理论上我们已经可以看到各个不同的 arena,例如 mode arena、audio arena 和 transient arena。这些内存区域的名称已经能够被正确地显示出来。接下来我们需要开始做的是记录在这些 arena 中分配的内容,并将它们真实地布局出来。这个工作并不算太复杂,可能会在后续进一步完成,具体是否继续则取决于需求。
实现这一目标的方法之一是将每个 arena 本身变成一个独立的 debug 元素。这样我们就可以在每个 arena 内部存储对应的内存分配事件。每当发生一次内存分配,就记录一个事件,保存在对应的 arena 中。
不过这里面存在一些差异和需要注意的细节。与其他元素不同,arena 的内存结构支持 push 和 pop 操作,具有一定的动态性。因此,我们不会像处理其他 debug 元素那样周期性清除记录信息。相反,我们会持续保留这些事件信息,不做定期清除,以便持续追踪每个 arena 中的内存使用历史。这也就意味着这些 debug 元素会积累越来越多的事件数据,用于呈现更完整的内存活动状态。
在设计和实现上,这与之前的机制有所不同,更偏向于构建一个可以长期持久化内部状态的跟踪结构。目前没有发现其他问题或需要回应的内容,因此可以在之后的时间继续进行后续开发和优化。
相关文章:
游戏引擎学习第269天:清理菜单绘制
回顾并为今天的工作设定目标 昨天我们对调试系统中的菜单处理做了一些清理工作,今天我想继续对它们的展示和使用方式进行一些打磨和改进。今天的计划还不完全确定,我还没有完全决定要做什么,但是我希望能够完成这部分工作,所以我…...
《解锁React Native与Flutter:社交应用启动速度优化秘籍》
React Native和Flutter作为当下热门的跨平台开发框架,在优化应用启动性能方面各有千秋。今天,我们就深入剖析它们独特的策略与方法。 React Native应用的初始包大小对启动速度影响显著。在打包阶段,通过精准分析依赖,去除未使用的…...
Web3 初学者的第一个实战项目:留言上链 DApp
目录 📌 项目简介:留言上链 DApp(MessageBoard DApp) 🧠 技术栈 🔶 1. Solidity 智能合约代码(MessageBoard.sol) 🔷 2. 前端代码(index.html script.js…...
Innovus 25.1 版本更新:助力数字后端物理设计新飞跃
在数字后端物理设计领域,每一次工具的更新迭代都可能为项目带来巨大的效率提升与品质优化。今天,就让我们一同聚焦 Innovus 25.1 版本(即 25.10 版本)的更新要点,探寻其中蕴藏的创新能量。 一、核心功能的强势进 AI…...
Git简介和发展
Git 简介 Git是一个开源的分布式版本控制系统,跨平台,支持Windows、Linux、MacOS。主要是用于项目的版本管理,是由林纳斯托瓦兹(Linux Torvalds)在2005年为Linux内核开发而创建。 起因 在2002年至2005年间,Linux内核开发团队使…...
adb 实用命令汇总
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/ 基础adb命令 # 重启adb adb kill-server# 查看已连接的设备 adb devices# 进入命令行 adb shell# 使用 -s 参数来指定设备 adb -s <设备序列号> shell…...
DAX 权威指南1:DAX计算、表函数与计算上下文
参考《DAX 权威指南 第二版》 文章目录 二、DAX简介2.1 理解 DAX 计算2.2 计算列和度量值2.3 变量2.3.1 VAR简介2.3.2 VAR的特性 2.4 DAX 错误处理2.4.1 DAX 错误类型2.4.1.1 转换错误2.4.1.2 算术运算错误2.4.1.3 空值或 缺失值 2.4.2 使用IFERROR函数拦截错误2.4.2.1 安全地进…...
使用fdisk 、gdisk管理分区
用 fdisk 管理分区 fdisk 命令工具默认将磁盘划分为 mbr 格式的分区 命令: fdisk 设备名 fdisk 命令以交互方式进行操作的,在菜单中选择相应功能键即可 [rootlocalhost ~]# fdisk /dev/sda # 对 sda 进行分区 Command (m for help): # 进入 fdis…...
Python----神经网络(《Deep Residual Learning for Image Recognition》论文和ResNet网络结构)
一、论文 1.1、论文基本信息 标题:Deep Residual Learning for Image Recognition 作者:Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun 单位:Microsoft Research 会议:CVPR 2016 主要贡献:提出了一种深度残…...
PostgreSQL 的 pg_collation_actual_version 函数
PostgreSQL 的 pg_collation_actual_version 函数 pg_collation_actual_version 是 PostgreSQL 中用于检查排序规则实际版本信息的函数,主要与 ICU (International Components for Unicode) 排序规则相关。 函数基本概念 函数定义 pg_collation_actual_version(…...
使用Simulink开发Autosar Nvm存储逻辑
文章目录 前言Autosar Nvm接口设计模型及接口生成代码及arxmlRTE接口mappingRTE代码分析总结 前言 之前介绍过Simulink开发Dem故障触发逻辑,本文接着介绍另外一个常用的功能-Nvm存储的实现。 Autosar Nvm接口 Autosar Nvm中一般在上电初始化的时调用Nvm_ReadAll获…...
嵌入式STM32学习——继电器
继电器模块引脚说明 VCC(): 供电正极。连接此引脚到电源(通常是直流电源),以提供继电器线圈所需的电流。 GND(-): 地。连接此引脚到电源的负极或地。 IN(或…...
更换内存条会影响电脑的IP地址吗?——全面解析
在日常电脑维护和升级过程中,许多用户都会遇到需要更换内存条的情况。与此同时,不少用户也担心硬件更换是否会影响电脑的网络配置,特别是IP地址的设置。本文将详细探讨更换内存条与IP地址之间的关系,帮助读者理解这两者之间的本质…...
AWS SNS:解锁高并发消息通知与系统集成的云端利器
导语 在分布式系统架构中,如何实现高效、可靠的消息通知与跨服务通信?AWS Simple Notification Service(SNS)作为全托管的发布/订阅(Pub/Sub)服务,正在成为企业构建弹性系统的核心组件。本文深度…...
【Java ee初阶】网络编程 TCP
TCP的socket api 两个核心的类 ServerSocket 创建一个这样的对象,就相当于打开了一个socket文件。 这个socket对象是给服务器专门使用的 这个类本身不负责发送接收。 主要负责“建立连接” Socket 创建一个这样的对象,也就相当于打开了一个socket文…...
达索MODSIM实施成本高吗?哪家服务商靠谱?
在数字化转型的浪潮中,越来越多的制造企业开始关注达索系统的MODSIM技术。记得去年参加行业峰会时,一位来自汽车零部件企业的技术总监向我倾诉了他的困扰:"我们都知道MODSIM能提升研发效率,但听说实施成本很高,又…...
ISP接口隔离原则
任何层次的软件设计如果依赖了它并不需要的东西,就会带来意料之外的麻烦。ISP强调使用多个特定的接口,而不是一个总接口,避免依赖不需要的接口。即不需要则不应该知道。 ISP特点 降低耦合度:客户端只依赖它需要的接口࿰…...
AI Agent(8):安全与伦理考量
引言 AI Agent作为具有一定自主性的智能系统,其行为可能产生深远影响。确保这些系统安全、可靠、符合伦理标准,并遵守相关法规,不仅是技术挑战,也是社会责任。 随着AI Agent能力的增强,其潜在风险也在增加,从数据泄露到决策偏见,从自主性滥用到责任归属不清,这些问题…...
Python3虚拟环境与包管理:项目隔离的艺术
Python3虚拟环境与包管理 为什么需要虚拟环境?虚拟环境工具:你的岛屿建设者一、使用venv创建虚拟环境创建虚拟环境激活虚拟环境退出虚拟环境 二、 包管理:岛上的补给系统2.1 pip:Python的包安装工具基本用法依赖管理 2.2 高级包管…...
23、DeepSeekMath论文笔记(GRPO)
DeepSeekMath论文笔记 0、研究背景与目标1、GRPO结构GRPO结构PPO知识点**1. PPO的网络模型结构****2. GAE(广义优势估计)原理****1. 优势函数的定义**2.GAE(广义优势估计) 2、关键技术与方法3、核心实验结果4、结论与未来方向关键…...
Python自动化-python基础(下)
六、带参数的装饰器 七、函数生成器 运行结果: 八、通过反射操作对象方法 1.添加和覆盖对象方法 2.删除对象方法 通过使用内建函数: delattr() # 删除 x.a() print("通过反射删除之后") delattr(x, "a") x.a()3 通过反射判断对象是否有指定…...
用Python绘制动态彩色ASCII爱心:技术深度与创意结合
引言 在技术博客的世界里,代码不仅仅是解决问题的工具,更可以是表达创意的媒介。今天我将分享一个独特的Python爱心代码项目,它结合了数学之美、ASCII艺术和动态效果,展示了Python编程的无限可能。这个项目不仅能运行展示出漂亮的…...
【C++】红黑树
1.红黑树的概念 是一种二叉搜索树,在每个节点上增加一个存储位表示节点的颜色,Red或black,通过对任何一条从根到叶子的路径上各个结点着色方式的限制,确保没有一条路径会比其他路径长出俩倍,是接近平衡的。 2.红黑树…...
链表头插法的优化补充、尾插法完结!
头插法的优化补充 这边我们将考虑到可以将动态创建链表,和插入新链表到链表头前方,成为新链表头的方法分开,使其自由度上升,在创建完链表后,还可以添加链表元素到成为新的链表头。 就是说可以单独的调用这个insertHea…...
Java多线程(超详细版!!)
Java多线程(超详细版!!) 文章目录 Java多线程(超详细版!!)1. 线程 进程 多线程2.线程实现2.1线程创建2.1.1 继承Thread类2.1.2 实现runnable接口2.1.2.1 思考:为什么推荐使用runnable接口?2.1.2.1.1 更高的…...
超详细fish-speech本地部署教程
本人配置: windows x64系统 cuda12.6 rtx4070 一、下载fish-speech模型 注意:提前配置好git,教程可在自行搜索 git clone https://gitclone.com/github.com/fishaudio/fish-speech.git cd fish-speech 或者直接进GitHub中下载也可以 …...
Flink和Spark的选型
在Flink和Spark的选型中,需要综合考虑多个技术维度和业务需求,以下是在项目中会重点评估的因素及实际案例说明: 一、核心选型因素 处理模式与延迟要求 Flink:基于事件驱动的流处理优先架构,支持毫秒级低延迟、高吞吐的…...
解锁 DevOps 新境界 :使用 Flux 进行 GitOps 现场演示 – 自动化您的 Kubernetes 部署
前言 GitOps 是实现持续部署的云原生方式。它的名字来源于标准且占主导地位的版本控制系统 Git。GitOps 的 Git 在某种程度上类似于 Kubernetes 的 etcd,但更进一步,因为 etcd 本身不保存版本历史记录。毋庸置疑,任何源代码管理服务…...
【从零实现JsonRpc框架#1】Json库介绍
1.JsonCpp第三方库 JSONCPP 是一个开源的 C 库,用于解析和生成 JSON(JavaScript Object Notation)数据。它提供了简单易用的接口,支持 JSON 的序列化和反序列化操作,适用于处理配置文件、网络通信数据等场景。 2.Jso…...
使用FastAPI和React以及MongoDB构建全栈Web应用02 前言
Who this book is for 本书适合哪些人阅读 This book is designed for web developers who aspire to build robust, scalable, and efficient web applications. It caters to a broad spectrum of developers, from those with foundational knowledge to experienced prof…...
JavaScript中的数据类型
目录 前言 基本类型 Number 特殊的数值NaN Infinity和-Infinity String Boolean Undefined null Symbol Undefined和Null的区别 引用类型 Object(对象) Array(数组) Function(函数) 函数声…...
AI 助力,轻松进行双语学术论文翻译!
在科技日新月异的今天,学术交流中的语言障碍仍然是科研工作者面临的一大挑战。尤其是对于需要查阅大量外文文献的学生、科研人员和学者来说,如何高效地理解和翻译复杂的学术论文成为了一大难题。然而,由Byaidu团队推出的开源项目PDFMathTrans…...
第3.2.3节 Android动态调用链路的获取
3.2.3 Android App动态调用链路 在Android应用中,动态调用链路指的是应用在运行时的调用路径。这通常涉及到方法调用的顺序和调用关系,特别是在应用的复杂逻辑中,理解这些调用链路对于调试和性能优化非常重要。 1,动态调用链路获…...
【Android】文件分块上传尝试
【Android】文件分块上传 在完成一个项目时,遇到了需要上传长视频的场景,尽管可以手动限制视频清晰度和视频的码率帧率,但仍然避免不了视频大小过大的问题,且由于服务器原因,网络不太稳定。这个时候想到了可以将文件分…...
大模型中的三角位置编码实现
Transformer中嵌入表示 位置编码的实现 import torch import math from torch import nn# 词嵌入位置编码实现 class EmbeddingWithPosition(nn.Module):"""vocab_size:词表大小emb_size: 词向量维度seq_max_len: 句子最大长度 (人为设定,例如GPT2…...
深入详解人工智能数学基础——微积分中的自动微分及其在PyTorch中的实现原理
🧑 博主简介:CSDN博客专家、CSDN平台优质创作者,高级开发工程师,数学专业,10年以上C/C++, C#, Java等多种编程语言开发经验,拥有高级工程师证书;擅长C/C++、C#等开发语言,熟悉Java常用开发技术,能熟练应用常用数据库SQL server,Oracle,mysql,postgresql等进行开发应用…...
【Linux学习笔记】系统文件IO之重定向原理分析
【Linux学习笔记】系统文件IO之重定向原理分析 🔥个人主页:大白的编程日记 🔥专栏:Linux学习笔记 文章目录 【Linux学习笔记】系统文件IO之重定向原理分析前言一. 系统文件I/01.1 一种传递标志位的方法1.2 hello.c写文件:1.3 he…...
《React Native与Flutter:社交应用中用户行为分析与埋点统计的深度剖析》
React Native与Flutter作为两款备受瞩目的跨平台开发框架,正深刻地影响着应用的构建方式。当聚焦于用户行为分析与埋点统计时,它们各自展现出独特的策略与工具选择,这些差异和共性不仅关乎开发效率,更与社交应用能否精准把握用户需…...
Cesium高度参考系统
🌍 Cesium高度参考系统趣味探索 🚀 高度参考系统形象比喻 想象一下,你正在玩一个积木游戏: CLAMP_TO_GROUND:积木被"强力胶水"粘在桌面上,无论桌面高低起伏如何 RELATIVE_TO_GROUND:积木放在"微型支架"上,始终保持离桌面固定距离 NONE:积木漂…...
海纳思(Hi3798MV300)机顶盒遇到海思摄像头
海纳思机顶盒遇到海思摄像头,正好家里有个海思Hi3516的摄像头模组开发板,结合机顶盒来做个录像。 准备工作 海纳斯机顶盒摄像机模组两根网线、两个电源、路由器一块64G固态硬盘 摄像机模组和机顶盒都接入路由器的LAN口,确保网络正常通信。 …...
[python] 类
一 介绍 具有相同属性和行为的事物的通称,是一个抽象的概念 三要素: 类名,属性,方法 格式: class 类名: 代码块 class Pepole:name "stitchcool"def getname(self):return self.name 1.1 创建对象(实例化) 格式: 对象名 类名() p1 Pepole()…...
Python中的事件循环是什么?事件是怎么个事件?循环是怎么个循环
在Python异步编程中,事件循环(Event Loop)是核心机制,它通过单线程实现高效的任务调度和I/O并发处理。本文将从事件的定义、循环的运行逻辑以及具体实现原理三个维度展开分析。 一、事件循环的本质:协程与任务的调度器…...
单片机-STM32部分:11、ADC
飞书文档https://x509p6c8to.feishu.cn/wiki/OclUwlkifiRKR2k6iLbczn5tn8g STM32的ADC是一种逐次逼近型模拟数字转换器。 是用于将模拟形式的连续信号转换为数字形式的离散信号的一类设备。 逐次逼近型ADC的原理图下: STM32f103系列有3个ADC,精度为12…...
【含文档+PPT+源码】基于微信小程序的社区便民防诈宣传系统设计与实现
项目介绍 本课程演示的是一款基于微信小程序的社区便民防诈宣传系统设计与实现,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含:项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套…...
Laravel 安全:批量赋值 fillable 与 guarded
Laravel 的模型中有两个 protected 字段 fillable 与 guarded,注意:必须是 protected 以上开放程度。 我们经常通过提交表单进行数据的增删改,为了方便的进行数据批量修改操作 Laravel 提供了批量赋值机制: 假如我们想要在数据库…...
[杂谈随感-13]: 人的睡眠,如何布置床的位置比较有安全?感?
睡眠环境中的床位布置直接影响心理安全感与睡眠质量,需从空间防御性、人体感知机制及环境心理学多维度综合设计。 以下基于科学原理与实践案例,系统解析床位布置的核心策略: 一、空间防御性布局:构建心理安全边界 背靠实体墙&a…...
协议路由与路由协议
协议路由”和“路由协议”听起来相似,但其实是两个完全不同的网络概念。下面我来分别解释: 一、协议路由(Policy-Based Routing,PBR) ✅ 定义: 协议路由是指 根据预设策略(策略路由࿰…...
内网穿透系列三:开源本地服务公网映射工具 tunnelmole
以下是对 tunnelmole 简要介绍: tunnelmole 是一款开源的内网穿透工具,一行命令就能把本地http服务映射成公网可访问的链接提供公共免费的网络服务,直接下载运行命令即可使用,也支持自行配置搭建私有客户端、服务端参考开源地址&…...
发行基础:本地化BUG导致审核失败
1、早上收到邮件,Steam客服说本地化功能找不到,无法切换多国语言,所以正式版V1.0程序未通过。 大脑瞬间有要爆炸的感觉,测试后发现V1以及demo都存在同样问题。 属于重大BUG,需要立即解决,最高优先级。 2、…...
QB/T 1649-2024 聚苯乙烯泡沫塑料包装材料检测
聚苯乙烯泡沫塑料包装材料是指以可发行聚苯乙烯珠粒为原料,经加热预发泡后在模具中加热成型而制得,具有闭孔结构的聚苯乙烯泡沫塑料包装材料。 QB/T 1649-2024聚苯乙烯泡沫塑料包装材料检测项目: 测试项目 测试标准 外观 QB/T 1649 气味…...