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

Renderer 使用材质分析:materials、sharedMaterials 及 MaterialPropertyBlock

一、materials 与 sharedMaterials

1.1 使用上的区别与差异

Unity 开发时,在 C# 中通过 Renderer 取材质操作是非常常见的操作,Renderer 有两种常规获取材质的方式:

  1. sharedMaterials:可以理解这个就是原始材质,所有使用了同一个材质资源的模型 renderer, sharedMaterial 相同,修改了 sharedMaterials 相当于就是修改了资源

  2. materials:material 这个相当于 material Instance,比如同一个箱子模型实例化两个 renderer, sharedMaterial 相同,这时候你想让其中一个箱子是红色的,另一个箱子是绿色的,这时候就可以使用 material,clone 不同的材质实例来做表现的差异化

当然,除了 clone material instance 来做表现差异化,Unity 更希望你用 MaterialPropertyBlock,这个才是做材质表现差异化的正确路子

先不提 MaterialPropertyBlock,我们拉回到 sharedMaterial,material,有带 s 的和不带 s 的,这个对应美术同学制作过程中的多维子材质的概念:也就是一个完整几何体是可以有多个材质,虽然渲染次数还是多次,但完整的几何体天然的解决缝合问题,并且如果相邻渲染,几何体可以不用重复传入,性能好一些,所以不管 sharedMaterials 还是 materials,都是数组的形式存储,对于没有 s 的,其实就是取数组的第一个元素,因为大多数情况数组里还是仅仅有一个元素

综上所述,我们脑海中的 Renderer 内部是什么样子的,一定有个 sharedMaterial Array 和 material Array,当 material Array 存在的时候,就用其来渲染,否则用 sharedMaterial Array 来渲染,这样我使用了 material,后边的逻辑想舍弃它继续用 sharedMaterial,只需置空 material Array 即可,真实的 Unity 是这样的规则吗?直接上 Unity 源码

1.2 Unity 源码分析

[NativeHeader("Runtime/Graphics/Renderer.h")]
public partial class Renderer : Component
{[FreeFunction(Name = "RendererScripting::GetMaterial", HasExplicitThis = true)] extern private Material GetMaterial();[FreeFunction(Name = "RendererScripting::GetSharedMaterial", HasExplicitThis = true)] extern private Material GetSharedMaterial();[FreeFunction(Name = "RendererScripting::SetMaterial", HasExplicitThis = true)] extern private void SetMaterial(Material m);[FreeFunction(Name = "RendererScripting::GetMaterialArray", HasExplicitThis = true)] extern private Material[] GetMaterialArray();[FreeFunction(Name = "RendererScripting::GetMaterialArray", HasExplicitThis = true)] extern private void CopyMaterialArray([Out] Material[] m);[FreeFunction(Name = "RendererScripting::GetSharedMaterialArray", HasExplicitThis = true)] extern private void CopySharedMaterialArray([Out] Material[] m);[FreeFunction(Name = "RendererScripting::SetMaterialArray", HasExplicitThis = true)] extern private void SetMaterialArray([NotNull] Material[] m);public Material[] materials{get{#if UNITY_EDITORif (IsPersistent()){Debug.LogError("Not allowed to access Renderer.materials on prefab object. Use Renderer.sharedMaterials instead", this);return null;}#endifreturn GetMaterialArray();}set { SetMaterialArray(value); }}public Material material{get{#if UNITY_EDITORif (IsPersistent()){Debug.LogError("Not allowed to access Renderer.material on prefab object. Use Renderer.sharedMaterial instead", this);return null;}#endifreturn GetMaterial();}set { SetMaterial(value); }}public Material sharedMaterial { get { return GetSharedMaterial(); } set { SetMaterial(value); } }public Material[] sharedMaterials { get { return GetSharedMaterialArray(); } set { SetMaterialArray(value); } }}
namespace RendererScripting
{Material* GetMaterial(Renderer* r);Material* GetSharedMaterial(Renderer* r);void      SetMaterial(Renderer* r, Material* m);dynamic_array<Material*> GetMaterialArray(Renderer* r);void GetMaterialArray(Renderer* r, dynamic_array<Material*>& mat);void GetSharedMaterialArray(Renderer* r, dynamic_array<PPtr<Material> >& mat);void SetMaterialArray(Renderer* r, const dynamic_array<Material*>& ma);}
 Material* RendererScripting::GetMaterial(Renderer* r)
{
#if UNITY_EDITORDebugAssert(!r->IsPersistent());
#endifreturn r->GetAndAssignInstantiatedMaterial(0, false);
}Material* RendererScripting::GetSharedMaterial(Renderer* r)
{return r->GetMaterialCount() ? r->GetMaterial(0) : 0;
}void RendererScripting::SetMaterial(Renderer* r, Material* m)
{r->SetMaterialCount(std::max(1, r->GetMaterialCount()));r->SetMaterial(m, 0);
}void RendererScripting::GetMaterialArray(Renderer* r, dynamic_array<Material*>& mat)
{
#if UNITY_EDITORDebugAssert(!r->IsPersistent());
#endifDebugAssert(r->GetMaterialCount() <= mat.size());for (int i = 0, in = r->GetMaterialCount(); i < in; ++i)mat[i] = r->GetAndAssignInstantiatedMaterial(i, false);
}void RendererScripting::GetSharedMaterialArray(Renderer* r, dynamic_array<PPtr<Material> >& mat)
{DebugAssert(r->GetMaterialCount() <= mat.size());for (int i = 0, in = r->GetMaterialCount(); i < in; ++i)mat[i] = r->GetMaterialArray()[i];
}dynamic_array<Material*> RendererScripting::GetMaterialArray(Renderer* r)
{dynamic_array<Material*> ret(r->GetMaterialCount(), kMemDynamicArray);RendererScripting::GetMaterialArray(r, ret);return ret;
}void RendererScripting::SetMaterialArray(Renderer* r, const dynamic_array<Material*>& ma)
{r->SetMaterialCount(ma.size());for (int i = 0, in = ma.size(); i < in; ++i)r->SetMaterial(ma[i], i);
}

上述代码仅仅截取了 Renderer 中 sharedMaterial 和 material 的源代码调用部分,其他部分暂时省去,先从源代码的 get 部分分析,可以发现:

  • material 的 get 最终调用来自于 Renderer::GetAndAssignInstantiatedMaterial 函数

  • sharedMaterial 的 get 最终调用来自于 Renderer::GetMaterial 函数

virtual PPtr<Material> Renderer::GetMaterial(int i) const override 
{ return m_Materials[i]; 
}
void Renderer::SetMaterial(PPtr<Material> material, int index)
{Assert(index < (int)m_Materials.size());m_Materials[index] = material;/*#if !DEPLOY_OPTIMIZEDMaterial* materialPtr = material;if (materialPtr && materialPtr->GetOwner ().GetInstanceID () != 0 && materialPtr->GetOwner() != PPtr<Object> (this)){ErrorString("Assigning an instantiated material is not a good idea. Since the material is owned by another game object, it will be destroyed when the game object is destroyed.\nYou probably want to explicitly instantiate the material.");}#endif*/SetDirty();
}
Material* Renderer::GetAndAssignInstantiatedMaterial(int i, bool allowFromEditMode)
{// Grab shared materialMaterial* material = NULL;if (GetMaterialCount() > i)material = GetMaterial(i);// instantiate material if necessaryMaterial* instantiated = &Material::GetInstantiatedMaterial(material, *this, allowFromEditMode);// Assign materialif (material != instantiated){SetMaterialCount(std::max(GetMaterialCount(), i + 1));SetMaterial(instantiated, i);}return instantiated;
}

从 Renderer 的源码分析,内部仅仅有一个 material 数组,而不是两个,这个和我们上述脑海中浮现的数据结构已经不一样了,很有意思,倒是要看看他到底怎么做的:因此到这继续深度分析,抛开诸多假象来看实际的本质

class EXPORT_COREMODULE Renderer : public Unity::Component, public BaseRenderer
{ ...........typedef dynamic_array<PPtr<Material> > MaterialArray;MaterialArray       m_Materials;    ///< List of materials to use when rendering.........
}

看上边 Renderer::GetAndAssignInstantiatedMaterial 的实现,发现其主要调用了 Material::GetInstantiatedMaterial 来实现的材质克隆,再粘一下代码

Material& Material::GetInstantiatedMaterial(Material* material, Object& renderer, bool allowInEditMode)
{if (material == NULL)material = GetDefaultMaterial();if (material->m_Owner == PPtr<Object>(&renderer))return *material;else{if (!allowInEditMode && !IsWorldPlaying())ErrorStringObject("Instantiating material due to calling renderer.material during edit mode. This will leak materials into the scene. You most likely want to use renderer.sharedMaterial instead.", &renderer);// Make sure the properties are initialized before we're cloning, otherwise we'll end up using the properties of the default materialmaterial->EnsurePropertiesExist();Material* instance;instance = CreateObjectFromCode<Material>();instance->SetNameCpp(Append(material->GetName(), " (Instance)"));instance->m_Shader = material->m_Shader;instance->m_Owner = &renderer;// Creating the material above already creates the shared material data, so release and create a new one using copy constructor.// Would be nice to avoid this extra work (but then, the default "create material" already does a bunch of extra other work// that we are discarding here; an optimization for some future day).SAFE_RELEASE(instance->m_SharedMaterialData);instance->m_SharedMaterialData = UNITY_NEW(SharedMaterialData, kMemMaterial)(*material->m_SharedMaterialData);instance->m_SharedMaterialData->smallMaterialIndex = instance->GetInstanceID();instance->CopySettingsFromOther(*material);instance->m_SavedProperties = material->m_SavedProperties;return *instance;}
}

从当中可以看出:

  1. Material 的 m_Owner 是否指向是 Renderer,决定此 Material 是否是 Renderer 的实例化材质(InstantiatedMaterial)
  2. 如果传入的 material 是①所述的实例化材质直接返回,否则就克隆一份 material 并都将归属 m_Owner 指向 renderer
  3. 克隆出来的实例材质名字规则是在原有 material 的名字后边追加字符串(Instance)

1.3 得出结论

  1. Renderer 仅有一份 material 数组,模型初始化后,其内容就为 sharedMaterial(以 mesh 类为例,可以理解 MeshRenderer 创建后,material 数组中的内容就是 prefab 上的材质资源列表
  2. 执行 renderer.material,无论左值还是右值都会触发 Renderer 的创建材质实例的函数,此时如果 material 数组中的材质已经是本 renderer 创造的材质实例,则直接返回,否则创建返回,它会覆盖 shareMaterial
  3. 在步骤②之后,如果再次调用 renderer.shareMaterial,右值则直接返回当前 material 数组中的材质(当然它已经不再是最早的那个 material 资产了,也就是说此 renderer.shareMaterial 非比原 renderer.shareMaterial),左值则会再次实例化创建新的 material 并赋值,material 数组中的材质会再次被覆盖

因此,不存在最早我们分析的:sharedMaterial 和 material 是泾渭分明的两套数组存储,他们最终 cache 在同一个数组里,就是说你调用了 renderer.material 后,renderer.sharedMaterial 也就变成了你最新克隆的 renderer.material,如果你需要找回初始的 renderer.sharedMaterial,就只能自己提前 cache

源码中 m_Owner 就是材质克隆归属的依据:如果不满足克隆规则,Unity 会重新克隆,这样很多时候都会和我们预想的事与愿违

觉得绕可以直接看下面的例子:

Renderer r = go.GetComponent<Renderer>();
Material ma = r.material;  
r.sharedMaterial = ms1;
Material mb = r.material; 

上述代码,假设 r.sharedMaterial 为 ms。

  • 执行完第2句,r.material 和 r.sharedMaterial 皆为 ms(Instance),ms 在此处的引用已经丢失
  • 执行完第3句,r.sharedMaterial 为 ms1
  • 执行完第4句,r.material 和 r.sharedMaterial 皆为 ms1(Instance),ms1 在此处的引用已经丢失

所以,替换 sharedMaterial 要谨慎,如果你直接修改了它(r.sharedMaterial),可能就会动到资源文件,但如果你在实例化了 material 之后再访问 renderer.shareMaterial,难以避免的会再次实例化一个新的材质,很明显这个时候就会出现资源的浪费(一个 GO 实例化了两个 material),因此尽量还是采用 MaterialPropertyBlock 来做材质个性化的事

二、MaterialPropertyBlock

https://www.jianshu.com/p/eff18c57fa42

使用MaterialPropertyBlock来替换Material属性操作 - UWA问答 | 博客 | 游戏及VR应用性能优化记录分享 | 侑虎科技

接上文,其实从应用层考虑的话,其实我们只是想实现一个简单的需求:那就是修改当前 GameObject(Renderer) 的材质属性

前面提到过,如果你通过 renderer.material 修改材质属性,那么其实底层相当于是给你实例化了一个新的 material,并且这个 material 专属于当前的 GO,其实这样也没有问题,只要你能管理好这个实例化后的 material 也不是不行

当然还有一个更快更省的方法,就是使用 MaterialPropertyBlock

//一个使用 MaterialPropertyBlock 及 Renderer.SetPropertyBlock 修改材质的例子
private void onFxCircleLoaded(GameObject obj, int fxId, object userData)
{if (!obj || userData == null){return;}Vector4 vec = (Vector4)userData;var mr = obj.GetComponentInChildren<MeshRenderer>();if (mr){var mpb = MCommonObjectPool<MaterialPropertyBlock>.Get();mr.GetPropertyBlock(mpb);mpb.SetVector("_Params", vec);mr.SetPropertyBlock(mpb);mpb.Clear();MCommonObjectPool<MaterialPropertyBlock>.Release(mpb);}
}

网上很多文章都会将 MaterialPropertyBlock 和 GPU Instancing 绑定讲解,但其实 MaterialPropertyBlock 本质上只是一种优化的手段:其还可以被用于 Graphics.DrawMesh 和 Renderer.SetPropertyBlock 两个 API,当我们想要绘制许多相同材质但不同属性的对象时都可以使用它(无论是否 GPU Instancing)

它和直接赋值 renderer.material 不同,完全不会产生额外的材质实例,使用 MaterialPropertyBlock 会直接覆盖某个渲染器上对应的属性,开辟一片新的存储空间存储当前变量而并非在原先的 cbuffer (此 cbuffer 非比 DX 里的 constant buffer,更准确的说法应该是指 Unity 材质属性区)里面,后面也不再从 cbuffer 中拿数据了,也因此它会打破 SRP Batcher

2.1 使用 MaterialPropertyBlock 的注意事项

  1. 没有必要在 shader 属性前面声明 [PerRendererData] 前缀:有教程将 [PerRendererData] 和 MaterialPropertyBlock 捆绑在了一起,其实它们没有直接的逻辑关系,[PerRendererData] 只影响 Editor 的显示行为,即当你通过 MaterialPropertyBlock 改变了某个 Material 的属性之后,只有加上了这个,才能在预览对应的 Material 面板上看到对应属性值的变更,很明显,这没有太大的意义
  2. MaterialPropertyBlock 会使得 SRP Batcher 不生效,这个上面刚提到过,毕竟这两种方法本质思路都是开辟一段新的内存用于数据的读取,很明显,在数据唯一的这一铁定条件下,它不可能存在于两块空间中,因此这两套方案可以说是平行/不相容

相关文章:

Renderer 使用材质分析:materials、sharedMaterials 及 MaterialPropertyBlock

一、materials 与 sharedMaterials 1.1 使用上的区别与差异 Unity 开发时&#xff0c;在 C# 中通过 Renderer 取材质操作是非常常见的操作&#xff0c;Renderer 有两种常规获取材质的方式&#xff1a; sharedMaterials&#xff1a;可以理解这个就是原始材质&#xff0c;所有使…...

java学习----网络编程

网络编程入门 网络编程概述 计算机网络 ​ 计算机网络是指地理位置不同的具有独立功能的计算机及其外部设备&#xff0c;通过通信线路连接起来&#xff0c;在网络操作系统&#xff0c;网络管理软件及网络通信协议的管理协调下&#xff0c;实现资源共享和信息传递的计算机系统…...

这些「误区」99%的研发都踩过

意识不到误区的存在最为离谱&#xff1b; 01生活中&#xff0c;职场上&#xff0c;游戏里&#xff0c;都少不了正面对喷过&#xff1a;意识太差&#xff1b; 在个人的认知中意识即思维&#xff0c;意识太差即思维中存在的误区比较多&#xff1b; 每个人或多或少都存在思维上的…...

Bi系统跟数据中台的区别是什么?

随着数据时代的发展&#xff0c;BI分析是当今数据时代必不可少的能力之一。BI系统通过系统化产品化的方法&#xff0c;能够大幅降低数据的获取成本、提升数据使用效率。同时借助可视化、交互式的操作&#xff0c;可以高效支持业务的分析及发展。 BI如此火热&#xff0c;随之而…...

微信小程序反编译方法分享

文章目录一、前言二、准备工作&#xff08;一&#xff09;安装Nodejs&#xff08;二&#xff09;解密和逆向工具三、小程序缓存文件解密&#xff08;一&#xff09;定位小程序缓存路径&#xff08;二&#xff09;源码解密&#xff08;三&#xff09;源码反编译四、小结一、前言…...

有了这些接口测试用例+工具,测试效率想不提升都难

写在前面&#xff1a;在日常开发过程中&#xff0c;有人做前端开发&#xff0c;有人负责后端开发。接口的主要作用就是连接前后台。但是&#xff0c;由于前端和后端开发的速度可能不一样&#xff0c;尤其是后端开发好了&#xff0c;但前端还未开发。这种时候我们需要做接口测试…...

麒麟 arm架构安装nginx

目录 1、下载nginx安装包并解压 在线安装&#xff1a; 离线安装&#xff1a; 上传nginx安装包&#xff08;下载地址&#xff1a;https://nginx.org/download/nginx-1.20.2.tar.gz&#xff09;到指定目录 2、安装系统相关依赖软件、组件包 1、上传或者下载对应的组件包 2、安…...

logrotate失效的排查---selinux开启状态拦截问题及解决方法

首先测试环境selinux 处于关闭状态 disable # getenforce disable重新开启selinux配置与生产环境一致 [rootlocal]# cat /etc/selinux/config # This file controls the state of SELinux on the system. # SELINUX can take one of these three values: # enforcing - S…...

Allegro使用总结-查看Layout基本操作:

好久没用CSDN写过笔记了&#xff0c;没想到无意间打开&#xff0c;编辑器更新啦&#xff01;以前巨难用的“富文本编辑器”终于改观了&#x1f62d;变的好像语雀&#xff0c;うれしい1. 视图/画面操作a. 画面缩放&#xff08;Zoom&#xff09;&#xff1a;F11/F12 或 鼠标滚轮b…...

cmd del命令笔记

使用 /s 删除文件夹下所有的 del /s sub # 删除目录下所有文件&#xff0c;这个目录不会删除 /p 确认提示 /q 静默模式&#xff0c;不会提示要不要删除 如过和/p同时使用&#xff0c;那么不提示 /a 根据属性删除&#xff0c;a是attribute的意思 del /a:r 01.jpg # 01.jp…...

apifox持续集成+java+企微机器人+xxljob定时推送

总览&#xff1a; apifox做接口测试后&#xff0c;把用例合并组装成测试套件&#xff0c;然后apifox-cli通过终端命令实现把套件执行后&#xff0c;输出本地文件的测试报告html或json。本地解析后拿到有用的解决通过定时执行推送到企微群里。 然后把html一起推到群里。 这个…...

盘点Linux内核网络知识100道题,这篇就够了

计算机网络模型 1、五层因特网协议栈和七层OSI&#xff08;Open System Interconnections&#xff09;参考模型分别是什么&#xff1f; 5层&#xff1a;应用层、传输层、网络层、数据链路层、物理层 7层&#xff1a;应用层、表示层、会话层、传输层、网络层、数据链路层、物理…...

数据库敏感字段脱敏

文章目录什么是脱敏脱敏后带来什么问题解决方案一解决方案二具体实施方案一方案二存量数据处理什么是脱敏 如果你有申请过一些软件资质&#xff0c;应该会被要求敏感数据进行加密&#xff0c;比如密码不能明文&#xff0c;用户的手机号&#xff0c;身份证信息&#xff0c;银行…...

skynet 游戏服务器探索(1)--熟悉skynet(网络)

因为做游戏服务器开发&#xff0c;大多数都跟脚本打交道&#xff0c;要么是lua,要么是python,要么是php,方便热更新的只有lua与php, php相关的游戏服务器开发&#xff0c;参考我另外的文章https://blog.csdn.net/guoyilongedu/article/details/121049511lua脚本相关的&#xff…...

select、poll、epoll

select、poll、epoll select int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); int nfds&#xff1a;被select管理的文件描述符的个数&#xff0c;最大描述符编号1fd_set *readfds&#xff1a;读文件描述符集合fd_se…...

rollup的基本使用 基本配置与处理各种文件

rollup rollup是一个javascript的模块化打包工具 可以帮助我们编译小的代码到一个大的负载的代码中 比如一个库或者一个应用 rollup与webpack的区别 rollup主要针对ES Module进行打包 另外webpack通常可以通过各种loader处理各种各样的文件 以及处理他们的依赖关系 rollup更多…...

ubuntu-debian系-redhat系

debian系 包类型&#xff1a;.deb包 本地安装包安装工具&#xff1a;dpkg 本地包管理命令&#xff1a;dpkg -i package 安装包 dpkg -r package 卸载包 dpkg -l package 查看已安装包 远程安装包安装工具&#xff1a;apt / apt-get 远程包管理命令&#xff1a;apt-get apt-cac…...

Altium Designer 18中原理图DRC编译和PCB DRC检查-AD DRC

一、原理图编译 原理图检查的主要内容有&#xff1a; 1、元件位号冲突。也即多个元件编号相同&#xff0c;例如两个电容在原理图中都被命名为C2&#xff0c;显然肯定是无法生成PCB的。 2、网络悬浮。也即网络标号没有附着在电气走线上&#xff0c;一般这种是人操作失误&…...

zipfile — 访问 ZIP 压缩文件

zipfile — 访问 ZIP 压缩文件 1.概述 zipfile 模块可用于操作 ZIP 存档文件&#xff0c;.zip 是 PC 程序 PKZIP 推广的格式 2.测试数据 为了演示用&#xff0c;你需要创建以下三个文件 1.README.txt 内容如下&#xff0c;注意最后一行为空行 The examples for the zipfil…...

检查nmos管是否损坏

NCEP85T14 功率mos管为例 以NMOS举例&#xff0c;只用万用表二极管档测量MOS管的好坏-电子发烧友网 NMOS的D极和S极之间有一个寄生二极管&#xff0c;方向为S到D&#xff0c;利用二极管单向导电性以及MOS管导通时寄生二极管截止的特性&#xff0c;可以快速测量MOS好坏。 1、测…...

RestClient

什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端&#xff0c;它允许HTTP与Elasticsearch 集群通信&#xff0c;而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级&#xff…...

RocketMQ延迟消息机制

两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数&#xff0c;对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后&#xf…...

CMake基础:构建流程详解

目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

连锁超市冷库节能解决方案:如何实现超市降本增效

在连锁超市冷库运营中&#xff0c;高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术&#xff0c;实现年省电费15%-60%&#xff0c;且不改动原有装备、安装快捷、…...

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系&#xff0c;主要是分成几个表&#xff0c;用户表我们是记录用户的基础信息&#xff0c;包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题&#xff0c;不同的角色&#xf…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”

2025年#高考 将在近日拉开帷幕&#xff0c;#AI 监考一度冲上热搜。当AI深度融入高考&#xff0c;#时间同步 不再是辅助功能&#xff0c;而是决定AI监考系统成败的“生命线”。 AI亮相2025高考&#xff0c;40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕&#xff0c;江西、…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...