Unity引擎使用HybridCLR(华佗)热更新
大家好,我是阿赵。
阿赵我做手机游戏已经有十几年时间了。记得刚开始从做页游的公司转到去做手游的公司,在面试的时候很重要的一个点,就是会不会用Lua。使用Lua的原因很简单,就是为了热更新。
热更新游戏内容很重要。如果游戏内容需要改动,如果每次都要去平台出新的安装包提审,周期不可控,甚至像iOS这种提审特别麻烦的平台,还有不过审的风险。比如出个春节活动,提审完可能春节都过完了,那么这个内容也就没有意义。 但如果游戏的内容自己可以通过某些方法进行修改,不需要通过平台的审核就能直接改动,那么游戏制作的灵活性就大大增加了。我们使用Unity引擎来开发游戏,游戏资源是可以通过AssetBundle的方式热更新的,但C#代码,以前是不能热更新的,至少在iOS平台是不能的,安卓和pc有办法可以热更新dll。
Lua作为一个可以通过字符串或者字节资源的形式加载的脚本,在游戏热更新上起到了重要的作用,起码在近十年时间是出于统治地位的。但由于性能问题Lua也一直受到各种诟病,特别是在微信小游戏或者抖音小游戏上面,性能的确不是很好。
最近和UWA沟通的过程中,听说很多公司已经不用Lua,而改为用了HybridCLR(华佗)热更新。华佗热更新的原理是更新c#的程序集,也就是通过加载dll来实现代码层面的热更新,而且iOS也能用。不过由于公司的项目都是在用Lua开发的,如果换成华佗,等于整个项目要重新用C#写一篇,成本还是很高的。
学多一点东西肯定是有好处的,说不定以后新项目就能用得上。于是阿赵我也来学习一下华佗热更新的用法,并且记录一下一些使用的问题。
一、 安装
HybridCLR华佗热更新的在线文档地址是:
文档地址
里面有比较详细的安装说明,可以根据步骤一步步来安装。我只记录一下我遇到的问题。
1、 对应的Unity版本
在官方文档里面说,华佗支持的Unity版本有这些:
由于我有一个项目是使用2019.4.24开发的,一看文档说支持2019.4.x,感觉挺好,但继续看下去,会看到:
从文档看,2019只能在2019.4.40上面安装。其实2019.4前面的版本的确挺多问题的,比如我之前发现的URP的SRPBatcher合并问题等,各位如果还在用2019版本开发的朋友,我也挺建议大家都升级到2019.4.40。无奈的是,如果项目已经上线了,再来换版本,可能会导致AssetBundle打包的资源会全部变更,Unity打包AssetBundle的时候会把版本号写在文件开头,所以就算你所有内容都没变,只是换个Unity版本,打出来的AssetBundle文件也会全部改变的……
不过幸好,华佗的文档里面也有针对这种情况的处理办法,就是先把项目切换到2019.4.40,然后安装华佗,再切换会原来的版本。
抛开项目已有版本的问题,其实就无所谓了,因为之后的版本很多都支持, 比如直接安装2022.3.x版本,就没这个问题了。
2、下载代码
由于代码库是从git下载,所以必须安装git。
然后通过Unity的PackageManager里面的Add package from git URL来安装
库地址:
https://gitee.com/focus-creative-games/hybridclr_unity.git
或
https://github.com/focus-creative-games/hybridclr_unity.git
我自己尝试的结果是没办法通过Add package from git URL来安装,安装了GIT和加了环境变量PATH也不行。我自己用GIT手动克隆,却是没问题的,这一点很神奇。
于是解决这个问题的方法是,可以手动把地址检出克隆到本地,然后把文件夹改名com.code-philosophy.hybridclr,并复制到项目里面和Assets文件夹同级的Packages文件夹
复制后打开项目,会看到有华佗的菜单
选择安装器,然后安装
按照文档说明基本都可以自动安装成功,但我还是失败了,看报错还是GIT的问题,于是我根据报错,自己检出克隆https://gitee.com/focus-creative-games/hybridclr到项目的HybridCLRData/hybridclr_repo文件夹
检出后再次点击Install按钮,就可以安装成功了。
HybridCLR菜单下出现了所有的选项子菜单。
二、 浅尝华佗热更新
1、 一些概念
在使用华佗热更新之前需要先了解一些概念
1. 程序集
先来操作,最后再说为什么。在项目里面创建一个文件夹,叫做HotUpdate,或者叫其他都行,你自己喜欢:
然后在这个文件夹里面,创建一个Assembly Definition文件:
帮这个文件起个名字,比如我这里就叫做HotUpdate:
注意要把Auto Referenced的勾选去掉。
然后在这个文件夹里面创建一个C#脚本,我这里随便命名为Hello:
创建完之后,点选这个Hello脚本,会看到里面多了一个Assembly信息,里面说明了,这个Hello的脚本,是属于HotUpdate.dll的。
操作到此结束,下面解释一下:
这个创建文件夹和Assembly Definition文件的过程,是Unity引擎的程序集功能,其实就是指定了某个文件夹作为一个程序集的范围。只要在这个文件夹下面的所有文件,包括子文件夹里面的文件,都属于当前这个Assembly Definition文件的程序集里面的内容。
一个程序集,字面意思就是程序的集合了,可以理解成是把里面的代码都打包了,之后需要热更新代码,其实就是热更新这个程序集的dll文件了。
2. AOT程序集和热更新程序集
使用Unity引擎制作游戏,各位肯定应该都会写C#。在项目里面所写的C#代码,就算我们不特意的打程序集,它们也会出现在一个程序集里面,就是Assembly-CSharp.dll,然后我们又可以根据自己的需要,创建一些程序集,所以最后打包的时候,除了Assembly-CSharp.dll,还会有一些自己的dll。这些多个程序集,之后会用于华佗热更新。
这里有个问题,热更新是以dll为单位的,那些可以热更新的程序集,在使用华佗热更新的时候,是会剥离出去,不会包含在主工程包里面的。而我们需要写代码加载这些dll文件,就必须有一些代码是包含在主工程里面不能热更新的。
所以在使用华佗热更新的时候,需要把程序集分成2部分,第一部分是包含在游戏主包里面不能热更新的,成为AOT程序集,第二部分是可以热更新的dll,成为热更新程序集。
3. 程序集的规划和程序集之间的引用关系
由于程序集起码要有AOT和可热更两个,甚至更多,所以在做之前,我们必须先规划一下它们之间的关系。具体来说,就是总共需要多少个程序集才能满足我们需要,既能热更,又可以划分清楚模块,做到分块更新。
程序集之间的引用,有2种方式,第一种,就是在程序集上面勾上Auto Referenced,这样它自动被其他程序集引用,可以互相调用里面的方法。
另外一种,就是在程序集上面指定依赖关系,比如我再建一个HotUpdate2的程序集,不勾选Auto Referenced:
这个时候如果HotUpdate程序集要访问HotUpdate2程序集,可以选择HotUpdate程序集,然后添加引用关系:
只要在HotUpdate程序集的Assembly Definition References里面添加了HotUpdate2的引用,那么HotUpdate就能调用HotUpdate2里面的方法了。
华佗热更新里面有一个规则,AOT程序集是不能直接引用热更新程序集的,不然在打包的时候会出错。所以,我们在创建自己的可热更程序集的时候,必须把Auto Referenced的勾选去掉,然后自己维护可热更新程序集之间的引用关系。
2、 尝试使用华佗热更新
1. 指定需要热更新的程序集
在HybridCLR菜单下面选择Settings设置:
然后添加可热更新的程序集:
在这里设置了的程序集,在打主包的时候,程序集是不会包含在主包里面的。
2. 生成必须的东西
在首次使用华佗热更新的时候,必须先选择Generate——All,生成所有必须的文件,其实就是All上面的哪些东西了。在之后的使用中,就不一定要生成All,可以根据实际需要来生成上面的内容。
3. 写热更新的测试代码
首先,为了打包之后看到控制台的打印,先创建一个脚本:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class ConsoleToScreen : MonoBehaviour
{const int maxLines = 50;const int maxLineLength = 120;private string _logStr = "";private readonly List<string> _lines = new List<string>();public int fontSize = 15;void OnEnable() { Application.logMessageReceived += Log; }void OnDisable() { Application.logMessageReceived -= Log; }public void Log(string logString, string stackTrace, LogType type){foreach (var line in logString.Split('\n')){if (line.Length <= maxLineLength){_lines.Add(line);continue;}var lineCount = line.Length / maxLineLength + 1;for (int i = 0; i < lineCount; i++){if ((i + 1) * maxLineLength <= line.Length){_lines.Add(line.Substring(i * maxLineLength, maxLineLength));}else{_lines.Add(line.Substring(i * maxLineLength, line.Length - i * maxLineLength));}}}if (_lines.Count > maxLines){_lines.RemoveRange(0, _lines.Count - maxLines);}_logStr = string.Join("\n", _lines);}void OnGUI(){GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity,new Vector3(Screen.width / 1200.0f, Screen.height / 800.0f, 1.0f));GUI.Label(new Rect(10, 10, 800, 370), _logStr, new GUIStyle() { fontSize = Math.Max(10, fontSize) });}
}
在场景上面建个空物体,然后把脚本拖上去:
这样做的目的只是为了让我们在接下来的测试中,把控制台打印输出到屏幕,让我们知道热更新有没有生效。
然后给Hello脚本修改一下:
using UnityEngine;public class Hello
{static public void Print(){Debug.Log("Hello World");}
}
这里只有一个静态方法,如果执行了,会打印Hello World到控制台,通过上面的脚本,控制台的打印就会出现在屏幕。
最后,要加一个AOT脚本,作为游戏启动、加载dll和调用dll。正常来说热更新的dll文件应该放在CDN上,然后下载到本地。这里为了测试,就写死放在StreamingAssets文件夹了。这里建一个叫做TestLoadDll的C#脚本:
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEngine;public class TestLoadDll : MonoBehaviour
{// Start is called before the first frame updatevoid Start(){Assembly dllLoader;
#if !UNITY_EDITORdllLoader = Assembly.Load(File.ReadAllBytes($"{Application.streamingAssetsPath}/HotUpdate.dll.bytes"));
#elsedllLoader = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");
#endifType type = dllLoader.GetType("Hello");type.GetMethod("Print").Invoke(null, null);}// Update is called once per framevoid Update(){}
}
然后在场景里面建一个空物体,把脚本拖上去:
这时候在编辑器里面运行,会看到Hello World打印出来了:
脚本里面的内容很简单,只是规定了在编辑器就直接读取工程里面的HotUpdate程序集,在非编辑器的情况下,就读取StreamingAssets文件夹下面的HotUpdate.dll.bytes文件。由于Unity的诡异规定,所以dll文件是不能直接读取的,要把后缀改成bytes。
然后后面的那段反射代码
Type type = dllLoader.GetType("Hello");
type.GetMethod("Print").Invoke(null, null);
不用害怕,这是因为AOT程序集不能直接引用可热更新的HotUpdate程序集,所以才用反射调用一下,仅此而已,如果没有特殊情况,是不需要这样做的。
4. 打包热更新用的dll
在HybridCLR菜单选择CompileDll——ActiveBuildTarget
这时候会把对dll进行打包,打包的结果在
项目文件夹\HybridCLRData\HotUpdateDlls\对应的平台文件夹\:
由于我现在的平台是Windows,所以实际路径会在StandaloneWindows64文件夹下。这里会看到了项目里面所用到的所有程序集的dll文件,其中就有我们想要热更新的HotUpdate.dll。我们刚才也指定了HotUpdate2程序集,但由于里面一个脚本都没有,所以是不会有dll打出来的。
把HotUpdate.dll复制到StreamingAssets文件夹并重命名为HotUpdate.dll.bytes
5. 打包测试
选择一个文件夹,常规的打个PC包出来:
发现打不出来,因为刚才指定了HotUpdate2程序集,但现在这个程序集是没有内容的
去华佗设置里面把HotUpdate2程序集从可热更新的程序集里面去掉。这次就能正常打包了。
运行打出来的包,能看到HelloWorld,证明打包成功,从刚才的读取dll的代码我们可以知道,现在是读取了StreamingAssets里面的HotUpdate.dll.bytes作为代码执行的。
6. 验证热更新修改代码
回到Hello脚本,修改一下:
using UnityEngine;public class Hello
{static public void Print(){Debug.Log("Hello Azhao");}
}
把原来的Hello World改成Hello Azhao
然后再次HybridCLR菜单选择CompileDll——ActiveBuildTarget,打包dll
再次在HybridCLRData\HotUpdateDlls\StandaloneWindows64目录找到HotUpdate.dll文件,然后拷贝到之前打的PC包的StreamingAssets文件夹:
这时候再次运行之前的PC包
可以看到,现在PC包显示的内容已经变成了Hello Azhao。到此为止,华佗热更新的基本流程已经跑通了。
三、 华佗热更新的深入使用
1、 尝试AssetBundle加载资源
接下来,尝试把C#脚本挂在GameObject上,并通过AssetBundle加载这个GameObject看看:
在HotUpdate程序集建一个PrintObject的C#脚本:
using UnityEngine;public class PrintObject : MonoBehaviour
{// Start is called before the first frame updatevoid Start(){Debug.Log("GameObject:" + gameObject.name);}// Update is called once per framevoid Update(){}
}
然后建一个cube,把脚本挂上去:
把这个Cube做成Prefab,并且设置AssetBundleName,打包AssetBundle:
然后打包AssetBundle,把AssetBundle文件放到StreamingAssets文件夹:
修改Hello脚本:
using UnityEngine;public class Hello
{static public void Print(){string path = Application.streamingAssetsPath + "/ab/cube.unity3d";AssetBundle ab = AssetBundle.LoadFromFile(path);if(ab){Object obj = ab.LoadAsset("Cube");if(obj != null){GameObject.Instantiate(obj);} }}
}
生成dll,并且和AssetBundle一起拷贝到pc包的StreamingAssets文件夹:
这时候,运行PC包,并没有出现我们想要的情况,而是有个报错:
这是为什么呢?
2、关于代码裁剪
如果在打包的时候没有用到某些Unity自带的API,但后期在热更新的代码上加上,就会出现报错,找不到方法。原因是IL2CPP的情况下,代码裁剪是不能被禁止的,而之前没有用过的API,在Unity打包的时候被裁剪掉了。
一般来说,为了防止需要的Unity原生API代码被裁剪的问题,可以在项目里面建一个link.xml文件,然后把需要保留不被裁剪的内容填进去。不过这样手动收集是很麻烦的,华佗的工具里面自带了收集link.xml的功能
只要点一下,就会把项目里面有调用过的API加入到link.xml里面。
这里还有2个问题
1、 需要保留的代码,除了加在link.xml之外,代码还要必须显式的引用过这些类或者函数,不然也还是会被裁剪。
2、 重新收集完link.xml之后,必须重新打包才能生效……
这样似乎就回到了使用Lua时的导出接口的操作了,没有导出过接口的类和方法,不能热更新……关键这一步你在编辑器内还很难发现,毕竟编辑器内的Unity自带API是不会被裁剪的。
这是一个我认为使用华佗热更新最大的问题。毕竟Unity很多API可能在一开始的时候没考虑到需要使用,后面用到才收集,就不能热更新了。
既然是需要重新出包了,所以也就不止是点一下LinkXml了,直接Generate——All,生成所有,那样就稳妥了。
全部重新生成之后,再次出包,就可以看到之前的报错没有了,可以加载AssetBundle里面的Cube,并且挂在上面的脚本也正常运行了:
3、 新增程序集的热更新
之前的例子里面只有1个可热更新的程序集,叫做HotUpdate,现在我想在不重新出包的情况下,增加一个HotUpdate2的可热更新程序集,试试能不能热更新。
由于之前是在AOT代码里面写死了需要加载HotUpdate.dll.bytes,所以如果增加新的程序集dll文件,肯定是不能加载的,所以要改成需要加载哪些dll文件要通过可热更的文件来决定。
这里为了测试简单,我放一个dll.txt文本在StreamingAssets文件夹,然后在里面用逗号分隔需要加载的程序集名字。由于HotUpdate.dll需要通过反射来调用,所以我就不写在txt里面了,这也说明,如果需要有一个程序调用入口,那么至少有一个dll是需要写在代码里面加载的。于是加载dll的代码会变成这样:
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEngine;public class TestLoadDll : MonoBehaviour
{// Start is called before the first frame updatevoid Start(){Assembly dllLoader;
#if !UNITY_EDITORstring path = Application.streamingAssetsPath + "/dll.txt";string content = File.ReadAllText(path);if(string.IsNullOrEmpty(content)==false) {string[] fileNames = content.Trim().Split(",");for(int i = 0;i < fileNames.Length; i++) { string fileName = fileNames[i];if(string.IsNullOrEmpty(fileName)==false){Assembly.Load(File.ReadAllBytes($"{Application.streamingAssetsPath}/"+fileName+".dll.bytes"));}}}dllLoader = Assembly.Load(File.ReadAllBytes($"{Application.streamingAssetsPath}/HotUpdate.dll.bytes"));
#elsedllLoader = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");
#endifType type = dllLoader.GetType("Hello");type.GetMethod("Print").Invoke(null, null);}// Update is called once per framevoid Update(){}
}
到现在为止,先打个PC包,作为热更新的基础包。
接下来同样的手法,建立HotUpdate2文件夹和程序集,在里面添加一个PrintGameObject的脚本:
using UnityEngine;public class PrintGameObject : MonoBehaviour
{// Start is called before the first frame updatevoid Start(){Debug.Log("HotUpdate2:" + gameObject.name);}// Update is called once per framevoid Update(){}static public void Run(){Debug.Log("This is HotUpdate2");}
}
把这个PrintGameObject脚本挂在之前的Cube预设上,原来的PrintObject脚本就不挂了:
在HotUpdate程序集添加HotUpdate2程序集的引用:
修改Hello脚本:
using UnityEngine;public class Hello
{static public void Print(){PrintGameObject.Run();string path = Application.streamingAssetsPath + "/ab/cube.unity3d";AssetBundle ab = AssetBundle.LoadFromFile(path);if(ab){Object obj = ab.LoadAsset("Cube");if(obj != null){GameObject.Instantiate(obj);} }}
}
主要是加了一句PrintGameObject.Run();
接下来还是常规操作,把HotUpdate2加到可热更新的列表
在dll.txt里面写入HotUpdate2。然后打包AssetBundle、打包Dll,把这些东西都拷贝到PC包的StreamingAssets,然后运行,会看到:
发现一个神奇的事情,HotUpdate2的代码其实已经加载了,PrintGameObject里面的Run方法都打印出来This is HotUpdate2了,但挂在Cube上的PrintGameObject脚本却找不到……
接下来改一下做法,把Cube上面的PrintGameObject脚本去掉,变成在实例化GameObject之后用AddComponent来添加脚本:
using UnityEngine;public class Hello
{static public void Print(){PrintGameObject.Run();string path = Application.streamingAssetsPath + "/ab/cube.unity3d";AssetBundle ab = AssetBundle.LoadFromFile(path);if(ab){Object obj = ab.LoadAsset("Cube");if(obj != null){GameObject go = (GameObject)GameObject.Instantiate(obj);go.AddComponent<PrintGameObject>();} }}
}
再次打包AssetBundle,打包dll,拷贝到PC包的StreamingAssets文件夹,运行PC包:
会看到,添加成功了,PrintGameObject脚本也运行成功了。
关于新增的程序集挂到GameObject的AssetBundle热更的问题,我到最后都没有解决,不知道是不是有解决办法。我只能暂时得出结论,如果新增程序集,纯代码调用时没问题的,但如果挂在GameObject上通过AssetBundle加载,就会有问题。
这就导致一个问题,我们如果想出了安装包之后可以长时间的热更新,不需要重新出包,就必须对可能用到的程序集做好规划,尽量不要去改变了。
相关文章:

Unity引擎使用HybridCLR(华佗)热更新
大家好,我是阿赵。 阿赵我做手机游戏已经有十几年时间了。记得刚开始从做页游的公司转到去做手游的公司,在面试的时候很重要的一个点,就是会不会用Lua。使用Lua的原因很简单,就是为了热更新。 热更新游戏内容很重要。如果…...

深度学习进阶:神经网络优化技术全解析
文章目录 前言一、优化问题的本质1.1 目标1.2 挑战 二、梯度下降优化算法2.1 基础SGD2.2 动量法2.3 Adam优化器 三、正则化技术3.1 L2正则化3.2 Dropout 四、学习率调度4.1 为什么要调度?4.2 指数衰减4.3 ReduceLROnPlateau 五、实战优化:MNIST案例5.1 完…...

肿瘤检测新突破:用随机森林分类器助力医学诊断
前言 你有没有想过,科技能不能在肿瘤检测中发挥巨大的作用?别着急,今天我们将带你走进一个“聪明”的世界,通过随机森林分类器进行肿瘤检测。对,你没听错,机器学习可以帮助医生更快、更准确地判断肿瘤是良性还是恶性,就像医生口袋里的“超级助手”一样,随时准备提供帮…...

DeepSeek学习 一
DeepSeek学习 一 一、DeepSeek是什么?二、Deepseek可以做什么?模型理解提问内容差异使用原则 模式认识三、如何提问?RTGO提示语结构CO-STAR提示语框架DeepSeek R1提示语技巧 总结 一、DeepSeek是什么? DeepSeek是一家专注通用人工…...

编程考古-Borland历史:《.EXE Interview》对Anders Hejlsberg关于Delphi的采访内容(上)
为了纪念Delphi在2002年2月14日发布的25周年(2020.2.12),这里有一段由.EXE杂志编辑Will Watts于1995年对Delphi首席架构师Anders Hejlsberg进行的采访记录。在这次采访中,Anders讨论了Delphi的设计与发展,以及即将到来的针对Windows 95的32位版本。 问: Delphi是如何从T…...

高并发之接口限流,springboot整合Resilience4j实现接口限流
添加依赖 <dependency><groupId>io.github.resilience4j</groupId><artifactId>resilience4j-spring-boot2</artifactId><version>1.7.0</version> </dependency><dependency><groupId>org.springframework.boot…...

电脑如何拦截端口号,实现阻断访问?
如果你弟弟喜欢玩游戏,你可以查询该应用占用的端口,结合以下方法即可阻断端口号,让弟弟好好学习,天天向上! 拦截端口可以通过防火墙和路由器进行拦截 ,以下是常用方法: 方法 1:使用…...

RK3588 安装ffmpeg6.1.2
在安装 ffmpeg 在 RK3588 开发板上时,你需要确保你的开发环境(例如 Ubuntu、Debian 或其他 Linux 发行版)已经设置好了交叉编译工具链,以便能够针对 RK3588 架构编译软件。以下是一些步骤和指导,帮助你安装 FFmpeg: 1. 安装依赖项 首先,确保你的系统上安装了所有必要的…...

SQL SELECT DISTINCT 语句
在 SQL 中,SELECT DISTINCT 语句用于从表中查询不重复的值。这对于需要从数据库检索唯一值时非常有用。DISTINCT 关键字会去除结果集中重复的行,只返回唯一的记录。 SELECT DISTINCT column1, column2, ... FROM table_name; column1, column2, ... 是…...

MELON的难题
MELON的难题 真题目录: 点击去查看 E 卷 200分题型 题目描述 MELON有一堆精美的雨花石(数量为n,重量各异),准备送给S和W。MELON希望送给俩人的雨花石重量一致,请你设计一个程序,帮MELON确认是否能将雨花石平均分配。 输入描述 第1行输入为雨花石个数: n,0 < n &l…...

Restful 接口设计规范
一、资源与 URL 1. 使用名词表示资源 URL 应该以名词为主,用来表示具体的资源,而不是动词。例如,/users 表示用户资源集合,/users/{id} 表示单个用户资源。 2. 采用复数形式 一般来说,资源的 URL 应该使用复数形式…...

Java后端高频面经——Spring、SpringBoot、MyBatis
Spring定义一个Bean有哪些方法?依赖注入有哪些方法? (1)定义Bean的方法 注解定义Bean,Component 用于标记一个类作为Spring的bean。当一个类被Component注解标记时,Spring会将其实例化为一个bean࿰…...

扩散模型中三种加入条件的方式:Vanilla Guidance,Classifier Guidance 以及 Classifier-Free Guidance
扩散模型主要包括两个过程:前向扩散过程和反向去噪过程。前向过程逐渐给数据添加噪声,直到数据变成纯噪声;反向过程则是学习如何从噪声中逐步恢复出原始数据。在生成过程中,模型从一个随机噪声开始,通过多次迭代去噪&a…...

Banana Pi OpenWRT One Wifi6 OpenWrt社区官方开源路由器评测
第一款不可破解、开源、版权软件、符合 FCC、CE 和 RoHS 的维修权路由器 OpenWRT项目今年已经20岁了,为了纪念这一时刻,Banana Pi OpenWrt One/AP-24.XY路由器开发系统已经上市。这是OpenWRT团队与硬件公司的第一个联合项目。选择 Banana Pi,…...

9.1go结构体
Go不是完全面向对象的,没有类的概念,所以结构体应该承担了更多的责任。 结构体定义 使用 type 和 struct 关键字定义: type Person struct { Name string Age int } 字段可以是任意类型,包括其他结构体或指针。 字段名以大写…...

Manus全球首个通用Agent,Manus AI:Agent应用的ChatGPT时刻
文章目录 前言Manus AI: 全球首个通用AgentManus AI: 技术架构与创始人经历AI Agent的实现框架与启示AI Agent的发展预测行业风险提示 前言 这是一篇关于Manus AI及其在通用人工智能领域的应用和前景的报告,主要介绍了Manus AI的产品定位、功能、技术架构、创始人经…...

【SAP-PP】生产版本维护
一、基本概念 生产版本:用于定义一种产品,不同的生产方式,包含物料清单(BOM)和工艺路线的信息,给生产带来更多的灵活性。在做产品需求计划时和产品生产时(创建生产订单、生产订单下达前和生产订…...

软考 中级软件设计师 考点笔记总结 day01
文章目录 软考1.0上午考点下午考点 软考1.11、数值及其转换2、计算机内数据表示2.1、定点数 - 浮点数2.2、奇偶校验 和 循环冗余校验 (了解)2.3、海明码 (掌握)2.4、机器数 软考1.0 上午考点 软件工程基础知识: 开发模型、设计原则、测试方…...

K8s控制器Deployment详解
回顾 ReplicaSet 控制器,该控制器是用来维护集群中运行的 Pod 数量的,但是往往在实际操作的时候,我们反而不会去直接使用 RS,而是会使用更上层的控制器,比如说 Deployment。 Deployment 一个非常重要的功能就是实现了 Pod 的滚动…...

【微知】Centos如何迁移到Anolis系统的失败记录?(yum -y install centos2anolis、centos2anolis.py)
背景 本文记录如何从centos 8迁移到anolis系统。 详细步骤 下载迁移repo wget https://mirrors.openanolis.cn/anolis/migration/anolis-migration.repo -O /etc/yum.repos.d/anolis-migration.repo下载centos2anolis工具包 yum -y install centos2anolis安装额外工具包 …...

在 macOS 上使用 CLion 进行 Google Test 单元测试
介绍 Google Test(GTest)是 Google 开源的 C 单元测试框架,它提供了简单易用的断言、测试夹具(Fixtures)和测试运行机制,使 C 开发者能够编写高效的单元测试。 本博客将介绍如何在 macOS 上使用 CLion 配…...

Python SQLite3 保姆级教程:从零开始学数据库操作
Python SQLite3 保姆级教程:从零开始学数据库操作 本文适合纯新手!无需任何数据库基础,跟着步骤操作即可掌握 SQLite3 的核心用法。 目标:让你像用记事本一样轻松操作数据库! 目录 什么是 SQLite3?环境准…...

深度解析:视频软编码与硬编码的优劣对比
视频编码 一、基本原理与核心技术 压缩原理 通过时空冗余消除实现数据压缩: 空间冗余:利用帧内预测(如DC/角度预测)消除单帧内相邻像素相似性。时间冗余:运动估计与补偿技术(ME/MC)减少连续帧间…...

Azure云生态系统详解:核心服务、混合架构与云原生概念
核心服务:深入掌握Azure SQL Database、Azure Database for PostgreSQL、Azure Database for MySQL的架构、备份恢复、高可用性配置(如Geo-Replication、自动故障转移组、异地冗余备份)。混合架构:熟悉Azure Arc(管理混…...

人工智能之数学基础:正交矩阵
本文重点 正交矩阵是线性代数中一个重要的特殊矩阵,它在许多领域都有广泛的应用。 什么是正交矩阵 如图所示,当矩阵A满足如上所示的条件的时候,此时我们就可以认为是正交矩阵,需要注意一点矩阵A必为方阵。 正交矩阵的充要条件 …...

分布式锁—7.Curator的分布式锁
大纲 1.Curator的可重入锁的源码 2.Curator的非可重入锁的源码 3.Curator的可重入读写锁的源码 4.Curator的MultiLock源码 5.Curator的Semaphore源码 1.Curator的可重入锁的源码 (1)InterProcessMutex获取分布式锁 (2)InterProcessMutex的初始化 (3)InterProcessMutex.…...

【笔记】STM32L4系列使用RT-Thread Studio电源管理组件(PM框架)实现低功耗
硬件平台:STM32L431RCT6 RT-Thread版本:4.1.0 目录 一.新建工程 二.配置工程 编辑 三.移植pm驱动 四.配置cubeMX 五.修改驱动文件,干掉报错 六.增加用户低功耗逻辑 1.设置唤醒方式 2.设置睡眠时以及唤醒后动作 编辑 3.增加测试命…...

C++什么是深复制和浅复制,构造函数和析构函数,哪一个可以写成虚函数,为什么?
在C之中深复制是指对于值类型复制它的值,对于指针类型不仅仅复制指针指向的值,还会重新分配一个内存空间用于放置复制的值(对动态分配的内存进行重新分配和内存复制),这种深复制不会出现悬空指针的问题,但是…...

从连接到交互:SDN 架构下 OpenFlow 协议的流程与报文剖析
在SDN架构中,交换机与控制器之间的通信基于 OpenFlow协议,其设计目的是实现控制平面与数据平面的解耦。以下是 交换机连接控制器 和 数据包进入交换机触发交互 的详细流程及协议报文分析: 一、交换机连接控制器的流程(初始化阶段&…...

第七课:Python反爬攻防战:Headers/IP代理与验证码
在爬虫开发过程中,反爬虫机制成为了我们必须面对的挑战。本文将深入探讨Python爬虫中常见的反爬机制,并详细解析如何通过随机User-Agent生成、代理IP池搭建以及验证码识别来应对这些反爬策略。文章将包含完整的示例代码,帮助读者更好地理解和…...