Elixir实战:8 容错基础知识 (3) 监督者
myzbx 2025-01-21 20:00 15 浏览
监督者是一个通用进程,用于管理系统中其他进程的生命周期。监督者进程可以启动其他进程,这些进程被视为其子进程。通过链接、监视器和退出陷阱,监督者可以检测任何子进程的可能终止,并在需要时重新启动它。
不属于监督者的进程称为工作进程。这些是提供系统实际服务的进程。您当前版本的待办事项系统仅由工作进程组成,例如待办事项缓存和待办事项服务器进程。
如果任何工作进程崩溃,可能是由于一个错误,您系统的某些部分将永远消失。这就是监督者可以提供帮助的地方。通过在监督者下运行工作进程,您可以确保失败的进程被重启,并恢复系统的服务。
要做到这一点,系统中至少需要一个监督进程。在 Elixir 中,可以使用 Supervisor 模块 (https://hexdocs.pm/elixir/Supervisor.xhtml) 来实现。通过调用 Supervisor.start_link/2 ,您可以启动监督进程,随后其工作方式如下:
- 监控进程捕获退出,然后启动子进程。
- 如果在任何时间点,子进程终止,监控进程会收到相应的退出消息并执行纠正措施,例如重启崩溃的进程。
- 如果监督进程终止,它的子进程也会被终止。
启动监督者有两种不同的方法。在基本方法中,您调用函数 Supervisor.start_link ,传递一个描述要在监督者下启动的每个子项的列表,以及一些额外的监督者选项。或者,您可以传递一个定义回调函数的模块,该函数返回这些信息。我们将从基本方法开始,稍后再解释第二种版本。
让我们向待办事项系统介绍一位监督者。图 8.3 回顾了系统中的这些过程:
- Todo.Server —允许多个客户端在单个待办事项列表上协作
- Todo.Cache —维护一个待办服务器的集合,并负责它们的创建和发现
- Todo.DatabaseWorker —对数据库执行读写操作
- Todo.Database —管理数据库工作者池并将数据库请求转发给他们
待办缓存过程是系统的入口点。当您启动缓存时,所有所需的进程都会启动,因此缓存可以被视为系统的根。现在,我们将介绍一个新的监督进程,它将监督待办缓存过程。
8.3.1 准备现有代码
在开始与主管工作之前,您需要对缓存进行一些更改。首先,您将注册缓存进程。这将允许您与该进程进行交互,而无需知道其 PID。
您还需要在启动待办缓存过程时创建一个链接。如果您希望在监视器下运行该过程,这是必需的。为什么监视器使用链接而不是监控?因为链接是双向的,因此监视器的终止意味着它的所有子进程将自动被终止。这反过来允许您正确终止系统的任何部分,而不会留下悬挂的进程。在本章和下一章中,您将看到这如何运作,当您处理更细粒度的监视时。
创建与调用进程的链接就像用 GenServer.start_link 替代 GenServer.start 一样简单。在此过程中,您还可以将相应的 Todo.Cache 接口函数重命名为 start_link 。
最后,您将使 start_link 函数接受一个参数并忽略它。这看起来有些混乱,但这使得启动一个监督过程变得稍微容易一些。原因将在稍后讨论子规范时解释。更改显示在以下列表中。
清单 8.1 待办事项缓存的变化 (supervised_todo_cache/lib/todo/cache.ex)
defmodule Todo.Cache do
use GenServer
def start_link(_) do
GenServer.start_link(__MODULE__, nil, name: __MODULE__)
end
def server_process(todo_list_name) do
GenServer.call(__MODULE__, {:server_process, todo_list_name})
end
def init(_) do
IO.puts("Starting to-do cache.")
...
end
...
end
重命名接口函数
以名称注册并链接到调用进程
使用注册名称的接口功能
调试消息
请注意,您还可以从 init/1 回调中调用 IO.puts/1 以进行调试。此调试表达式包含在所有其他 GenServer 回调模块中( Todo.Database 、 Todo.DatabaseWorker 和 Todo.Server )。
8.3.2 启动监督进程
通过这些更改,您可以立即尝试启动监督进程,待办缓存作为其唯一子进程。将当前文件夹更改为 supervised_todo_cache,并启动 shell ( iex -S mix )。现在,您可以启动监督者:
iex(1)> Supervisor.start_link([Todo.Cache], strategy: :one_for_one)
Starting to-do cache.
Starting database server.
Starting database worker.
Starting database worker.
Starting database worker.
启动一个监督者和待办事项缓存作为其子项
从控制台输出可以看出,调用 Supervisor.start_link/2 导致待办事项缓存开始。缓存进程随后启动了数据库进程。
让我们仔细看看 Supervisor.start_link/2 的调用:
Supervisor.start_link(
[Todo.Cache],
strategy: :one_for_one
)
子规格列表
监督者策略
如函数名称所示, Supervisor.start_link/2 启动一个监督进程并将其链接到调用者。
第一个参数是所需子项的列表。更准确地说,这个列表的每个元素都是一个子项规范,描述了如何启动和管理子项。我们稍后会详细讨论子项规范。在这种简单形式中,提供的子项规范是一个模块名称。在这种情况下,子项由 Todo.Cache 模块中的某个回调函数描述。
当监督进程启动时,它将遍历此列表并根据规范启动每个子进程。在此示例中,监督将调用 Todo.Cache.start_link/1 。一旦所有子进程启动, Supervisor.start_link/2 将返回 {:ok, supervisor_pid} 。
Supervisor.start_link/2 的第二个参数是特定于监督者的选项列表。 :strategy 选项,也称为重启策略,是必需的。此选项指定监督者应如何处理其子进程的终止。 one_ for_one 策略表示如果一个子进程终止,则应启动另一个子进程来替代它。还有其他几种策略(例如,“如果单个子进程崩溃,则重启所有子进程”),我们将在第 9 章中讨论它们。
注意 这里的重启一词使用得比较随意。从技术上讲,进程无法被重启。它只能被终止;然后,可以在其位置启动另一个由同一模块驱动的进程。新进程具有不同的 PID,并且与旧进程不共享任何状态。
无论如何,在 Supervisor.start_link/2 返回后,系统中的所有必需进程都在运行,您可以与系统进行交互。例如,您可以启动一个待办事项服务器:
iex(2)> bobs_list = Todo.Cache.server_process("Bob's list")
Starting to-do server for Bob's list.
#PID<0.161.0>
缓存进程作为监督进程的子进程启动,因此我们说它是被监督的。这意味着如果缓存进程崩溃,它的监督者将重新启动它。
您可以通过引发缓存进程的崩溃快速验证这一点。首先,您需要获取缓存的 PID。如前所述,缓存现在以一个名称(它自己的模块名称)注册,因此可以借助 Process.whereis/1 轻松获取其 PID:
iex(3)> cache_pid = Process.whereis(Todo.Cache)
#PID<0.155.0>
现在,您可以使用 Process.exit/2 函数终止进程,该函数接受一个 PID 和退出原因,然后向给定进程发送相应的退出信号。退出原因可以是任意术语。在这里,您将使用原子 :kill ,它以特殊方式处理。退出原因 :kill 确保目标进程被无条件终止,即使该进程正在捕获退出。让我们看看它的实际效果:
iex(4)> Process.exit(cache_pid, :kill)
Starting to-do cache.
如您从输出中所见,过程立即重新启动。您还可以证明待办缓存现在是一个具有不同 PID 的进程:
iex(5)> Process.whereis(Todo.Cache)
#PID<0.164.0>
您可以像使用旧流程一样使用新流程:
iex(6)> bobs_list = Todo.Cache.server_process("Bob's list")
Starting to-do server for Bob's list.
#PID<0.167.0>
这个简短的实验证明了一些基本的容错能力。在崩溃之后,系统自我修复并恢复了完整的服务。
名称允许流程发现
重要的是要解释为什么将待办事项缓存注册为本地名称。您应该始终记住,要与进程进行通信,您需要拥有其 PID。在第 7 章中,您使用了一种简单的方法,创建了一个进程,然后传递其 PID。这在您进入监控者领域之前是可以的。
问题在于,受监督的进程可以被重启。请记住,重启归结为用一个新进程替代旧进程——新进程有一个不同的 PID。这意味着对崩溃进程的 PID 的任何引用都变得无效,标识了一个不存在的进程。
这就是注册名称重要的原因。它们提供了一种可靠的方式来查找进程并与之交互,无论可能的进程重启。
8.3.3 子规范
要管理子进程,监督者需要一些信息,例如以下问题的答案:
- 孩子应该如何开始?
- 如果孩子终止了该怎么办?
- 应该使用什么术语来唯一标识每个孩子?
这些信息统称为子规范。回想一下,当调用 Supervisor.start_link/2 时,您发送了一份子规范列表。在其基本形态中,规范是一个映射,包含几个字段来配置子项的属性。
例如,待办事项缓存的规范可能如下所示:
%{
id: Todo.Cache,
start: {Todo.Cache, :start_link, [nil]},
}
子项的 ID
启动规范
:id 字段是一个任意术语,用于区分该子项与同一主管的其他子项。
:start 字段是形状为 {module, start_function, list_of_ arguments} 的三元组。在启动子进程时,通用监督代码将使用 apply(module, start_function, list_of_arguments) 来调用由此元组描述的函数。被调用的函数必须启动并链接该进程。
您可以省略规范中的一些其他字段,在这种情况下,将选择一些合理的默认值。我们将在第 9 章稍后讨论其中的一些。您还可以参考官方文档 https://hexdocs.pm/elixir/Supervisor.xhtml#module-child-specification 以获取更多详细信息。
无论如何,您可以将规格图直接传递给 Supervisor.start_link 。以下是一个示例:
Supervisor.start_link(
[
%{
id: Todo.Cache,
start: {Todo.Cache, :start_link, [nil]}
}
],
strategy: :one_for_one
)
这将指示主管调用 Todo.Cache.start_link(nil) 来启动子进程。请记住,您已将 Todo.Cache.start_link 更改为接受一个参数(该参数被忽略),因此您需要传递某个值(在此示例中为 nil )。
这种方法的一个问题是容易出错。如果缓存的实现发生变化,例如启动函数的签名,您需要记住在启动监控程序的代码中调整规范。
为了解决这个问题, Supervisor 允许您在子规范列表中传递一个元组 {module_name, arg} 。在这种情况下, Supervisor 将首先调用 module_name .child_spec(arg) 以获取实际的规范。此函数必须返回规范映射。然后,监督者根据返回的规范启动子进程。
Todo.Cache 模块已经定义了 child_spec/1 ,即使您没有自己编写。默认实现是由 use GenServer 注入的。因此,您也可以以以下方式启动监督者:
Supervisor.start_link(
[{Todo.Cache, nil}],
strategy: :one_for_one
)
因此, Supervisor 将调用 Todo.Cache.child_spec(nil) 并根据返回的规范启动子进程。验证注入的 child_spec/1 实现返回的内容很简单:
iex(1)> Todo.Cache.child_spec(nil)
%{id: Todo.Cache, start: {Todo.Cache, :start_link, [nil]}}
换句话说,生成的 child_spec/1 返回一个规范,该规范调用模块的 start_link/1 函数,并将参数传递给 child_spec/1 。这正是你让 Todo.Cache.start_link 接受一个参数的原因,尽管该参数被忽略:
defmodule Todo.Cache do
use GenServer
def start_link(_) do
...
end
...
end
生成默认的 child_spec/1
符合默认的 child_spec/1
通过这样做,您使 Todo.Cache 与生成的 child_spec/1 兼容,这意味着您可以将 Todo.Cache 包含在子项列表中,而无需进行任何额外的工作。
如果您不喜欢这种方法,您可以向 use GenServer 提供一些选项,以调整生成的 child_spec/1 的输出。有关更多详细信息,请参阅官方文档(https://hexdocs.pm/elixir/GenServer.xhtml#module-how-to-supervise)。如果您需要更多控制,您可以自己定义 child_spec/1 ,这将覆盖默认实现。
最后,如果您不关心传递给 child_spec/1 的参数,您可以在子规范列表中仅包含模块名称。在这种情况下, Supervisor 将向 child_spec/1 传递空列表 [] 。因此,您也可以像这样启动 Todo.Cache :
Supervisor.start_link(
[Todo.Cache],
strategy: :one_for_one
)
在进一步之前,让我们回顾一下监督者启动的工作原理。当你调用 Supervisor.start_link(child_specs, options) 时,以下情况发生:
- 新过程已启动,由 Supervisor 模块提供动力。
- 监督进程逐一遍历子规范列表,并启动每个子进程。
- 每个规范在需要时通过调用相应模块中的 child_spec/1 来解决。
- 监督者根据子规范的 :start 字段启动子进程。
8.3.4 包装监督者
到目前为止,您已经在 shell 中与 supervisor 进行了交互。但在实际应用中,您会希望在代码中使用 supervisor。就像使用 GenServer 一样,建议将 Supervisor 包装在一个模块中。
以下列表实现了您第一个监督者的模块。
清单 8.2 待办事项系统监督者 (supervised_todo_cache/lib/todo/system.ex)
defmodule Todo.System do
def start_link do
Supervisor.start_link(
[Todo.Cache],
strategy: :one_for_one
)
end
end
通过这个简单的添加,启动整个系统变得容易:
$ iex -S mix
iex(1)> Todo.System.start_link()
Starting to-do cache.
Starting database server.
Starting database worker.
Starting database worker.
Starting database worker.
名称 Todo.System 被选用来描述模块的目的。通过调用 Todo.System.start_link() ,您可以启动整个待办事项系统,包含所有必需的服务,如缓存和数据库。
8.3.5 使用回调模块
另一种启动监督者的方法是提供回调模块。这的工作方式类似于 GenServer 。您需要开发一个必须实现 init/1 函数的模块。该函数必须返回子规范的列表和其他监督者选项,例如其策略。
这里是您如何重写 Todo.System 以使用这种方法:
defmodule Todo.System do
use Supervisor
def start_link do
Supervisor.start_link(__MODULE__, nil)
end
def init(_) do
Supervisor.init([Todo.Cache], strategy: :one_for_one)
end
end
包含一些常见的模板内容
使用 Todo.System 作为回调模块启动监督者
实现所需的回调函数
与 GenServer 一样,您从 use Supervisor 开始,以便在您的模块中获得一些通用的模板代码。
关键部分发生在您调用 Supervisor.start_link/2 时。您现在传递的是回调模块,而不是子规范的列表。在这种情况下,监督进程将调用 init/1 函数以提供监督规范。传递给 init/1 的参数是您传递给 Supervisor .start_link/2 的第二个参数。最后,在 init/1 中,您借助 Supervisor .init/2 函数描述监督者,传递给它子项列表和监督者选项。
前面的代码是 Supervisor.start_ link([Todo.Cache], strategy: :one_for_one) 的更复杂的等效形式。显然,您需要更多的代码行才能获得相同的效果。好的一面是,这种方法给您更多的控制。例如,如果您需要在启动子进程之前进行一些额外的初始化,您可以在 init/1 中做到。此外,回调模块在热代码重载方面更灵活,允许您修改子进程列表而无需重新启动整个监视器。
在大多数情况下,直接传递子规格列表的简单方法就足够了。此外,正如您在前面的示例中看到的,如果将 Supervisor 的使用封装在一个专用模块中,切换不同的方法就很容易。因此,在本书中,您将仅使用简单方法,而不使用回调模块。
8.3.6 连接所有过程
在这一点上,您正在监督待办缓存过程,因此您获得了一些基本的容错能力。如果缓存过程崩溃,将启动一个新过程,系统可以恢复提供服务。
然而,您当前实现中存在一个问题。当主管重新启动待办事项缓存时,您将获得一个完全独立的进程层次结构,并且会有一组与之前的待办事项服务器毫无关系的新待办事项服务器。之前的待办事项服务器将成为未使用的垃圾,仍在运行并消耗内存和 CPU 资源。
让我们演示这个问题。首先,启动系统并请求一个待办事项服务器:
iex(1)> Todo.System.start_link()
iex(2)> Todo.Cache.server_process("Bob's list")
Starting to-do server for Bob's list.
#PID<0.159.0>
缓存的待办服务器在后续请求中未启动:
iex(3)> Todo.Cache.server_process("Bob's list")
#PID<0.159.0>
检查正在运行的进程数量:
iex(4)> length(Process.list())
71
现在,终止待办缓存:
iex(5)> Process.exit(Process.whereis(Todo.Cache), :kill)
Starting to-do cache.
最后,请求一个待办事项服务器用于鲍勃的列表:
iex(6)> Todo.Cache.server_process("Bob's list")
Starting to-do server for Bob's list.
#PID<0.165.0>
如您所见,在您重启待办事项缓存后,检索先前获取的服务器会创建一个新进程。这并不令人惊讶,因为您终止了先前的缓存进程,这也销毁了进程状态。
当一个进程终止时,它的状态被释放,新进程以全新的状态开始。如果你想保留状态,必须自己处理;我们将在第 9 章讨论这个问题。
在缓存过程重新启动后,您将拥有一个完全新的进程,它对之前缓存的内容没有任何概念。同时,您的旧缓存结构(待处理服务器)并没有被清理。您可以通过重新检查正在运行的进程数量来看到这一点:
iex(7)> length(Process.list())
72
您有一个额外的进程,即之前为 Bob 的待办事项列表启动的待办服务器。这显然不好。终止待办缓存会破坏其状态,因此您还应该关闭所有现有的待办服务器。这样,您可以确保正确的进程终止。
要做到这一点,您必须在进程之间建立链接。每个待办事项服务器必须与缓存相连。进一步来说,您还需要将数据库服务器与待办事项缓存连接,并将数据库工作者与数据库服务器连接。这将有效确保整个结构相互链接,如图 8.4 所示。
通过链接一组相互依赖的过程,您可以确保一个过程的崩溃也会导致其依赖项崩溃。无论哪个过程崩溃,链接都确保整个结构被终止。由于这将导致缓存过程的终止,监控者会注意到这一点,并会启动一个新的系统。
通过这种方法,您可以检测系统中任何部分的错误并从中恢复,而不会留下悬挂的进程。缺点是,您允许错误产生广泛的影响。单个数据库工作者或待办服务器中的错误将导致整个结构崩溃。这远非完美,您将在第 9 章中进行改进。
目前,让我们坚持这种简单的方法并实现所需的代码。在您当前的系统中,您有一个待办事项监督者,它启动并监督缓存。您必须确保缓存与所有其他工作进程直接或间接相连。
更改很简单。您只需将项目中所有流程的 start 切换为 start_link 。在相应的模块中,您当前有如下内容:
def start(...) do
GenServer.start(...)
end
此代码片段必须转换为以下内容:
def start_link(...) do
GenServer.start_link(...)
end
当然,每个 module.start 调用必须替换为 module .start_link 。这些更改是机械的,代码在这里没有呈现。完整的解决方案位于 todo_links 文件夹中。
让我们看看新系统是如何工作的:
iex(1)> Todo.System.start_link()
iex(2)> Todo.Cache.server_process("Bob's list")
Starting to-do server for Bob's list.
iex(3)> length(Process.list())
71
iex(4)> Process.exit(Process.whereis(Todo.Cache), :kill)
iex(5)> bobs_list = Todo.Cache.server_process("Bob's list")
Starting to-do server for Bob's list.
iex(6)> length(Process.list())
71
终止整个过程结构
进程计数保持不变。
当您崩溃一个进程时,整个结构会被终止,并且一个新的进程会在其位置启动。链接确保相关的进程也被终止,从而保持系统的一致性。
8.3.7 重启频率
重要的是要记住,监督者不会无限期地重启子进程。监督者依赖于最大重启频率,该频率定义了在给定时间段内允许多少次重启。默认情况下,最大重启频率为 5 秒内 3 次重启。您可以通过将 :max_restarts 和 :max_seconds 选项传递给 Supervisor.start_link/2 来更改这些参数。如果超过此频率,监督者将放弃并终止自己及其所有子进程。
让我们在 shell 中验证这一点。首先,启动 supervisor:
iex(1)> Todo.System.start_link()
Starting the to-do cache.
现在,您需要频繁重启待办事项缓存进程:
iex(1)> for _ <- 1..4 do
Process.exit(Process.whereis(Todo.Cache), :kill)
Process.sleep(200)
end
在这里,您终止缓存进程并短暂休眠,允许主管重新启动该进程。这一过程重复四次,这意味着在最后一次迭代中,您将超过默认的最大重启频率(5 秒内三次重启)。
这里是输出:
Starting the to-do cache.
Starting database server.
...
** (EXIT from #PID<0.149.0>) :shutdown
重复三次
主管终止。
在超过最大重启频率后,监控程序放弃并终止,同时也关闭了子进程。
您可能会想知道这个机制的原因。当系统中的一个关键进程崩溃时,它的监控者会尝试通过启动一个新进程将其重新上线。如果这没有帮助,那么无限重启就没有意义。如果在给定的时间间隔内发生了太多次重启,很明显问题无法解决。在这种情况下,监控者能做的唯一明智的事情就是放弃并终止自己,这也会终止它的所有子进程。
该机制在所谓的监督树中发挥着重要作用,在这些树中,监督者和工作人员被组织在更深的层次结构中,这使您能够控制系统如何从错误中恢复。下一章将对此进行详细解释,您将在其中构建一个细粒度的监督树。
摘要
- 运行时错误有三种类型:抛出、错误和退出。
- 当运行时错误发生时,执行会向上移动到相应的 try 块。如果错误未被处理,进程将崩溃。
- 可以在另一个进程中检测到进程终止。为此,您可以使用链接或监视器。
- 链接是双向的——任一进程的崩溃都会传播到另一个进程。
- 默认情况下,当一个进程异常终止时,所有与之链接的进程也会终止。通过捕获退出,您可以对链接进程的崩溃做出反应并采取相应措施。
- 监督者是一个管理其他进程生命周期的进程。它可以启动、监督和重启崩溃的进程。
- Supervisor 模块用于启动监督者并与之协作。
- 一个监督者由子规范列表和监督策略定义。您可以将这些作为参数提供给 Supervisor.start_link/2 ,或者您可以实现一个回调模块。
相关推荐
- Oculus虚拟控制器细节曝光:可实现游戏控制
-
最近,Oculus发布了OculusRift的开发者指南与Oculus的0.7版本SDK开发开发工具,从中我们看到了关于OculusTouch虚拟现实游戏控制器与虚拟现实头盔配合使用的更多细节。O...
- 58年游戏控制器进化史 1分钟视频科普醒脑
-
今日,油管频道SuperDeluxe发布了一段手柄发展史特制视频,从1958年电子游戏鼻祖《TENNISFORTWO》控制器开始,到随后较为知名的「Pong」与「FC」,很多产品是具有相似结构和...
- 真正用手玩游戏 对应Valve Index的SteamVR手套控制器预购
-
尽管不温不火,不过SteamVR游戏也有不少忠实玩家,日前一款支持ValveIndex的SteamVR手套控制器RealityXRGameGlove公开预购,一起来了解下。·RealityX...
- 499元起,八位堂全按键专业街机控制器“天刃星”发售
-
IT之家5月18日消息,八位堂昨日在国内发售了新款全按键专业街机控制器“天刃星”,可选复古版、Xbox版,定价分别为499元和599元。两款控制器均为电子竞技和街机游戏的职业玩家设计...
- 八位堂发布两款Fightingdeck控制器:专为格斗、射击类游戏打造
-
IT之家5月17日消息,八位堂日前宣布,将于2025年7月15日推出两款全新的Fightingdeck街机控制器:一款定价89.99美元(IT之家注:现汇率约合649元...
- HORI《太鼓达人》游戏专用控制器11月6日首销,478元
-
IT之家11月1日消息,HORI索尼授权《太鼓达人》专用游戏控制器“太鼓”现已上架京东,将于11月6日开售,这款“太鼓”支持PS5/PS4/PC使用,相比Switch...
- 修改手机通知栏,居然能玩游戏!
-
在之前的内容中,技能君给大家分享过不少关于手机通知栏美化的小技巧,然而,这些胡里花俏的技能,全都是仅支持安卓手机的。所以,今天技能君就给iPhone的小伙伴们带来一款仅支持iPhone的通知栏美化小组...
- 用完支付宝、微博和淘宝的小组件,我发现我回不去了
-
iOS14的更新之后,最开心的就是我们这群被苹果封闭系统“禁欲”了很多年的苹果用户了。仿佛一瞬间所有人的iPhone都成为了一款“变装”游戏,各种花里胡哨的图标,好看的小组件一...
- 《使命召唤17:黑色行动冷战》追加3v3狙击战模式
-
《使命召唤17:黑色行动冷战》周二进行了更新中,追加了一个在《使命召唤16:现代战争》中首次普及的游戏模式——3V3狙击游戏。它通过限定玩家使用狙击步枪来改变常规的枪战模式。TreyarchStud...
- Unity常用组件整理(一)
-
【1】变换Transform为物体提供最基本的位置、旋转、缩放等属性,而且“父子关系”也由它管理(每个物体有且只有一个)【2】光源Light为场景提供各种光源。大部分材质都需要有光源照射才能正...
- HarmonyOS新增300多个开源组件,涉游戏、音视频等8大类
-
据“HarmonyOS开发者”微信公众号7月28日消息,自第一批HarmonyOS400+组件开源后,本次新增300多个开源组件,共涉及8大类(工具、网络、文件数据、UI、框架、动画图形、音视频、游...
- 《我的世界》狂欢节强势来袭 惊喜折扣福利送不停
-
亲爱的冒险家,双11即将到来,我们也迫不及待为大家带来了好消息~“MC狂欢节”强势来袭!方块世界有惊喜折扣等着你哦~11月6日到11月11日,《我的世界》将携手知名KOL与开发者在虎牙直播欢乐开播,...
- 游戏开发需要学什么
-
游戏开发可以说是当下最热门的专业了,不过这一行业虽然很火热,但也有一定的竞争压力。这个行业需要大量的人才,而且游戏开发这个行业目前还处于初级阶段,所以发展空间还是很大的。现在有很多人都想进入这个行业,...
- 昆仑万维开源Matrix-Game大模型 可据指令生成相应游戏世界视频
-
【太平洋科技快讯】5月13日,昆仑万维宣布,旗下Matrix系列的交互式世界生成方向正式落地,推出Matrix-Game大模型(17B),并开源其核心代码。Matrix-Game是工业界首个...
- 游戏帧数低硬件不背锅 如何完善游戏运行环境?
-
众所周知,在玩游戏的过程中,电脑配置对游戏的体验影响是巨大的,不同的硬件配置会对游戏的运行效果产生千差万别的效果。但是除了硬件条件以外,运行环境对游戏的运行效果也是有着千丝万缕的影响。想要获得一个完美...
- 一周热门
- 最近发表
- 标签列表
-
- HTML 简介 (30)
- HTML 响应式设计 (31)
- HTML URL 编码 (32)
- HTML Web 服务器 (31)
- HTML 表单属性 (32)
- HTML 音频 (31)
- HTML5 支持 (33)
- HTML API (36)
- HTML 总结 (32)
- HTML 全局属性 (32)
- HTML 事件 (31)
- HTML 画布 (32)
- HTTP 方法 (30)
- 键盘快捷键 (30)
- CSS 语法 (35)
- CSS 选择器 (30)
- CSS 轮廓宽度 (31)
- CSS 谷歌字体 (33)
- CSS 链接 (31)
- CSS 定位 (31)
- CSS 图片库 (32)
- CSS 图像精灵 (31)
- SVG 文本 (32)
- 时钟启动 (33)
- HTML 游戏 (34)