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 update
    void Start()
    {
        Assembly dllLoader;
#if !UNITY_EDITOR
    dllLoader = Assembly.Load(File.ReadAllBytes($"{Application.streamingAssetsPath}/HotUpdate.dll.bytes"));
#else
    dllLoader = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");        
#endif
        Type type = dllLoader.GetType("Hello");
        type.GetMethod("Print").Invoke(null, null);
    }

    // Update is called once per frame
    void 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 update
    void Start()
    {
        Debug.Log("GameObject:" + gameObject.name);
    }

    // Update is called once per frame
    void 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 update
    void Start()
    {
        Assembly dllLoader;
#if !UNITY_EDITOR
    string 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"));
#else
        dllLoader = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");        
#endif
        Type type = dllLoader.GetType("Hello");
        type.GetMethod("Print").Invoke(null, null);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

  到现在为止,先打个PC包,作为热更新的基础包。
  接下来同样的手法,建立HotUpdate2文件夹和程序集,在里面添加一个PrintGameObject的脚本:
在这里插入图片描述

using UnityEngine;

public class PrintGameObject : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("HotUpdate2:" + gameObject.name);
    }

    // Update is called once per frame
    void 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加载,就会有问题。
  这就导致一个问题,我们如果想出了安装包之后可以长时间的热更新,不需要重新出包,就必须对可能用到的程序集做好规划,尽量不要去改变了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【神经网络】python实现神经网络(一)——数据集获取

一.概述 在文章【机器学习】一个例子带你了解神经网络是什么中&#xff0c;我们大致了解神经网络的正向信息传导、反向传导以及学习过程的大致流程&#xff0c;现在我们正式开始进行代码的实现&#xff0c;首先我们来实现第一步的运算过程模拟讲解&#xff1a;正向传导。本次代…

【Linux】冯诺依曼体系与操作系统理解

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;Linux 目录 前言 一、冯诺依曼体系结构 二、操作系统 1. 操作系统的概念 2. 操作系统存在的意义 3. 操作系统的管理方式 4. 补充&#xff1a;理解系统调用…

HTML-网页介绍

一、网页 1.什么是网页&#xff1a; 网站是指在因特网上根据一定的规则&#xff0c;使用 HTML 等制作的用于展示特定内容相关的网页集合。 网页是网站中的一“页”&#xff0c;通常是 HTML 格式的文件&#xff0c;它要通过浏览器来阅读。 网页是构成网站的基本元素&#xf…

STM32——GPIO介绍

GPIO(General-Purpose IO ports,通用输入/输出接口)模块是STM32的外设接口的核心部分,用于感知外界信号(输入模式)和控制外部设备(输出模式),支持多种工作模式和配置选项。 1、GPIO 基本结构 STM32F407 的每个 GPIO 引脚均可独立配置,主要特性包括: 9 组 GPIO 端口…

字节码是由什么组成的?

Java字节码是Java程序编译后的中间产物&#xff0c;它是一种二进制格式的代码&#xff0c;可以在Java虚拟机&#xff08;JVM&#xff09;上运行。理解字节码的组成有助于我们更好地理解Java程序的运行机制。 1. Java字节码是什么&#xff1f; 定义 Java字节码是Java源代码经过…

链表算法题目

1.两数相加 两个非空链表&#xff0c;分别表示两个整数&#xff0c;只不过是反着存储的&#xff0c;即先存储低位在存储高位。要求计算这两个链表所表示数的和&#xff0c;然后再以相同的表示方式将结果表示出来。如示例一&#xff1a;两个数分别是342和465&#xff0c;和为807…

blender学习25.3.8

【04-进阶篇】Blender材质及灯光Cycle渲染&后期_哔哩哔哩_bilibili 注意的问题 这一节有一个大重点就是你得打开显卡的渲染&#xff0c;否则cpu直接跑满然后渲染的还十分慢 在这里你要打开GPU计算&#xff0c;但是这还不够 左上角编辑&#xff0c;偏好设置&#xff0c;系…

【godot4.4】布局函数库Layouts

概述 为了方便编写一些自定义容器和控件、节点时方便元素布局&#xff0c;所以编写了一套布局的求取函数&#xff0c;统一放置在一个名为Layouts的静态函数库中。 本文介绍我自定义的一些布局计算和实现以及函数编写的思路&#xff0c;并提供完整的函数库代码&#xff08;持续…

Windows下配置Conda环境路径

问题描述&#xff1a; 安装好Conda之后&#xff0c;创建好自己的虚拟环境&#xff0c;同时下载并安装了Pycharm&#xff0c;但在Pycharm中找不到自己使用Conda创建好的虚拟环境。显示“Conda executable is not found” 解决办法&#xff08;依次尝试以下&#xff09; 起初怀…

OpenHarmony子系统开发编译构建指导

OpenHarmony子系统开发编译构建指导 概述 OpenHarmony编译子系统是以GN和Ninja构建为基座&#xff0c;对构建和配置粒度进行部件化抽象、对内建模块进行功能增强、对业务模块进行功能扩展的系统&#xff0c;该系统提供以下基本功能&#xff1a; 以部件为最小粒度拼装产品和独…

leetcode日记(80)复原IP地址

只能说之前动态规划做多了&#xff0c;看到就想到动态规划&#xff0c;然后想想其实完全不需要&#xff0c;回溯法就行了。 一开始用了很多莫名其妙的代码&#xff0c;写的很复杂……&#xff08;主要因为最后不能加‘.’&#xff09;其实想想只要最后加入vector时去掉最后一个…

LINUX网络基础 [五] - HTTP协议

目录 HTTP协议 预备知识 认识 URL 认识 urlencode 和 urldecode HTTP协议格式 HTTP请求协议格式 HTTP响应协议格式 HTTP的方法 HTTP的状态码 ​编辑HTTP常见Header HTTP实现代码 HttpServer.hpp HttpServer.cpp Socket.hpp log.hpp Makefile Web根目录 H…

【A2DP】SBC 编解码器互操作性要求详解

目录 一、SBC编解码器互操作性概述 二、编解码器特定信息元素(Codec Specific Information Elements) 2.1 采样频率(Sampling Frequency) 2.2 声道模式(Channel Mode) 2.3 块长度(Block Length) 2.4 子带数量(Subbands) 2.5 分配方法(Allocation Method) 2…

电脑内存智能监控清理,优化性能的实用软件

软件介绍 Memory cleaner是一款内存清理软件。功能很强&#xff0c;效果很不错。 Memory cleaner会在内存用量超出80%时&#xff0c;自动执行“裁剪进程工作集”“清理系统缓存”以及“用全部可能的方法清理内存”等操作&#xff0c;以此来优化电脑性能。 同时&#xff0c;我…

基于multisim的花样彩灯循环控制电路设计与仿真

1 课程设计的任务与要求 &#xff08;一&#xff09;、设计内容&#xff1a; 设计一个8路移存型彩灯控制器&#xff0c;基本要求&#xff1a; 1. 8路彩灯能演示至少三种花型&#xff08;花型自拟&#xff09;&#xff1b; 2. 彩灯用发光二极管LED模拟&#xff1b; 3. 选做…

Axure常用变量及使用方法详解

点击下载《Axure常用变量及使用方法详解.pdf》 摘要 Axure RP 作为一款领先的前端原型设计工具&#xff0c;提供了全面的 变量 和 函数 系统&#xff0c;以支持复杂的交互设计和动态内容展示。本文将从专业角度详细解析 Axure 中的 全局变量、中继器数据集变量/函数、元件变量…

MySql的安装及数据库的基本操作命令

1.MySQL的安装 1.1进入MySQL官方网站 1.2点击下载 1.3下拉选择MySQL社区版 1.4选择你需要下载的版本及其安装的系统和下载方式 直接安装以及压缩包 建议选择8.4.4LST LST表明此版本为长期支持版 新手建议选择红框勾选的安装方式 1.5 安装包下载完毕之后点击安装 2.数据库…

树莓派5首次开机保姆级教程(无显示器通过VNC连接树莓派桌面)

第一次开机详细步骤 步骤一&#xff1a;树莓派系统烧录1 搜索打开烧录软件“Raspberry Pi Imager”2 选择合适的设备、系统、SD卡3 烧录配置选项 步骤二&#xff1a;SSH远程树莓派1 树莓派插电2 网络连接&#xff08;有线或无线&#xff09;3 确定树莓派IP地址 步骤三&#xff…

分布式锁—7.Curator的分布式锁

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

电脑睡眠智能管控:定时、依状态灵活调整,多模式随心选

软件介绍 今天要给大家介绍一款十分实用的软件——DontSleep&#xff0c;从名字就能看出&#xff0c;它的核心功能之一是阻止电脑进入睡眠状态&#xff0c;不过它的能耐可远不止于此&#xff0c;还具备强大的定时以及根据电脑实时状况灵活调整的功能。 这款软件采用绿色单文件…