Unity引擎使用HybridCLR(华佗)热更新

news2025/3/10 17:57:17

  大家好,我是阿赵。
  阿赵我做手机游戏已经有十几年时间了。记得刚开始从做页游的公司转到去做手游的公司,在面试的时候很重要的一个点,就是会不会用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加载,就会有问题。
  这就导致一个问题,我们如果想出了安装包之后可以长时间的热更新,不需要重新出包,就必须对可能用到的程序集做好规划,尽量不要去改变了。

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

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

相关文章

Vue项目通过内嵌iframe访问另一个vue页面,获取token适配后端鉴权(以内嵌若依项目举例)

1. 改造子Vue项目进行适配(ruoyi举例) (1) 在路由文件添加需要被外链的vue页面配置 // 若依项目的话是 router/index.js文件 {path: /contrast,component: () > import(/views/contrast/index),hidden: true },(2) 开放白名单 // 若依项目的话是 permission.js 文件 cons…

vue3 vite项目安装eslint

npm install eslint -D 安装eslint库 npx eslint --init 初始化配置&#xff0c;按项目实际情况选 自动生成eslint.config.js&#xff0c;可以添加自定义rules 安装ESLint插件 此时打开vue文件就会标红有问题的位置 安装prettier npm install prettier eslint-config-pr…

Excel·VBA江西省预算一体化工资表一键处理

每月制作工资表导出为Excel后都需要调整格式&#xff0c;删除0数据的列、对工资表项目进行排序、打印设置等等&#xff0c;有些单位还分有“行政”、“事业”2个工资表就需要操作2次。显然&#xff0c;这种重复操作的问题&#xff0c;可以使用VBA代码解决 目录 代码使用说明1&a…

【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…

R软件线性模型与lmer混合效应模型对生态学龙类智力测试数据层级结构应用

全文链接&#xff1a;https://tecdat.cn/?p40925 在生态与生物学研究中&#xff0c;数据常呈现复杂结构特征。例如不同种群、采样点或时间序列的观测数据间往往存在相关性&#xff08;点击文末“阅读原文”获取完整代码、数据、文档&#xff09;。 传统线性模型在处理这类非独…

打造智能聊天体验:前端集成 DeepSeek AI 助你快速上手

DeepSeek AI 聊天助手集成指南 先看完整效果&#xff1a; PixPin_2025-02-19_09-15-59 效果图&#xff1a; 目录 项目概述功能特点环境准备项目结构组件详解 ChatContainerChatInputMessageBubbleTypeWriter 核心代码示例使用指南常见问题 项目概述 基于 Vue 3 TypeScrip…

C语言-语法

数据类型 字符串 C中字符串拼接不用+号,直接使用空格。 char* str = "hello" "world"; 换行链接,加上\就不会报错 char* longStr = "00000000000000000000000000000\ 00000000000000000000000000000"; typedef C 语言提供了 typedef …

Unity组件TrailRenderer屏幕滑动拖尾

Unity组件TrailRenderer屏幕滑动拖尾 介绍制作总结 介绍 今天要做一个拖动效果&#xff0c;正好用到了TrailRenderer这个组件&#xff0c;正好分享一下 效果参考如下&#xff1a; 制作 1.创建空物体TrailObject添加组件TrailRenderer 下面的材质可以根据自己想要制作的效果去…

大模型信息整理

1. Benchmarks Reasoning, conversation, Q&A benchmarks HellaSwagBIG-Bench HardSQuADIFEvalMuSRMMLU-PROMT-BenchDomain-specific benchmarks GPQAMedQAPubMedQAMath benchmarks GSM8KMATHMathEvalSecurity-related benchmarks PyRITPurple Llama CyberSecEval2. 国内外…

【Tools】Windows下Git 2.48安装教程详解

00. 目录 文章目录 00. 目录01. Git简介02. Git参考资料03. Git安装04. Git测试05. 附录 01. Git简介 Git(读音为/gɪt/。)是一个开源的分布式版本控制系统&#xff0c;可以有效、高速的处理从很小到非常大的项目版本管理。 [1] Git 是 Linus Torvalds 为了帮助管理 Linux 内核…

护网中shiro常问的问题

1. 漏洞原理 Apache Shiro 是一个强大的 Java 安全框架&#xff0c;提供身份验证、授权、加密及会话管理功能。Shiro 使用 rememberMe 机制来存储用户会话信息&#xff0c;该机制依赖于加密后的 Cookie。当攻击者能够控制 Cookie 并且服务器使用了不安全的反序列化机制时&…

swift -(5) 汇编分析结构体、类的内存布局

一、结构体 在 Swift 标准库中&#xff0c;绝大多数的公开类型都是结构体&#xff0c;而枚举和类只占很小一部分 比如Bool、 Int、 Double、 String、 Array、 Dictionary等常见类型都是结构体 ① struct Date { ② var year: Int ③ var month: Int ④ …

软件工程笔记下

从程序到软件☆ 章节 知识点 概论☆ 软件的定义&#xff0c;特点&#xff0c;生存周期。软件工程的概论。软件危机。 1.☆软件&#xff1a;软件程序数据文档 &#xff08;1&#xff09;软件&#xff1a;是指在计算机系统的支持下&#xff0c;能够完成特定功能与性能的包括…

【项目日记(九)】细节优化与对比测试

前言 上面我们对申请和释放的过程都已写完&#xff0c;并进行了单线程的联调。本期我们来对一些细节进行优化以及与malloc 进行对比测试。 目录 前言 一、大于256KB的内存申请问题 • 申请过程 • 释放过程 • 简单测试 二、使用定长内存池脱离使用new 三、优化释放对…

PyTorch系列教程:编写高效模型训练流程

当使用PyTorch开发机器学习模型时&#xff0c;建立一个有效的训练循环是至关重要的。这个过程包括组织和执行对数据、参数和计算资源的操作序列。让我们深入了解关键组件&#xff0c;并演示如何构建一个精细的训练循环流程&#xff0c;有效地处理数据处理&#xff0c;向前和向后…

10 【HarmonyOS NEXT】 仿uv-ui组件开发之Avatar头像组件开发教程(一)

温馨提示&#xff1a;本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦&#xff01; 目录 第一篇&#xff1a;Avatar 组件基础概念与设计1. 组件概述2. 接口设计2.1 形状类型定义2.2 尺寸类型定义2.3 组件属性接口 3. 设计原则4. 使用…

C语言——【全局变量和局部变量】

&#x1f680;个人主页&#xff1a;fasdfdaslsfadasdadf &#x1f4d6;收入专栏&#xff1a;C语言 &#x1f30d;文章目入 1.&#x1f680; 全局变量2.&#x1f680; 局部变量3.&#x1f680; 局部和全局变量&#xff0c;名字相同呢? 1.&#x1f680; 全局变量 全局变量&…

浅谈 DeepSeek 对 DBA 的影响

引言&#xff1a; 在人工智能技术飞速发展的背景下&#xff0c;DeepSeek 作为一款基于混合专家模型&#xff08;MoE&#xff09;和强化学习技术的大语言模型&#xff0c;正在重塑传统数据库管理&#xff08;DBA&#xff09;的工作模式。通过结合其强大的自然语言处理能力、推理…

DeepSeek-R1本地化部署(Mac)

一、下载 Ollama 本地化部署需要用到 Ollama&#xff0c;它能支持很多大模型。官方网站&#xff1a;https://ollama.com/ 点击 Download 即可&#xff0c;支持macOS,Linux 和 Windows&#xff1b;我下载的是 mac 版本&#xff0c;要求macOS 11 Big Sur or later&#xff0c;Ol…

Java面试第九山!《SpringBoot框架》

引言 你是否经历过这样的场景&#xff1f;想快速开发一个Java Web应用&#xff0c;却被XML配置、依赖冲突、服务器部署搞得焦头烂额。Spring Boot的诞生&#xff0c;正是为了解决这些"配置地狱"问题。 对比项Spring Boot传统 Spring配置复杂度自动配置&#xff0c;…