游戏引擎学习第203天

news2025/4/5 18:07:38

回顾当前情况

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

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

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

之前提到,如果你使用栈进行反转,那么你会得到宽度优先遍历(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之类的,它们可能会包含一些简单的脚本语言,允许没有编程经验的人开始进行实验。这种工具通常会有简单的调试功能,帮助学习者快速理解代码如何运行。

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2328732.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习数据集划分比例多少合适

在机器学习和深度学习中,测试集的划分比例需要根据数据量、任务类型和领域需求灵活调整。 1. 常规划分比例 通用场景 训练集 : 验证集 : 测试集 60% : 20% : 20% 适用于大多数中等规模数据集(如数万到数十万样本),平衡了训练数…

CExercise_1_5 水仙花数

题目: 经典循环案例:请求出所有的水仙花数,并统计总共有几个。 所谓的水仙花数是指一个三位数,其各位数字的立方和等于该数本身。 举例:153就是一个水仙花数,153 1 * 1 * 1 5 * 5 * 5 3 * 3 * 3 1 125…

哈密尔顿路径(Hamiltonian Path)及相关算法题目

哈密尔顿路径要求访问图中每个顶点恰好一次,通常用于解决旅行商问题(TSP)或状态压缩DP问题。 哈密尔顿路径(Hamiltonian Path)是指在一个图中经过每个顶点恰好一次的路径。如果这条路径的起点和终点相同(即…

MINIQMT学习课程Day10

开始获取股票数据课程的学习: 获取qmt账号的持仓情况后,我们进入下一步,如何获得当前账号的委托状况 还是之前的步骤,打开qmt,选择独立交易, 之后使用pycharm,编写py文件 导入包&#xff1a…

JAVA实战开源项目:智慧图书管理系统(Vue+SpringBoot) 附源码

本文项目编号 T 152 ,文末自助获取源码 \color{red}{T152,文末自助获取源码} T152,文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…

Linux 系统管理综合实训 —— 基于 NAT 模式的多 IP 配置、Nginx 服务部署及存储管理

1. 虚拟机网络配置:NAT模式与多IP地址设置 将你的虚拟机的网卡模式设置为nat模式,给虚拟机网卡配置三个主机位分别为100、200、168的ip地址 设置静态IP [rootlocalhost ~]# nmcli c modify ens160 ipv4.method manual ipv4.addresses 192.168.2.100/2…

如何在windows 环境、且没有显卡的情况下用python跑通从ModelScope下载的大模型的调用

文章目录 背景介绍源代码:安装调试过程1.设置第三方镜像源2.预先安装:3.在python中创建代码:4.最终修改程序,将device_map从“cuda”改成“auto”,大模型调用1.5B(1___5B)的5.最终跑出结果解释:示例&#x…

黑马点评redis改 part 1

本篇将主要阐述短信登录的相关知识,感谢黑马程序员开源,感谢提供初始源文件(给到的是实战第7集开始的代码)【Redis实战篇】黑马点评学习笔记(16万字超详细、Redis实战项目学习必看、欢迎点赞⭐收藏)-CSDN博…

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

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

第三期:深入理解 Spring Web MVC [特殊字符](数据传参+ 特殊字符处理 + 编码问题解析)

✨前言:传参和状态管理,看似简单其实门道不少 在 Web 开发中,前端和后端最核心的交流方式就是“传参”,而“传参”除了涉及如何写代码获取参数,还藏着很多开发者容易忽略的细节: 为什么 URL 带了中文&…

Everything 安装教程与使用教程(附安装包)

文章目录 前言一、Everything 介绍二、Everything 安装教程1.Everything 安装包下载2.选择安装文件3.选择安装语言4.接受许可协议5.选择安装位置6.配置安装选项7.完成安装 三、Everything 使用教程1.启动软件2.简单关键词搜索3.按类型搜索 前言 在日常使用电脑时,随…

SQL语句(三)—— DQL

目录 基本语法 一、基础查询 1、查询多个字段 2、字段设置别名 3、去除重复记录 4、示例代码 二、条件查询 1、语法 2、条件列表常用的运算符 3、示例代码 三、分组查询 (一)聚合函数 1、介绍 2、常见的聚合函数 3、语法 4、示例代码 &…

Opencv计算机视觉编程攻略-第九节 描述和匹配兴趣点

一般而言,如果一个物体在一幅图像中被检测到关键点,那么同一个物体在其他图像中也会检测到同一个关键点。图像匹配是关键点的常用功能之一,它的作用包括关联同一场景的两幅图像、检测图像中事物的发生地点等等。 1.局部模板匹配 凭单个像素就…

汇编学习之《push , pop指令》

学习本章前线了解ESP, EBP 指令 汇编学习之《指针寄存器&大小端学习》-CSDN博客 栈的特点: 好比一个垂直容器,可以陆续放入物体,但是先放的物体通常会被后面放的物体压着,只有等上面后放的物品拿出来后,才能…

Python循环控制语句

1. 循环类型概述 Python提供两种主要的循环结构&#xff1a; while循环 - 在条件为真时重复执行for循环 - 遍历序列中的元素 2. while循环 基本语法 while 条件表达式:循环体代码示例 count 0 while count < 5:print(f"这是第{count1}次循环")count 13. f…

微信小程序(下)

目录 在事件处理函数中为 data 中的数据赋值 事件传参 bindinput 的语法格式 实现文本框和 data 之间的数据同步 条件渲染 结合 使用 wx:if hidden wx:if与 hidden 的对比 wx:for 手动指定索引和当前项的变量名 wx:key 的使用 WXSS 和 CSS 的关系 什么是 rpx 尺寸…

【零基础入门unity游戏开发——2D篇】2D 游戏场景地形编辑器——TileMap的使用介绍

考虑到每个人基础可能不一样&#xff0c;且并不是所有人都有同时做2D、3D开发的需求&#xff0c;所以我把 【零基础入门unity游戏开发】 分为成了C#篇、unity通用篇、unity3D篇、unity2D篇。 【C#篇】&#xff1a;主要讲解C#的基础语法&#xff0c;包括变量、数据类型、运算符、…

vector的介绍与代码演示

由于以后我们写OJ题时会经常使用到vector&#xff0c;所以我们必不可缺的是熟悉它的各个接口。来为我们未来作铺垫。 首先&#xff0c;我们了解一下&#xff1a; https://cplusplus.com/reference/vector/ vector的概念&#xff1a; 1. vector是表示可变大小数组的序列容器…

ubuntu 22.04 解决LXC 报错CGroupV1 host system

解决CGroupV1 host system 报错 echo "cgroupv1 environment" sed -i s/^GRUB_CMDLINE_LINUX.*/GRUB_CMDLINE_LINUX_DEFAULT"quiet splash systemd.unified_cgroup_hierarchy0" / /etc/default/grub update-grub reboot 下载oracle 7 Linux 容器测试 l…