当前位置: 首页 > news >正文

游戏引擎学习第203天

回顾当前情况

在这里我将直播完成整个游戏的制作。我们现在面临一些技术上的困难,确实如此。我的笔记本电脑的电源接口坏了,所以我不得不准备了这台备用笔记本,希望它能够正常工作。我所以希望一切都还好,尽管我不完全确定是否一切都顺利。不过我们会尽全力在这样的情况下继续前进。。如果今天的问题太多,可能我们会休息一周,等笔记本修好了再继续。不过不管怎样,我们现在的任务是继续进行调试界面的重新排列,我们已经做了很多工作,但还没有完全到达可以测试、调试并修复的阶段。今天的目标就是完成这个部分。

上周发生了一些不幸的事情,感觉自己状态很差。我的笔记本坏了,信用卡号被盗,虽然没有盗走实际的信用卡,但是数据泄露让所有人都能获取到信用卡号码,只是时间问题,最后它确实被用掉了。此外,我还扭伤了脚,总之就是一周过得非常糟糕。我感觉上周在解释树的遍历问题时讲得很糟糕,可能解释得完全错了,我甚至不太记得自己说了什么,但我确信我解释错了。所以现在我想花点时间更正一下,尝试更清楚地解释这个问题。

黑板:广度优先与深度优先树遍历

之前提到,如果你使用栈进行反转,那么你会得到宽度优先遍历(breadth-first traversal),但这其实并不准确。实际情况是,栈的类型决定了遍历的方式。对于树的遍历,栈的使用方式会直接影响遍历的顺序。即使我之前写过很多这样的代码,脑袋一时乱了,导致我没有正确理解这一点。栈的遍历方式取决于你如何操作栈中的元素。

如果我们考虑一个树的例子,假设有一棵树,节点按字母顺序排列:A、B、C、D、E、F、G、H。如果你使用栈进行遍历,首先将A推入栈中,接着访问其子节点(B和C)。然后,栈的操作顺序决定了你访问节点的顺序。对于深度优先遍历(DFS),你会沿着树的深度一路向下遍历,直到没有更多的子节点为止,然后回溯。深度优先遍历的顺序是:A -> B -> D -> E -> F -> G -> H。

如果进行宽度优先遍历(BFS),你会按照树的层级顺序来访问节点。也就是说,首先访问根节点A,然后访问A的子节点B和C,接着是B和C的子节点D、E、F、G,最后是叶子节点H。这个顺序是:A -> B -> C -> D -> E -> F -> G -> H。

这两种遍历的差别在于栈的操作方式。栈的操作可以是后进先出(LIFO)方式,这适用于深度优先遍历;而如果你从栈的底部取元素,那就变成了先进先出(FIFO)方式,这适用于宽度优先遍历。因此,栈的操作方式决定了遍历顺序。

要进行宽度优先遍历,你需要从栈的底部取元素,而不是从顶部取。也就是说,当你将节点A推入栈时,A的子节点B和C也会被推入栈中。接下来,你会先取出B,再取出C,然后继续推入B和C的子节点。这样你就会按照层级顺序进行遍历。

总结来说,栈的操作顺序决定了遍历的类型。栈本身不强制要求你按照某种特定顺序遍历,它只是一个数据结构,用来存储和操作节点。对于深度优先遍历,我们通常使用栈;而对于宽度优先遍历,则需要使用队列。

之前的解释有误,我希望通过这次更正,能够让大家更清楚地理解栈和队列在遍历树结构时的不同作用。

好的,让我通过具体的例子来帮助理解深度优先遍历(DFS)和广度优先遍历(BFS)是如何在树结构中运作的。

假设我们有一棵简单的树,树的结构如下:

        A/ \B   C/ \   / \D   E F   G

我们先来看看两种遍历的实现方式:

1. 深度优先遍历(DFS)

深度优先遍历的基本思想是“尽可能深入每一条路径”,即沿着一条分支一直向下,直到没有更多的子节点,再回溯到上一层,继续遍历未访问的节点。

遍历过程:

  1. 从根节点A开始,将A推入栈。
  2. 访问A的子节点B,B被推入栈。
  3. 访问B的子节点D,D被推入栈。
  4. D没有子节点,开始回溯到B。
  5. 访问B的下一个子节点E,E被推入栈。
  6. E没有子节点,回溯到B,再回溯到A。
  7. 访问A的下一个子节点C,C被推入栈。
  8. 访问C的子节点F,F被推入栈。
  9. F没有子节点,回溯到C。
  10. 访问C的下一个子节点G,G被推入栈。
  11. G没有子节点,回溯到C,再回溯到A,遍历结束。

DFS遍历顺序: A -> B -> D -> E -> C -> F -> G

2. 广度优先遍历(BFS)

广度优先遍历的基本思想是“逐层访问节点”,即首先访问树的所有节点的一层,然后再访问下一层。

遍历过程:

  1. 从根节点A开始,A进入队列。
  2. 访问A,A的子节点B和C被推入队列。
  3. 访问队列中的第一个节点B,B的子节点D和E被推入队列。
  4. 访问队列中的下一个节点C,C的子节点F和G被推入队列。
  5. 访问队列中的节点D,D没有子节点,继续下一个节点。
  6. 访问队列中的节点E,E没有子节点,继续下一个节点。
  7. 访问队列中的节点F,F没有子节点,继续下一个节点。
  8. 访问队列中的节点G,G没有子节点,遍历结束。

BFS遍历顺序: A -> B -> C -> D -> E -> F -> G


对比:

  • DFS 是沿着一条路径一直深入,直到叶子节点,回溯后再继续遍历未访问的节点。
  • BFS 则是按层次逐层遍历,每一层的所有节点访问完后才会进入下一层。

栈与队列的作用:

  • 深度优先遍历(DFS) 使用的是 。栈的特性是后进先出(LIFO),每次从栈中取出最后推入的节点进行访问,因此遍历是深度优先的。
  • 广度优先遍历(BFS) 使用的是 队列。队列的特性是先进先出(FIFO),每次从队列中取出最先加入的节点进行访问,因此遍历是广度优先的。

希望这个例子能够帮助你更清楚地理解这两种遍历方式的区别!

调试器:进入DEBUGDrawMainMenu并查找层级

目前的情况是,虽然我们完成了大部分的代码工作,但功能还没有完全正常工作。游戏正在运行,调试系统也显然处于活动状态,但是没有显示任何内容。最初的问题是调试界面没有显示出来,因此我们需要找出问题所在。

我们首先去检查调试部分的代码,特别是关于树形结构和层级显示的部分。我们发现调试菜单的树状结构似乎并没有出现问题,至少从代码来看,树本身应该是存在的。我们已经在调试菜单的循环中进入了树的结构,所以树应该能够正确显示。进一步检查后发现,虽然树的结构已经创建并推送到栈中,但问题出现在树的绘制上。调试菜单的绘制代码没有问题,问题很可能出在树的构建上。

继续分析时发现,当程序开始构建调试树时,它执行了一些初始化操作。我们知道程序在开始时会创建根节点,并且会往树中添加新的组。此时,我们设置了根组,并检查了这些操作的执行过程。经过调试,发现问题出在了树的添加过程上。实际的问题是我们在创建变量组时,应该调用 debug_add_variable_to_group 函数来添加新的节点,但这一部分的代码没有正确执行。

总结来看,当前的主要问题是树的构建过程没有正确添加变量到树中,导致树没有被完全建立,从而导致调试界面无法正确显示层级结构。因此,接下来的步骤是确保调用 debug_add_variable_to_group 来正确构建树的层级结构。

在这里插入图片描述

在这里插入图片描述

调试器:逐步执行并触发第一次异常

加上了两行代码段错误
目前的情况是,调试系统稍微有了一些进展,但仍然存在问题。我们已经深入到变量的处理部分,但仍然遇到一个错误,系统中存在一个指向 null 的链接,这是完全不可能发生的情况。这个错误表明我们并没有正确地初始化所有的内容,因此需要进一步查找哪里出了问题。

为了定位问题,接下来的任务是仔细检查代码,看看初始化过程中有没有遗漏的部分。我们需要确保所有相关的变量和指针都被正确初始化,以避免出现指向 null 的错误链接。
在这里插入图片描述

调试器:检查DebugState->RootGroup

接下来,我决定查看树的结构,以确定错误发生的具体位置。我首先查看了调试变量的状态,特别是查看了该组的内容。通过检查,我发现了一个问题:在 VAR 组中的一个指针应该是 null,但实际上它不应该是 null。这个问题提示我们在构建树时可能存在某些初始化的错误。

具体来说,树中的一个节点的 VAR 指针被错误地设置为 null,这是不应该发生的情况。因此,接下来需要进一步调试,找出为什么这些 VAR 指针会被错误地设置为 null,从而导致系统出现问题。
在这里插入图片描述

game_debug_variables.h:在DEBUGAddVariableToGroup中设置Link->Var

问题出现在添加调试变量时,我们实际上并没有正确设置相关的变量。具体来说,在将调试变量添加到组时,相关的设置操作并没有被执行。这导致了系统没有正确初始化,进而出现了问题。为了修复这个问题,需要确保在添加调试变量时,所有相关的变量都被正确地设置和初始化。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

运行游戏并发现它看起来稍微好一些

目前系统看起来已经有所改进,虽然仍然不完全正确,但已经接近完成。可以看到,系统中的一些功能,比如缩放等,已经能够正常工作了。问题在于这些功能的数据并没有被存储,因为缓存机制还没有实现。不过,除了这一点,其他的部分基本上是正确的,系统逐步朝着正确的方向发展。

game_config.h:确保写出正确

系统在某些方面已经有所进展,变量的保存功能已经成功实现,并且能够正确写出所有的变量数据,这部分是正常工作的。然而,整体功能仍然没有完全正确,仍然需要进一步的改进和整理。接下来,需要继续对代码进行调试和优化,确保所有功能按预期运行。

Depth 是零导致没进去写
在这里插入图片描述

在这里插入图片描述

game_debug.cpp:测试View->Collapsible.ExpandedAlways是否为真

目前,系统没有处理展开和折叠的功能,这主要是因为在实现树形遍历时,没有加入对子节点的展开与折叠检查。在之前的实现中,尽管存在折叠和展开的概念,但代码没有考虑这些节点的状态,导致即使节点是折叠的,仍然会继续向下遍历其子节点。

为了改进这一点,计划加入检查逻辑,在遍历时根据视图是否展开来决定是否继续深入子节点。如果视图是展开的,才会继续遍历其子节点;如果视图是折叠的,则不进行遍历。通过这种方式,能够更好地控制树的遍历行为,并避免对折叠节点的不必要访问。
在这里插入图片描述

在这里插入图片描述

运行游戏并发现一切都被折叠

我们现在运行这个时,理论上所有内容或者全部内容应该都会被折叠。然而,实际上我们可能永远都无法真正实现这一点。原因在于,当我们执行重新编译(recompilation)这一步时,它会覆盖掉我们用于调试视图的静态占位对象(debug view dummy)。因此,这种方法的效果可能非常有限,甚至无法达到预期目的。

game_debug.h和game_debug.cpp:向debug_state中添加debug_view Dummy,并让GetDebugViewFor返回它

其实这个是可以实现的,不过我们可以做一件事:为了测试的目的,先临时创建一个调试用的对象,放到调试状态相关的模块里。这样我们就可以把原本的影响因素排除掉。换句话说,我们可以假设这里面有一个调试用的 dummy 对象,然后这个逻辑就直接返回这个调试用 dummy。

这样一来,我们就不需要使用静态变量了,也就不会被重编译过程覆盖。通过这种方式,我们能更清楚地观察整个流程的运行情况。这种做法会让测试过程简单一些、直观一些。

在这里插入图片描述

在这里插入图片描述

运行游戏并看到调试菜单短暂展开

理论上我们觉得这个应该是可以正常工作的,确实在某个瞬间我们能看到它出现了一下,但很快就消失了。我们目前还不完全确定为什么会这样,整体逻辑上好像并不完全说得通。

这个行为让人感觉有些奇怪,所以我们打算进一步查看和分析一下到底是哪里出了问题。我们希望能找出导致这个现象的具体原因,以便后续能够修复或者调整相关逻辑。

game_debug.cpp:完成实现GetDebugViewFor

当我们进行调试并交互时,系统会将“expanded”状态始终设置为 Knox,按理来说这种设置应该只在那个调试用的 dummy 上发生一次。但实际上问题在于,这个 dummy 被多个地方同时使用了,所有模块都在向它写入数据,包括写入位置信息的部分。因此,产生的问题就可以理解了——这个 dummy 被过度复用了。

所以,与其继续用临时手段去修补,不如直接把 getDebugViewFor 的实现完成。这才是更合理也更长久的解决方案。

我们真正想要实现的,是以一种稳定的方式保存用户交互中的状态,比如一个元素是展开还是折叠。但我们不希望这些状态硬编码在具体变量上。举个例子:如果我们有两个完全相同结构的树状组件或者位图查看器,但它们在布局或尺寸上略有不同,我们希望系统能够分别记住它们的状态。

为了解决这个问题,我们的目标是:只要有一个调试变量存在,无论它有多少不同的视图状态(比如 UI 状态、展开与否等),都可以被单独地缓存起来。这样我们就能针对不同实例(如多个树或位图)保留独立的交互状态。

所以当我们调用 getDebugViewFor 时,传入的其实是某个具体的“链接”(link),而不是变量本身。因为链接已经包含了变量的引用信息,所以没必要再单独传变量了。

接下来,在 getDebugViewFor 里,我们会基于这个链接进行状态缓存和读取。

此外,为了统一交互逻辑,我们决定之后所有交互行为都不再直接作用于变量,而是基于变量的链接来进行。因为这些链接才是储存合成状态(如 UI 状态、用户交互反馈等)的地方。

我们做的调整其实很简单:交互对象从变量换成了链接,交互逻辑基本不变。只是我们更换了一个层级去处理数据,也就是从“变量级”切换到了“链接级”。

debugInteract 函数里,我们也要把交互目标改为链接。但有时我们可能还无法在很上层判断是否是变量,所以需要在更底层做判断处理。

总的来说,我们通过引入链接来作为状态缓存的主入口,实现了对多个调试变量实例状态的独立管理。这样既解决了 dummy 复用冲突的问题,也让整个调试框架更具扩展性和健壮性。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

运行游戏并看到它几乎正常工作

现在我们已经完成了从直接与变量(bars)交互,改为与链表(links)交互的调整。这样一来,在结构层级上,我们就有了一个更清晰、分离的管理方式。逻辑上变得更干净、可控,而且从实际运行来看一切也都保持正常。

接下来我们要做的,就是把缓存机制(cache)真正实现好。当前的问题是,所有组件仍然在写入同一份状态数据,导致状态互相干扰。为了避免这个问题,我们需要为每个交互对象维护一份独立的状态缓存。

一旦我们完成这个缓存的实现,每个调试组件的交互状态(比如展开/折叠、滚动位置、UI反馈等)就会被隔离开来,不再互相影响。这会让整个调试系统更加稳定、可靠。

所以下一步就是去完成这部分缓存逻辑的编码,实现多个链接对应的独立状态存储。完成之后,整个系统的调试交互逻辑就算真正打通了。

game_debug.h:考虑将debug_view存储在debug_variable_link中

我们现在要尝试继续完善这个功能,也就是在调用 getDebugViewFor 时传入 link 变量。从逻辑上讲,我们现在其实已经有了 link,那么是否可以直接把状态存储在 link 上?这看起来似乎是更自然的做法。

回头看之前的实现思路,之所以绕了一圈去设计缓存机制,可能只是因为当时思路有些混乱。其实我们已经拥有所需的信息——每个 link 都可以作为状态的存储载体,那为什么不直接利用它?

不过,这种做法也不是没有问题。最大的问题在于:如果将来我们不想依赖 link,或者存在一些不具备 link 的场景,比如某些迭代器遍历的是实体成员,但这些成员并不是明确注册成 link 的变量。那在这些情况下,如果状态完全依赖 link 存储,就会带来一定局限性。

举个例子:如果我们只是遍历某个实体的所有成员,希望这些成员在调试界面上是可编辑的,但又不希望它们必须显式声明为 link。那么如果状态都存在 link 上,就无法满足这种需求。

这说明,如果我们希望系统在某些模式下更灵活,比如无需绑定 link 就能提供交互功能,那么把状态完全绑定在 link 上反而可能成为一种限制。

所以,这个问题需要权衡。一方面,使用 link 作为状态容器逻辑清晰、结构简洁;另一方面,脱离 link 的状态管理也必须被考虑进去,以支持更宽泛的调试与编辑需求。可能最终的方案需要支持两种模式:有 link 时就直接使用它进行状态存储,而在无 link 的情境下,则使用外部缓存机制来补充这个功能。

game_debug.h:引入debug_id

我们现在更倾向的做法是引入一个抽象的标识概念,比如叫做 debugViewTagdebugViewID。这个标识用于明确区分不同的调试视图实例。通过这种方式,我们就能很好地对调试状态进行管理,而不再依赖特定的数据结构,比如变量链接本身。

变量链接(link)本身可以拥有一个 viewID,也可以通过其它方式生成这个 ID,比如使用某个对象的指针或者其他可以唯一标识视图的东西。这样我们就能准确地定位和标识某个具体的调试视图实例,而不会因为多个相似实例之间的状态冲突而出现混乱。

所以我们决定采用这种方式来处理调试视图状态的识别问题。比如可以定义一个结构体,里面包含两个指针(或者其他足够精确的标识数据),作为调试视图的唯一 ID。然后所有调试相关的状态存储、读取、更新,都是基于这个 ID 来进行的。

通过这样的机制,我们可以为每个视图生成一个唯一标识,同时还能保持系统的通用性和灵活性,不再局限于一定要通过变量链接来实现状态管理。这样设计既清晰又可扩展,适用于各种不同的交互场景和调试需求。
在这里插入图片描述

game_debug.cpp:将ViewID传递给GetDebugViewFor

我们接下来要做的,就是实现这个调试视图标识的生成逻辑。我们会传入这样一个标识对象,用来标识和区分不同的调试状态。我们可以把它称作 debugID,或者更具体地说是 debugIDFromLink,表示这个标识是从某个变量链接中生成的。

这个过程非常简单,可以快速生成出对应的 ID。例如,当我们有一个变量链接时,就可以直接从中构造出一个 debugID。这个 ID 是一种抽象化的标识形式,可以代表一个具体的调试视图实例。

通过这种方式,我们实现了一个清晰的分离:不直接依赖变量或者链接对象本身,而是通过一个可组合、可复用的调试 ID 来管理所有视图状态。这样每次操作都只需要传递这个 debugID,就能在缓存中读取、存储或更新对应的交互状态。

接下来就是把这个流程串起来,让系统支持基于 debugID 进行状态操作,从而让调试功能更加稳定灵活、易于扩展。
在这里插入图片描述

在这里插入图片描述

game_debug.cpp:引入DebugIDFromLink

我们现在要让 debugID 返回一个结构体,用于唯一标识每一个调试视图实例。思路是:我们有一个变量链接(link),从这个链接中提取其指针地址作为 ID 的基础。

我们定义一个 debugID,它包含两个值。第一个值(value0)设为 link 的指针,这个指针本身就已经能够唯一标识这个具体的链接对象。第二个值(value1)暂时设为 0,保持未使用状态,留作将来扩展或者用于额外标识。

这个 debugID 是我们管理状态缓存的关键,通过这个标识,我们可以轻松查找或记录某个调试组件的状态。

这样设置之后,系统就可以通过一个统一的方式识别和处理任何调试视图。每个视图都有自己的独立 ID,不会互相干扰。无论是变量链接、实体成员、还是其他需要交互的结构,只要能生成这样的 ID,就能纳入统一的调试体系中。

这个机制简洁高效,为我们后续的状态缓存、交互同步等功能打下了很好的基础。下一步就是基于这个 ID 实现状态存取的逻辑,使整个调试系统真正运行起来。
在这里插入图片描述

game_debug.h:向debug_interaction中添加debug_id ID和debug_variable *Var

我们现在可以更进一步地完善整个交互体系:交互系统不再要求必须传入变量链接(link),而是只需要传入一个能被调试系统识别的标识,也就是之前定义的 debugID

换句话说,调试交互的本质现在变成了:只要能提供一个 debugID,就可以进行交互操作。这个 debugID 成为了调试交互的核心锚点。

虽然变量本身仍然会作为交互对象传入(因为我们确实是在操作这个变量),但我们通过额外提供一个 debugID,来标识当前操作发生的具体上下文。这就避免了状态混淆,比如在多个界面上操作同一个变量时,可以通过不同的 ID 区分状态。

这样设计之后,为后续做进一步的转换和优化提供了良好基础。比如,我们可以实现更灵活的调试 UI、多视图支持、局部状态隔离等等功能,而且也为以后可能的状态快照、序列化和回溯等功能埋下了伏笔。

这一改动使调试系统的设计从面向对象本身,转向面向上下文的状态识别方式,是一个非常关键的转变。接下来就可以基于这种机制,展开更多高级功能的实现了。
在这里插入图片描述

game_debug.cpp:还原DEBUGEndInteract和DEBUGInteract中的代码

这可能是目前最合适的方案。现在,不需要再使用之前复杂的处理方式,而是可以将代码恢复到一种更简洁、直接的形式。通过这种简化,我们可以让交互更加流畅和高效。

具体来说,我们将使用 debugStateinteractionID 进行交互标识。这样,交互系统就能通过传入的 debugStateinteractionID 来正确识别和处理调试操作。对于 debugState 的交互,我们只需要传入对应的链接(link),这样就能简洁地管理交互逻辑。

总之,通过这种调整,系统变得更加简化,交互处理不再复杂化,而且维护性也更好。这种方法不仅解决了之前的一些问题,还让代码更加清晰,便于后续扩展和优化。
在这里插入图片描述

game_debug.cpp:引入VarLinkInteraction以简化这些例程

为了使得整个交互系统更加清晰和高效,我们决定再进行一步优化。当前,交互的赋值方式较为随意,缺乏一定的结构性和标准化。为了提升系统的可维护性和一致性,决定重新设计交互处理的流程。

具体来说,我们将引入 VarLinkInteraction 这种结构,它将变量链接(link)和交互类型一起传递给交互处理逻辑。这一结构将被用来创建调试交互(debug interaction),而这个调试交互会包含所需的所有信息,包括调试变量的链接指针、交互类型、调试 ID 等。通过这种方式,交互的各种信息都会被正确地封装在一起,避免了重复赋值和信息分散的情况。

这样一来,所有的交互行为就通过这个标准化的流程进行处理,确保每次交互都能统一管理,减少了代码的冗余和混乱。当系统中需要修改交互处理方式时,只需要在这一部分进行调整,避免了对多个地方的修改和调试。

通过这种设计,交互管理变得更加模块化和集中,其他部分的代码将通过调用标准的调试交互接口来进行处理。这个方法提升了代码的清晰度、可扩展性,同时也让以后修改和优化变得更加容易。
在这里插入图片描述

game_debug.h:向debug_state中添加debug_view *ViewHash

为了能够查找调试视图(debug view),需要实现一种机制,将调试 ID 映射到对应的视图状态。目前,我们已经有了调试 ID 的传递方式,但还没有实际的查找机制。因此,需要构建一个简单的哈希表,用于存储和查找调试视图。

具体来说,我们将使用哈希表(hash table)来映射调试 ID 到视图对象。这是一个非常直接的映射过程。在实现哈希表时,由于指针值通常会对齐到一个特定的内存边界,因此可以通过忽略指针的低位(底部位)来优化哈希查找。这样做可以避免哈希表的冲突,并提高查找效率。

接下来,我们就可以用哈希表来存储调试视图,并通过传入的调试 ID 来查找对应的视图。在实现哈希表时,最关键的是选择一个合适的哈希函数,保证它能够有效地分配哈希桶,并且避免冲突。好的哈希函数可以显著提升查找速度和系统性能。

总之,通过引入哈希表来存储和查找调试视图,可以让系统更加高效,并且能够支持大规模的视图交互管理。而正确选择和实现哈希函数,则是确保这个系统高效运行的关键。
在这里插入图片描述

game_debug.cpp:在GetDebugViewFor中实现哈希函数

为了实现调试视图的查找和插入,需要通过哈希表来存储和管理调试视图。在这种实现方式中,首先要计算哈希索引,使用调试 ID 生成哈希值,然后查找哈希表中是否存在相应的视图。如果找到,则表示该视图已经存在,无需插入;如果找不到,则需要创建一个新的视图并将其插入哈希表。

具体步骤如下:

  1. 计算哈希索引:通过调试 ID 来计算哈希索引。调试 ID 是由两个值组成的,因此哈希索引可以通过这两个值的某种组合方式生成。可以使用一些简单的操作,例如将两个值相乘或相加来混合这些值,以生成一个哈希索引。需要注意的是,哈希计算时应该忽略指针的低位,因为这些低位通常包含内存对齐的信息,无法用于区分不同的元素。

  2. 查找哈希表:根据计算出的哈希索引,查找哈希表中是否已经有对应的调试视图。如果找到了对应的视图,说明该视图已经存在,可以直接返回该视图。如果没有找到对应的视图,则需要继续进行插入操作。

  3. 插入新的视图:如果没有找到匹配的视图,则需要创建一个新的调试视图,并将其插入到哈希表中。在插入时,需要根据视图的 ID 以及其他相关信息来设置该视图的属性,比如类型和下一个视图的指针,构建哈希表中的链式结构。

  4. 链式哈希表:哈希表采用链式结构,所有具有相同哈希值的元素会被链在一起。因此,插入新视图时需要确保新的视图正确地链接到哈希表中的其他视图。

通过这样的哈希表管理系统,可以有效地查找和管理调试视图,从而提高系统的性能和可维护性。
在这里插入图片描述

在这里插入图片描述

game_debug.cpp:引入DebugIDsAreEqual

为了比较哈希值,只需要检查两个调试 ID 的指针是否相等,因为调试 ID 由两个指针组成。如果这两个指针的值相同,则可以认为它们是相等的。因此,比较哈希值的过程非常简单,只需要检查这两个指针的值是否相等即可。

在这个过程中,我们确保每个调试 ID 都有唯一的指针表示,并通过指针比较来判断哈希值是否匹配。这使得哈希表的查找和插入操作变得更加高效。
在这里插入图片描述

运行游戏并看到我们的内容正常工作,除了缩进问题

目前,我们已经成功实现了调试视图,并且理论上它应该可以正常工作,实际测试中也确实表现良好。现在,系统能够创建多个可编辑的项目,并且每个项目能够独立保存自己的设置,这样的功能非常实用。接下来,需要做的是修复一些缩进问题,确保代码的整洁和一致性。

我们还剩下大约十分钟的时间来继续改进,首先解决缩进问题,以便让代码更易于阅读和维护。通过这种方式,最终将能够保证系统在处理多个独立的调试视图时,能够有效地管理每个视图的状态和设置。
在这里插入图片描述

在这里插入图片描述

game_debug.cpp:设置Layout.Depth以修正缩进

问题出在布局深度没有被正确使用,导致代码没有按预期工作。理论上,布局元素应该有一个深度值,而这个值没有被正确地应用。这个问题是由于没有充分利用这个布局深度造成的。

为了解决这个问题,接下来需要对布局深度进行处理,确保它在相应的逻辑中得到使用。应该确保在相关的代码中,布局的深度被正确地引用和传递,这样才能使得布局的深度值得到有效使用,并确保整体布局系统正常工作。

总之,问题的根本原因是布局深度没有被正确传递和使用,需要对代码做出调整,明确地处理布局深度,确保它参与到最终的布局计算中。
在这里插入图片描述

在这里插入图片描述

运行游戏并看到缩进效果已经很好

问题出在调试相机的距离没有正确设置。调试相机的距离应该是一个标量值,但是它没有被正确地赋值。经过检查后发现,可能是因为该值被设置为零,导致其值无效。

为了修复这个问题,需要确保在相关的逻辑中正确设置调试相机的距离。此时,距离被错误地设置为 false,导致其值为零。应该调整代码,确保当相机距离被计算或更新时,它能够获得正确的标量值,而不是错误地被设置为零或布尔值。

总之,问题的根本原因是相机的距离设置不当,需要修复这个值的赋值逻辑,确保它在调试过程中能正确反映相机的实际距离。

game_debug_variables.h:调查为什么Real32没有正常工作

遇到的问题是调试相机的距离值不再是预期的标量值,而是被错误地识别为布尔值。经过检查发现,DebugCameraDistance 变量类型似乎被错误地设置成了布尔值,而不是预期的 real32 类型。这导致调试过程中该值被当作布尔值处理并打印出来。

在进一步调查后,发现 DebugCameraDistance 变量实际上没有被定义。因此,程序没有正确地识别并使用该变量,而是误用了布尔值。由于该变量未定义,程序无法获取到正确的类型和数据,导致值被错误地处理为布尔值。

为了解决这个问题,首先需要确保 DebugCameraDistance 变量被正确定义为 real32 类型。然后需要检查代码中的变量赋值部分,确保调试时传递的是正确的标量类型,而不是布尔值。
在这里插入图片描述

在这里插入图片描述

game_config.h:尝试手动为DebugCameraDistance添加.0f

问题的根本原因在于 debug camera distance 变量的定义。在检查过程中发现,变量 debug camera distance 应该被初始化为 0.0,即 real32 类型的零值,而不是误用其他类型。通过将其初始化为 0.0point zero,可以确保其被正确地识别和处理为 real32 类型,而不会被错误地当作布尔值或其他类型。

此外,程序中的类型推断机制可能导致了这个问题,程序试图根据值的类型来决定变量的类型,这就导致了不正确的类型匹配。因此,确保在配置文件中正确地为该变量设置初值,并明确其类型为 real32,有助于解决这个问题,确保变量能够正确地传递和使用。

运行游戏并发现DebugCameraDistance正常工作

目前,问题已经解决,debug camera 已经正常工作,没有出现什么实际的问题。接下来,下一步的逻辑应该是继续推进当前的工作,可能是进一步优化代码或实现其他功能。没有出现新的错误,说明之前的调整已经达到了预期的效果,可以安心继续进行后续的开发和调试工作。

game_debug.cpp:重新实现tear-offs

接下来,目标是让“tear-offs”功能正常工作,并进行测试。首先需要做的是在菜单中创建一个新的选项,可以从中提取出一个实例并与之交互。为此,需要实现一个新的功能,首先需要创建一个调试树(debug tree)。在此过程中,要使用之前定义的调试方法,如 debug_add_group,并结合现有的上下文状态来初始化该树。

具体实现步骤是:

  1. 创建一个新的调试树,并为其命名。
  2. 添加一个变量组到树中,这样就能把变量分组并进行管理。
  3. 然后通过调用 debug_add_variable_to_group 方法将变量添加到已经创建的组中。

这一过程实际上是将创建调试变量的步骤封装起来,使得整个操作更加简洁和模块化。通过这种方式,可以确保每个调试元素都能按需求进行灵活操作。

总之,通过这些步骤,能够构建起一个灵活的调试环境,支持动态地创建、管理和交互多个调试项,这也为后续的开发测试奠定了基础。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

game_debug_variables.h:引入DEBUGAddVariableToDefaultGroup

为了简化调试变量的添加过程,可以创建一个新的内部函数 DEBUGAddVariableToDefaultGroup,该函数将只负责将变量添加到默认的变量组中。这样,能够直接调用此函数,并且通过上下文来决定将变量放入何处,从而确保变量按需求分配到适当的组中。

具体步骤如下:

  1. 创建一个名为 DEBUGAddVariableToDefaultGroup 的函数,该函数将负责将变量添加到默认组。
  2. 通过这个函数,可以直接将变量添加到上下文中,确保每次都按照上下文的要求进行处理。
  3. 在执行 debug_add_route_group 时,可以调用 DEBUGAddVariableToDefaultGroup,它会自动将变量添加到默认的变量组中。

通过这种方法,调试变量的处理将变得更加简洁和一致。每次调用时,都会自动根据当前的上下文来处理变量,从而简化了变量组的管理。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

运行游戏并发现tear-off行为恢复如前

首先,通过克隆现有的设置,我们创建了一个新的实例(。在此过程中,理论上,这个新的实例应该与之前的行为相同,因为我们还没有做任何区别化的处理。然而,为了使这些实例能够正确地展开和收缩,我们需要确保它们能够根据需要进行调整。

接下来,要实现这个目标,需要检查调试交互的设置,尤其是对“树”操作的处理。可能在实现“添加树”的操作时,存在一些细微的错误,导致复制操作异常,可能是复制了整个结构,而不是预期的部分内容。这时需要重新审视“添加树”的逻辑,确保它能够正确地处理和区分每个实例,尤其是在调整它们的展开和收缩行为时。

game_debug.cpp:在DEBUGDrawMainMenu中获取正确的组

在处理这个问题时,我们首先尝试通过查看当前的设置,确认是否正确地处理了“tear value”。问题出现在树的显示上,我们发现在某个地方将树的根节点处理错误,导致显示了整个树,而不是我们预期的部分内容。

经过检查,发现问题出在主菜单的设置上。原本我们想要获取树的根节点,但实际上在某些操作中却错误地抓取了不是根节点的内容。这种错误导致了不正确的行为,调试时会让人困惑,因为错误的原因很难直接看出来。解决方法是删除错误的部分,并修正代码中的逻辑,使得树的根节点被正确地处理,从而恢复预期的功能。

在这里插入图片描述

在这里插入图片描述

运行游戏并拆分特定值

在这里,目标是从树中分离出特定的“tear-off”值,而不是整个树结构。通过这个操作,我们希望将这些元素解耦,使它们可以独立操作。目前的问题是这些元素依然是绑定在一起的。因此,解决方案是需要通过某种方式来区分这些元素,确保它们不再互相影响。

虽然理论上可以通过深度复制整个层级来实现解耦,但这样做会比较麻烦。考虑到状态是存储在外部的,其实完全可以通过拆解整个层级来处理,而无需进行深度复制。这种方式更简洁,因为状态已经在外部得到了妥善管理。

game_debug.h:使DebugIDFromLink和VarLinkInteraction使用Tree

为了确保不同的调试视图(debug view)能够正确区分并互相独立,目标是使调试 ID 更加明确,能够表达出当前正在迭代的是哪个树结构。这样做的好处是,当需要创建新的调试视图 ID 时,只需要传递树的信息,而不必重复克隆整个树结构。因为状态是外部管理的,所以我们可以直接复制状态,而无需复制整个树。

具体的做法是在创建调试视图变量链接(debug variable link)时,要求在进行 ID 创建时传递树的信息。这样,当我们执行调试 ID 链接时,能够清楚地知道当前操作的是哪棵树,从而避免重复的树克隆。通过这种方式,调试视图的迭代和交互会变得更加高效且清晰。
在这里插入图片描述

运行游戏并看到状态已经被隔离

通过这种方式,当我们将这些调试视图(debug views)分离后,它们应该会表现得完全不同,并且确实如此。现在,状态被存储为一个查找项,不仅会知道当前操作的是哪个调试视图,还会知道是哪个树在被操作。这样,可以确保每个调试视图的状态独立,避免了它们之间的相互干扰。

此外,在处理交互时,可以根据需要进一步扩展这一机制。例如,当我们高亮显示某个功能时,两个视图可能都会被高亮显示。这在某些情况下可能是一个好现象,能够提供更多的信息反馈,但如果不希望如此,我们也可以通过在头部比较调试 ID 来避免这种情况发生。

总体来说,成功实现了目标,即隔离了每个调试视图的状态。这对于提升交互的独立性和管理性非常有帮助,且对于未来的扩展也提供了良好的基础。
在这里插入图片描述

回顾今天

所做的工作实际上是将状态分离出来,并将其存储在可以通过某些关键值进行查找的地方。这样,当我们生成界面时,不再需要担心为每个元素单独存储状态。我们只需要请求获取某个特定树中、特定位置的状态存储,并且可以根据需要动态获取。

这种方法的一个优点是,只有当我们实际遇到某个元素时,才会存储它的状态。如果这棵树非常庞大,比如包含数十亿个条目,我们并不会为所有这些条目存储状态,而是仅仅为我们已经处理过的元素存储状态。这非常重要,因为这意味着即使树的规模是无限大的,我们也只会为实际接触过的元素创建状态存储。例如,假设这棵树代表着所有世界块(或其他类似的结构),我们不需要为所有这些元素提前创建存储空间,而是只有在访问到某个元素时才创建对应的存储。

这种方式的好处是,大规模的树结构能够被高效地管理,不会因其巨大规模而导致内存浪费,从而能够处理无限大的树结构,而不必为每个节点预先分配空间。

提出一个纯设计问题。在GetDebugViewFor()方法中,你的方法命名为查询,但在某些情况下它似乎是一个命令(它改变了系统的状态)。我听说通常要避免这样做。你同意吗?为什么会这样?

在设计上,方法命名为“GetDebugViewFor”时,其名字听起来像是一个查询(getter)操作,然而在某些情况下,它的行为却类似于命令(command),会改变系统的状态。有人提到,通常建议避免这种情况,命名应能明确区分查询与修改状态的操作。

对于这种命名方式的选择,通常会有两种看法。第一种是,更倾向于将方法命名为准确反映其功能的方式,如果一个方法会修改状态,即使它的名字像是查询,也可以接受,尤其是在一些情况下,命名的清晰性和开发的便利性可能更为重要。第二种则是,如果严格按照惯例命名,可以帮助开发者在代码中迅速识别出哪些方法是查询,哪些是会修改状态的操作,从而提高开发效率和减少错误。

在这段对话中,尽管方法命名为“GetDebugViewFor”,但实际上它背后有缓存机制,因此并不会立即改变系统的状态。因此,命名并未引起困惑,实际行为是符合预期的。总的来说,命名习惯和规则可能因人而异,但只要能清楚地表达方法的作用并保持一致性,是否严格按照传统命名规则并非绝对关键。
在这里插入图片描述

你认为学习所有的算法和数据结构是必须的吗?还是可以在需要某个算法或数据结构时再学习?还是说这类知识必须提前学习,以便知道何时需要它们?

学习基本的算法和数据结构是非常重要的,尤其是了解它们的基本功能和用途。虽然不一定需要知道如何从头实现每一种数据结构,比如不需要掌握如何编写一个B树的实现,但至少要理解它是什么,以及在什么情况下可能会用到它。这样在遇到问题时,你才能够识别出自己需要的工具,否则你可能会不知道该使用哪种数据结构。

如果完全没有这方面的基础,可能在遇到实际问题时就无法快速判断该选择什么方法。这意味着你无法在遇到需求时再去学习,因为此时你还不知道自己需要什么。所以,学习一些常见的算法和数据结构,并了解它们的基本概念是非常必要的。即使是抽象的概念,比如“映射”或“哈希表”,了解它们能帮助你更好地做出技术决策。

总的来说,广泛了解可用的选择和它们的作用,对于解决编程问题至关重要。这是一个基本的准备,可以让你在遇到问题时有更多的思路和方向。

问:二叉搜索树是B树的一个子集吗?我认为是

二叉搜索树(Binary Search Tree,BST)并不是B树(B-tree)的子集。虽然可以从某种角度来看,二叉搜索树是B树的一种变种,主要体现在每个节点有两个分支,而B树每个节点可以有多个分支。但是,二者在实现方式上有很大区别,因此不太适合将二叉搜索树视为B树的子集。

在二叉搜索树中,每个节点最多有两个子节点(左子树和右子树),而B树则允许每个节点有多个子节点,并且有不同的结构和维护方式。这使得它们在内存管理和性能优化等方面的设计差异较大。

尽管如此,理解二者的关系时,的确可以认为二叉搜索树是一个简化版本的B树,但它们的实现和用途还是有所区别的。

关于你之前的内存分配:难道内存分配的大小不应该依赖于用户选择的世界大小吗?目前它是在编译时固定的(如果我理解正确的话)

在内存分配的设计上,面临着一个困境。游戏的目标是能够处理任意大小的世界,但在实现过程中,如何根据用户机器的内存大小来动态调整世界的大小是一个挑战。

问题的关键在于,理想情况下,用户的机器内存越大,他们应该能够创建更大的游戏世界。例如,拥有32GB内存的用户应该能够创建比只有4GB内存的用户更大的世界。然而,难点在于很难设计一个算法,能够准确预测在特定大小的内存下,生成的世界会占用多少内存。因此,难以在程序运行时动态地计算出合适的世界大小。

如果能够解决这个问题,可以在程序启动时检测用户的内存大小,然后根据这个信息限制用户创建世界的最大尺寸,以确保其可以适配机器的内存。如果无法做到这一点,那么就需要选择一个合理的世界尺寸,这个尺寸能够在大多数系统中运行,并且不超出系统的内存限制,这将是用户能创建的最大世界大小。

我想成为像你一样的工具程序员/游戏技术作家,但我仍在学习中,尚未完全具备相关资格。我在考虑加入一个小型游戏公司,那里的要求可能没那么严格(他们可能使用Unity等工具),同时进行自己的低级别自学。这个主意怎么样?可以吗?

如果你想成为像工具程序员或者游戏技术写作这样的职业,现在通过进入一个简单的游戏公司积累经验,并同时进行自学,是一个非常合理的想法。

在游戏开发中,涉及的工作远不止编程这一部分,很多工作都和游戏制作的整体过程相关。即使你想成为一个专注于工具或引擎开发的程序员,了解整个游戏制作流程也非常重要。因此,即便你在一个使用Unity等简单引擎的公司工作,也能收获宝贵的经验,尽管这些经验可能不直接与你未来的工具开发方向完全契合。

在Unity等引擎中制作游戏的过程,可以让你深入了解游戏开发的各个方面,尤其是团队合作、艺术创作、游戏设计和项目管理等。更重要的是,亲身体验这些过程,你能发现Unity等引擎的优缺点。你会更清楚它在哪些方面表现优秀,在哪些方面存在问题,以及为何会出现问题。这些经验对于你将来选择是否使用Unity,或者在自定义引擎开发中采取哪些改进措施,都有很大的帮助。

此外,如果你能在一个拥有更强技术团队的公司工作,那将更好。比如,即使你做的工作相对简单,主要是做游戏脚本或者一些较基础的开发,但你能有机会和做引擎开发的资深程序员一起工作,这样你能学习到更多高阶的技术。尽管这样的机会比较难找,但能在这样的环境中工作无疑会对你的技术成长有极大的帮助。

总的来说,通过开始在游戏公司工作,尤其是通过与经验丰富的程序员合作,积累经验,并结合自学,最终能够让你在游戏开发和工具编程的职业道路上走得更远。

你提到过使用Blender。你是3D建模师吗?具体来说,你用这个软件包做什么操作?

我确实知道如何进行3D建模,并且能够制作生产级别的3D模型。然而,我在3D建模的纹理绘制方面并不擅长,因此在纹理处理和Rigging(绑定)方面可能不太合适,但在建模方面我绝对能胜任。

目前,我使用Blender主要是为了进行前期可视化(previz),这有助于与艺术家们的沟通。在Blender中,我能够快速制作一些模型的草图或初步版本,这样团队成员都能对某个项目的方向有一个共同的理解。目前的团队中,我是唯一一个拥有3D建模经验的人,因此我承担了这个职责。

虽然团队中的其他成员也有一定的3D经验,但大部分时间她们并不频繁使用3D软件,所以我主要负责在Blender中做一些初步的可视化工作,确保团队在创作和设计上达成一致。

我想看到Blender的预演直播

Blender的前期演示其实并没有特别的原因。现在你可以直接上网查找很多Blender的入门视频,里面会有详细的介绍,展示如何使用Blender进行各种操作和技巧。这些视频通常会帮助新手更好地理解Blender的基本功能和工作流程。

你会考虑做一个短时间的非正式直播,向大家展示一些你不会涉及的事情(比如元编程)吗?

在直播中展示像元编程这样的内容其实不太现实,因为这些内容通常需要很长时间才能充分讲解清楚。大部分我讲解的内容都是可以在相对较短的时间内解释清楚的,因此更适合直播。而一些需要更深入讲解的内容,像元编程,通常需要几小时的专门时间来覆盖,直播中很难做到这一点。所以,任何我没有涉及到的内容,基本上都是那种需要较长时间来详细讨论的。

嘿,喜欢在有空的时候看直播。对于一个想进入游戏开发的新手程序员,你推荐学习什么语言?

对于初学编程并希望进入游戏开发的初学者来说,选择哪种语言取决于目标和所需的复杂程度。如果你决定走专业的游戏开发路线,C和C++几乎是行业中最常用的语言,尤其是对于大型和复杂的游戏项目而言。但对于初学者来说,这些语言可能并不是最好的选择,因为它们对于编程新手来说可能太复杂。

如果只是想进行一些轻度的游戏编程,C#会是一个不错的选择,因为很多游戏开发者都使用Unity,而C#是Unity中的主要脚本语言。Unity的易用性和广泛应用,使得C#成为了许多人入门游戏编程的首选。

如果是完全没有编程基础的初学者,可以选择一种更加简单、易于实验的语言。比如我当时写的第一个程序是用Basic语言,这种语言是专为初学者设计的,能够让他们快速上手,理解编程的基本概念。如今,类似的语言可能已经不太常见,但可以寻找一些现代化的入门语言,这些语言可能会比较简单,允许用户在没有编程经验的情况下就能开始尝试编写代码。

现在有很多面向初学者的工具和语言,比如GameMaker之类的,它们可能会包含一些简单的脚本语言,允许没有编程经验的人开始进行实验。这种工具通常会有简单的调试功能,帮助学习者快速理解代码如何运行。

如果要我推荐一种工具,我可能会建议选择一个具有调试器的编程环境,这样可以帮助快速识别和修复代码中的问题,进而加快学习进程。调试器对于初学者非常重要,它可以帮助理解代码的执行流程以及定位错误。

总体来说,初学者应该寻找一些简单的、面向游戏开发的语言和工具,带有调试功能,能帮助他们更好地理解编程原理,并快速看到编写代码的结果。这种方式可以有效地加速学习过程。

相关文章:

游戏引擎学习第203天

回顾当前情况 在这里我将直播完成整个游戏的制作。我们现在面临一些技术上的困难,确实如此。我的笔记本电脑的电源接口坏了,所以我不得不准备了这台备用笔记本,希望它能够正常工作。我所以希望一切都还好,尽管我不完全确定是否一…...

从菜鸟到高手的提示词优化指南‌

如何用“说话的艺术”榨干AI潜力? ——从菜鸟到高手的提示词优化指南‌ 一、什么是好的提示词? 核心公式‌:精准提问 明确需求 限定条件 示范案例 好比让AI帮你买咖啡—— ❌ 差提示:“帮我买杯咖啡”(AI可能随便…...

应对高并发的根本挑战:思维转变【大模型总结】

以下是对这篇技术总结的详细解析,以分步说明的形式呈现,帮助理解亿万并发场景下的核心策略与创新思维: 一、应对高并发的根本挑战:思维转变 1. 传统架构的局限 问题:传统系统追求零故障和强一致性,但在海…...

【Java集合】单列集合List详解

参考笔记: java 单列集合List 万字详解(通俗易懂)_java singlelist-CSDN博客 目录 前言: 一、概述 二、特点 三、使用集合的经典四部曲 四、List接口常用的方法 五、List接口实现类——ArrayList 六、List接口实现类——Ve…...

蓝桥刷题note13(排序)

1.冒泡排序 适用场景: 数据量较小:适用于数据量较小的情况,例如数组长度在 10 以内。 优点 稳定性:冒泡排序是一种稳定的排序算法,相同元素的相对顺序不会改变。 缺点 时间复杂度高:平均和最坏时间复杂度为…...

【AI模型核心流程】(一)大语言模型输入处理机制详解与常见误解辨析

一、引言 大语言模型(LLM)如GPT、BERT、LLaMA等,已成为自然语言处理领域的核心技术。然而,许多开发者对其底层输入处理机制存在误解,尤其是从自然语言文本到模型可理解的向量表示这一过程。本文将从技术细节出发&…...

如何完整迁移 Git 仓库 ?

Git 已经成为软件开发中版本控制和协作的事实上的标准。有时,开发人员可能需要将整个 Git 存储库 (包括其历史记录、分支和标记) 移动到新的位置或托管服务。在这个全面的指南中,我们将讨论在不丢失任何关键数据或历史记录的情况下无缝地重新定位完整 Gi…...

《在 Ubuntu 22.04 上安装 CUDA 11.8 和 Anaconda,并配置环境变量》

安装 CUDA 11.8 和 Anaconda 并配置环境变量 在本教程中,我们将介绍如何在 Ubuntu 22.04 上安装 CUDA 11.8 和 Anaconda,并配置相应的环境变量。我们还将配置使用 阿里云镜像源 来加速软件包更新。以下是具体步骤。 步骤 1:更新软件源 首先…...

残差神经网络(ResNet)概念解析与用法实例:简洁的图像处理任务

目录 1. 前言 2. ResNet的核心思想 2.1 残差学习 2.2 跳跃连接 3. ResNet的架构 3.1 残差块 3.2 ResNet的整体架构 4. ResNet实例:随便处理处理图像 5. 总结 1. 前言 随着深度学习的发展,神经网络的层数不断增加,但随之而来的是梯度…...

家里网络访问Github有时候打不开,解决办法

1、修改Hosts文件修改法 通过DNS查询工具(如)获取最新GitHub域名解析IP修改系统hosts文件(路径:C:\Windows\System32\drivers\etc\hosts),添加:20.205.243.166 github.com 20.27.177.113 github…...

VirtualBox 配置双网卡(NAT + 桥接)详细步骤

在 VirtualBox 中为 CentOS 虚拟机配置双网卡(NAT 桥接),使其既能访问外网(NAT),又能与宿主机(Windows 10)或局域网通信(桥接)。 步骤 1:关闭虚…...

【2023】ORIGIN或MATLAB 颜色图,等高图,颜色条——需要拟合补全中间的颜色

前言 不是我疯了,就是世界疯了。我不知道究竟是哪一个疯了。瓶口和瓶盖尺寸不符。也许该怪瓶子,也许该怪盖子。但不管怎样,尺寸不符的事实不容动摇——《1Q84》 \;\;\;\;\;\; 有十几二十个导出的曲线数据,其中第一列是频率点,大约1001个,第二列是某种数据,都在0~1之间…...

flutter 专题 七十三Flutter打包未签名的ipa

在Flutter项目开发完成之后,需要把iOS项目拿给第三方(如打包机)进行签名,那我们首先就需要准备打包好未签名的的ipa包。 打包之前,需要先从第三方获取到iOS证书(.p12)和描述文件(.mobileprovision),然后然…...

ngx_get_full_name

定义在 src\core\ngx_file.c ngx_int_t ngx_get_full_name(ngx_pool_t *pool, ngx_str_t *prefix, ngx_str_t *name) {size_t len;u_char *p, *n;ngx_int_t rc;rc ngx_test_full_name(name);if (rc NGX_OK) {return rc;}len prefix->len;#if (NGX_WIN32)if (…...

leetcode-代码随想录-链表-链表总结篇

理论基础 链表: 每个节点由两部分组成:数据域和指针域(存放指向下一个节点的指针);入口节点称为头节点;最后一个节点的指针域指向NULL(空指针)。 分类: 单链表双链表&…...

如何用Python轻松实现快速复制或剪切文件列表中的所有文件呢?

在程序开发的过程中,处理文件是我们日常工作中一个很重要的环节。想象一下,当你需要把一大堆文件从一个文件夹移动到另一个文件夹时,手工操作真的会让人觉得烦躁对吧?这时,用代码来处理这些烦恼,真是太方便…...

【棒垒球规则】全国幼儿软式棒垒球比赛规则(二)·棒球1号位

幼儿棒垒球设备 2.01 球棒 球棒使用组委会提供的泡棉发泡安全球棒,以安全环保材料制成;球棒规格:长度为 53 厘米,重量为 200 克(10 克),棒头直径为 7 厘米,握把直径为 3 厘米。 2…...

在MacOS 10.15上使用MongoDB

这次是在MacOS 10.15上使用MongoDB。先在豆包问支持MacOS 10.15的MongoDB最新版是什么,答案是MongoDB 5.0。 抱着谨慎怀疑的态度去官方网站查询了一下,答案如下 MongoDB 7.x支持的最低版本MacOS是11MongoDB 6.x支持的最低版本MacOS是10.14 又找deepsee…...

【Ragflow】11. 文件解析流程分析/批量解析实现

概述 本文继续对ragflow文档解析部分进行分析,并通过脚本的方式实现对文件的批量上传解析。 文件解析流程 文件解析的请求处理流程大致如下: 1.前端上传文件,通过v1/document/run接口,发起文件解析请求 2.后端api\apps\docum…...

企业供应链管理

企业供应链管理 企业供应链管理 企业供应链管理企业信息化信息化的作用信息化的发展阶段信息化建设的挑战 SRM(供应商关系管理)SRM架构参考图企业内部系统协作: ERP (企业资源计划)OA (办公自动化)业务功能模块:企业日常办公 EMS …...

性能测试之jmeter的基本使用

简介 Jmeter是Apache的开源项目,基于Java开发,主要用于进行压力测试。 优点:开源免费、支持多协议、轻量级、功能强大 官网:https://jmeter.apache.org/index.html 安装 安装步骤: 下载:进入jmeter的…...

常见的微信个人号二次开发功能

一、常见开发功能 1. 好友管理 好友列表维护 添加/删除好友 修改好友信息(备注、标签等) 分组管理 创建/编辑/删除标签 好友分类与筛选 2. 消息管理 信息发送 支持多类型内容:文本、图片、视频、文件、小程序、名片、URL链接等 附加功…...

Muduo网络库实现 [十三] - HttpRequest模块

目录 设计思路 成员设计 模块实现 设计思路 首先我们要先知道HTTP的请求的流程是什么样子的,不然我们会学的很迷糊。对于HTTP请求如何到来以及去往哪里,我们应该很清楚的知道 HTTP请求在服务器系统中的传递流程是一个多层次的过程: 客户端发起请求…...

探索C++11:解锁现代编程(3)

1.包装器 1.1function std::function 是 C 标准库中的一个模板类&#xff0c;位于 <functional> 头文件中。它用于封装可调用对象&#xff0c;包括普通函数、Lambda 表达式、函数对象、成员函数等。std::function 提供了极大的灵活性&#xff0c;使得你可以将不同类型的…...

软件工程(应试版)图形工具总结(二)

遇到的问题&#xff0c;都有解决方案&#xff0c;希望我的博客能为你提供一点帮助。 教材参考《软件工程导论&#xff08;第六版&#xff09;》 七、 层次图&#xff08;H图&#xff09;与HIPO图 1、概述 1.1、层次图&#xff08;Hierarchy Chart / H图&#xff09; ​核心…...

人工智能在前端开发中的应用探索

一、人工智能在前端开发中的应用场景 人工智能&#xff08;AI&#xff09;技术的快速发展为前端开发带来了新的机遇和挑战。AI在前端开发中的应用主要集中在以下几个方面&#xff1a;智能代码生成、自动化测试、个性化推荐、智能交互设计以及性能优化。这些应用场景不仅提高了…...

木马学习记录

一句话木马是什么 一句话木马就是仅需要一行代码的木马&#xff0c;很简短且简单&#xff0c;木马的函数将会执行我们发送的命令 如何发送命令&#xff06;发送的命令如何执行? 有三种方式&#xff1a;GET&#xff0c;POST&#xff0c;COOKIE&#xff0c;一句话木马中用$_G…...

WebSocket 也有跨域问题?如何让 Spring Boot WebSocket 允许跨域连接?

前言 在现代 Web 开发中&#xff0c;跨域问题一直是开发者必须面对的挑战。无论是传统的 HTTP 请求还是实时通信的 WebSocket&#xff0c;浏览器的同源策略&#xff08;Same-Origin Policy&#xff09;都可能成为功能实现的拦路虎。许多开发者对 HTTP 的跨域解决方案&#xff…...

音视频入门基础:MPEG2-PS专题(8)——使用Wireshark分析GB28181的PS流

音视频入门基础&#xff1a;MPEG2-PS专题系列文章&#xff1a; 音视频入门基础&#xff1a;MPEG2-PS专题&#xff08;1&#xff09;——MPEG2-PS官方文档下载 音视频入门基础&#xff1a;MPEG2-PS专题&#xff08;2&#xff09;——使用FFmpeg命令生成ps文件 音视频入门基础…...

Bash详解

各类资料学习下载合集 ​​https://pan.quark.cn/s/8c91ccb5a474​​ Bash详解 Bash(Bourne Again SHell)是Linux和Unix系统中最常用的命令行解释器之一。它不仅提供了强大的命令行操作功能,还支持脚本编程,使得用户能够自动化任务和实现复杂的操作。本文将详细介绍Bash…...

WORD+VISIO输出PDF图片提高清晰度的方法

WORDVISIO输出PDF图片提高清晰度的方法 part 1: visio 绘图part 2: word 导出 part 1: visio 绘图 先在visio中把图片和对应的文字调整为适合插入到文章中的尺寸&#xff1b; 在visio中把所有元素进行组合&#xff1b; 把组合后的图片长和宽等比例放缩&#xff0c;如放大10倍…...

springMVC--Controller配置总结

控制器Controller 控制器复杂提供访问应用程序的行为&#xff0c;通常通过接口定义或注解定义两种方式 控制器负责解析客户的请求并转换成一个模型 在springMVC中&#xff0c;一个控制器类可以包含多种方法 在springMVC中&#xff0c;对于controller的配置有多种 实现Contr…...

JavaScript BOM核心对象、本地存储

目录 BOM 核心对象详解 一、location 对象 1. 常用属性 2. 常用方法 3. 应用场景 二、navigator 对象 1. 核心属性 2. 常用方法 3. 应用场景 三、history 对象 1. 核心属性和方法 2. 应用场景 四、兼容性与注意事项 五、总结 本地存储与复杂数据类型处理 一、本…...

单元测试之测试覆盖率-jacoco基本使用

简介 免费的、开源的、针对java的单元测试覆盖率工具。基于字节码&#xff0c;无需源码也可以工作。 代码覆盖率&#xff1a;用来衡量测试代码对功能代码的测试情况&#xff0c;量化说明测试的充分度。通过执行测试用例&#xff0c;功能代码中的哪些行被执行了&#xff0c;哪…...

css3.31面试题

CSS 相关的面试题一般围绕基础知识、布局、性能优化、兼容性、深入原理等几个方向。以下是一些常见的面试题总结&#xff1a; CSS 基础知识 盒模型&#xff08;Box Model&#xff09;是什么&#xff1f;有哪些类型&#xff1f; px、em、rem、vw、vh、% 的区别&#xff1f; …...

Nature Electronics|一种透气、可拉伸的液态金属基3D电子皮肤系统(健康监测/可穿戴电子/透汗透气性电子/电子皮肤/柔性电子/集成电路)

一、 摘要 穿戴式和皮肤电子设备的发展要求高密度可伸展电子系统能够与软组织共形,持续运行并提供长期的生物相容性。大多数可拉伸电子系统的集成密度低,并且与外部印刷电路板连接,这限制了功能,降低了用户体验并阻碍了长期可用性。在此,作者提出了一种可渗透的三维集成电…...

【家政平台开发(15)】解锁Spring Boot:家政平台后端开发全攻略

本【家政平台开发】专栏聚焦家政平台从 0 到 1 的全流程打造。从前期需求分析&#xff0c;剖析家政行业现状、挖掘用户需求与梳理功能要点&#xff0c;到系统设计阶段的架构选型、数据库构建&#xff0c;再到开发阶段各模块逐一实现。涵盖移动与 PC 端设计、接口开发及性能优化…...

AI Agent设计模式二:Parallelization

概念 &#xff1a;并行任务执行引擎 ✅ 优点&#xff1a;提升吞吐量&#xff0c;充分利用多核资源❌ 缺点&#xff1a;复杂度高&#xff0c;存在竞态条件风险 from langchain_openai import ChatOpenAI from langgraph.graph import StateGraph, START, END from typing impor…...

Upload-labs靶场通关

之前搭好了靶场&#xff0c;Upload-labs 靶场搭建 及一句话木马的原理与运用-CSDN博客 今天开始通关并写详细流程 Pass-1 来到靶场的第一关 先随便上传php 代码 点击上传 发现文件类型被限制了 方法1&#xff1a; 改文件后缀为合法文件&#xff08;.jpg .png .gif&#xf…...

Python数据结构之有序列表

一.基本介绍 在有序列表中&#xff0c;元素的相对位置取决于它们的基本特征。它们通常以升序或者降序排列&#xff0c;并且我们假设元素之间能进行有意义的比较。有序列表和无序列表(链表)的许多操作都是相同的。 二.代码实现 class OrderedList:"""有序列表类…...

LMK04828使用指南-01-简介与引脚功能描述

简介 LMK0482x系列是业界性能最高的时钟调节器&#xff0c;支持JEDEC JESD204B。 PLL2的14个时钟输出可以配置为使用设备和SYSREF时钟驱动七个JESD204B转换器或其他逻辑设备。可以使用直流和交流耦合提供SYSREF。不限于JESD204B应用&#xff0c;14个输出中的每一个都可以单独…...

统计学基本原理

目录 文章目录 目录统计学统计学基本概念描述性统计数据可视化图表工具 汇总统计统计数据的分布情况&#xff1a;中位数、众数、平均值统计数据的离散程度&#xff1a;极差、方差、标准差、离散系数 相关分析Pearson 线性关系相关系数Spearman 单调关系相关系数 回归分析回归模…...

日常真实工作环境,Mysql常用操作命令,笔记!

1、开放增删改查权限&#xff0c;不开放表结构修改权限 有许多生产环境是不需要修改表结构的&#xff0c;也是为了防止SQL注入。 创建用户 mysql> grant all on *.* to ie% identified by test1设置权限 1.首先我们先回收所有权限。 revoke all on *.* from ie% ;2.设…...

洛谷题单3-P1307 [NOIP 2011 普及组] 数字反转-python-流程图重构

题目描述 给定一个整数 N N N&#xff0c;请将该数各个位上数字反转得到一个新数。新数也应满足整数的常见形式&#xff0c;即除非给定的原数为零&#xff0c;否则反转后得到的新数的最高位数字不应为零&#xff08;参见样例 2&#xff09;。 输入格式 一个整数 N N N。 …...

洛谷题单3-P1420 最长连号-python-流程图重构

题目描述 输入长度为 n n n 的一个正整数序列&#xff0c;要求输出序列中最长连号的长度。 连号指在序列中&#xff0c;从小到大的连续自然数。 输入格式 第一行&#xff0c;一个整数 n n n。 第二行&#xff0c; n n n 个整数 a i a_i ai​&#xff0c;之间用空格隔开…...

PostgreSQL:表分区与继承

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…...

【NLP 55、投机采样加速推理】

目录 一、投机采样 二、投机采样改进&#xff1a;美杜莎模型 流程 改进 三、Deepseek的投机采样 流程 Ⅰ、输入文本预处理 Ⅱ、引导模型预测 Ⅲ、候选集筛选&#xff08;可选&#xff09; Ⅳ、主模型验证 Ⅴ、生成输出与循环 骗你的&#xff0c;其实我在意透了 —— 25.4.4 一、…...

CSS 创建与使用学习笔记

一、CSS 的作用 CSS&#xff08;层叠样式表&#xff09;用于控制 HTML 文档的样式和布局。当浏览器读取一个样式表时&#xff0c;它会根据样式表中的规则来格式化 HTML 文档&#xff0c;从而实现页面的美化和布局调整。 二、插入样式表的方法 CSS 可以通过以下三种方式插入到…...

CSS Id 和 Class 选择器学习笔记

一、概述 在 CSS 中&#xff0c;id 和 class 选择器是用于为 HTML 元素指定样式的强大工具。它们可以帮助我们精确地控制页面中元素的样式&#xff0c;让页面设计更加灵活和高效。 二、id 选择器 1. 定义和使用 定义&#xff1a;id 选择器用于为具有特定 id 属性的 HTML 元素…...

从小米汽车事故反思 LabVIEW 开发

近期&#xff0c;小米汽车的一起严重事故引发了社会各界的广泛关注。这起事故不仅让我们对智能汽车的安全性产生了深深的思考&#xff0c;也为 LabVIEW 开发领域带来了诸多值得汲取的知识与领悟。 在智能汽车领域&#xff0c;尤其是涉及到智能驾驶辅助系统时&#xff0c;安全是…...