Unity热更新

1,热更新的概念与作用


app更新通常分为两类,一种是整包更新(换包),一种是热更新(不换包,通过网络下载,动态更新资源等)。

  • 整包更新,是指在需要更新时,需要用户手动到应用商店或官方网站下载新版本安装包并重新安装的一种更新方式,该方式成本较高,一般只有在无法热更新时才使用。
  • 热更新,是指在不需要重新编译发布应用程序的情况下,通过远程更新服务器向客户端推送程序代码、资源文件等数据的一种技术手段,以修复程序漏洞、优化游戏性能、更新游戏内容等。

热更新又分资源热更新和代码热更新,资源热更新较为简单,一般的app都可实现,而代码热更新,由于考虑到安全性,代码编译等问题,实现起来较为困难,一种实用的方法就是把代码当成资源。Unity热更新就是把代码(如Lua代码)打包成AssetBundle,达到和其它资源一样的更新效果。

在当今快节奏时代,app更新频繁,尤其是游戏app,如果每次更新都需要换包,十分影响用户体验,极易造成用户流失,代价成本实在太高,因此app热更新是十分必要的。


如果有了热更新,会带来什么好处呢?

  • 提高用户体验。热更新可以实现及时修复bug和添加新功能等,减少了玩家等待更新的时间和下载流量,提高了用户的体验感。
  • 降低开发成本。热更新可以在不重新打包的情况下实现游戏的更新,避免了频繁发布新版本的成本和风险。
  • 提高迭代效率。热更新可以快速地进行游戏内容的调整和修改,加快了游戏迭代周期,提高了开发效率。

2,热更新原理

热更新,是通过把最新的资源或代码放到网络服务器,app检测到需要更新版本时,通过网下载资源或代码到本地包,将新的代码或资源加载到应用程序中,以替换旧的代码或资源。

Unity以C#为主要开发语言,如何能做到代码的热更新呢?
C#是编译型语言,Unity在打包后,会将C#编译成一种中间代码IL,后续对这些IL的编译方式不同可以分为AOT和JIT,最终编译为各平台的NativeCode,在没有特殊处理的情况下,无法直接通过替换NativeCode,来达成热更新的。
一种理想化的C#热更新流程是:

  • 把需要更新的代码编译成动态链接库
  • 游戏启动时加载新的动态链接库
  • 用反射的形式获取动态链接库中的实例或方法

这种模式在PC和Android平台是可以的,但在IOS平台是不可行的。因为IOS对申请的内存禁止了可执行权限,所以运行时创建/加载的NativeCode是无法执行的。


为了解决IOS上的热更新问题,有两个主流方案:ILRuntime 和 HybridCLR。


ILRuntime

Unity会把C#代码打包成DLL,ILRuntime在运行时用自己的解释器来解释IL并执行,而不是直接调用.NET FrameWork或Mono虚拟机来运行代码。它借助Mono.Cecil库来读取DLL的PE信息,以及当中类型的所有信息,最终得到方法的IL汇编码,然后通过内置的IL解译执行虚拟机来执行DLL中的代码。
但是ILRuntime会有不少限制

  • ILRuntime和原始的 compiler是两套东西,也就是说你的热更DLL和主工程的DLL实质是不互通的(如热更DLL中一个类要继承主工程DLL的一个类),所以就存在跨域问题,需要写委托适配器,委托转换器。在发布版本后这些不能热更,使用之前一定要预留好可能会使用的
  • 部分 C# 语法不支持:由于 ILRuntime 是基于 Mono 实现的,而 Mono 不支持所有 C# 语法,所以 ILRuntime 在某些 C# 语法方面也有限制,比如属性、泛型委托、可选参数等
  • 需要特殊处理的代码:由于 ILRuntime 的实现方式,一些特殊的代码需要进行特殊处理,比如反射、LINQ、协程等
  • 性能问题:由于 ILRuntime 需要动态解析和执行代码,相对于编译时静态绑定的方式,其性能会有一定程度的下降。同时,在使用过程中也需要注意避免频繁的跨域调用和反射操作,以免影响性能
  • ILRuntime对多线程Thread不兼容,在热更代码里使用多线程会导致Unity崩溃闪退

HybridCLR

是一个特性完整、零成本、高性能、低内存的近乎完美的Unity全平台原生c#热更方案。
IL2CPP是一个纯静态的AOT运行时,不支持运行时加载dll,因此不支持热更新。HybridCLR扩充了IL2CPP的代码,使其由纯AOT Runtime变成“AOT+Interpreter”混合Runtime,进而原生支持动态加载Assembly,使得基于IL2CPP打包的游戏不仅能在Android平台,也能在IOS、Consoles等限制了JIT的平台上高效地以AOT+interpreter混合模式执行。


HybridCLR是近年来一种划时代的Unity原生C#热更新技术,见https://hybridclr.doc.code-philosophy.com/

相比于直接热更新C#代码,使用C#+Lua脚本的热更新方案是目前最主流的实现方式。
Lua是一种跨平台的脚本语言,它主要依赖解释器和虚拟机实现跨平台功能,Lua是解释型语言,并不需要事先编译,而是运行时动态解释执行的。这样Lua就和普通的游戏资源如图片,文本没有区别。由于解释器和虚拟机都是跨平台的,lua脚本也就可以在不同的平台上运行了。
本质上就是利用相关插件(如ulua、slua、tolua、xlua等)提供一个Lua的运行环境(虚拟机),为Unity提供Lua编程的能力,让C#和Lua可以相互调用和访问。

3,xLua热更新方案

xLua是腾讯一个开源项目,xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用。
xLua在功能、性能、易用性都有不少突破,这几方面分别最具代表性的是:

  • 可以运行时把C#实现(方法,操作符,属性,事件等等)替换成lua实现;
  • 编辑器下无需生成代码,开发更轻量;
  • 出色的GC优化,自定义struct,枚举在Lua和C#间传递无C# gc alloc;

下载地址:https://github.com/Tencent/xLua


4,xLua的简单使用

4.1,xLua安装使用

xLua下载后,将xLua文件中的Assets文件夹下的文件放到项目中的Assets文件下,就完成了XLua的安装。
新建C#代码LuaManager.cs

using UnityEngine;
using XLua;public class LuaManager : MonoBehaviour
{LuaEnv m_luaEnv;void Start(){m_luaEnv = new LuaEnv();m_luaEnv.DoString("print('Hello World')");}
}

新建场景,挂上LuaManager.cs,运行,看到打印 Hello World ,则安装成功了

4.2,自定义Lua加载器

要想执行lua文件,就要用上Lua加载器了,修改LuaManager.cs


using System;
using System.IO;
using UnityEngine;
using XLua;public class LuaManager : MonoBehaviour
{public static string LuaDir = "src"; // 存放lua文件的位置,Assets根目录下LuaEnv m_luaEnv;Action m_startAction;Action m_updateAction;void Start(){m_luaEnv = new LuaEnv();m_luaEnv.AddLoader(new LuaEnv.CustomLoader(this.LuaLoaderFromRes));// 请求执行src下的Main.lua文件m_luaEnv.DoString("require('Main')", "chunk");LuaTable luaTable = this.m_luaEnv.Global.Get<LuaTable>("Main");if (luaTable != null){m_startAction = luaTable.Get<Action>("Start");m_updateAction = luaTable.Get<Action>("Update");}// 执行Main.lua Start方法m_startAction?.Invoke();}void Update(){// 执行Main.lua Update方法m_updateAction?.Invoke();}private byte[] LuaLoaderFromRes(ref string filePath){filePath = filePath.Replace('.', '/');if (!filePath.EndsWith(".lua")){filePath += ".lua";}#if UNITY_EDITORstring path = Application.dataPath + "/" + LuaDir + "/" + filePath;if (File.Exists(path)){//读取路径下的文件的值以字节形式返回return File.ReadAllBytes(path);}
#endif// TODO// android ios 等平台读取lua文件return null;}
}

在Assets目录下新建文件夹src,src文件夹下新建文件Main.lua

Main = {}
setmetatable(Main, {__index = _G})
local _ENV = Mainfunction Start()print("Lua Start")
endfunction Update()-- TODO
endreturn Main

运行,看到打印 Lua Start ,表示成功,Update()可增加每帧的逻辑,可在src下继续增加其它lua文件

4.3,Lua调用C#

[LuaCallCSharp],在C#类加上标签[LuaCallCSharp],就可在Lua中访问了
新建C#代码GameTest.cs

using UnityEngine;
using XLua;namespace MyGame
{[LuaCallCSharp] // 建立Lua调用C#的映射public class GameTest : MonoBehaviour{public string Name;void Start(){Debug.Log("Name:" + Name);}public void CallTest(string text){Debug.Log("Lua Call:" + text);}}
}

修改Main.lua

Main = {}
setmetatable(Main, {__index = _G})
local _ENV = Mainfunction Start()print("Lua Start")-- 访问C#的类,使用CS + 命名空间 + 类名local go = CS.UnityEngine.GameObject("LuaGameObject")local test = go:AddComponent(typeof(CS.MyGame.GameTest))test.Name = "Game Test"-- 调用方法,使用:test:CallTest("666")
endfunction Update()-- TODO
endreturn Main

如果不想在每个类中加标签[LuaCallCSharp],也可以参考XLua/Editor/ExampleConfig,集中配置。
注意,如果需要打包,需提前生成Wrap文件,执行菜单命令:XLua/Generate Code

至于C#调用Lua,4.2代码已有了,更详细的参考官方例子

推荐一个基于xLua的Unity游戏纯lua客户端完整框架:https://github.com/smilehao/xlua-framework

5,xLua可热更规则:

  • 进入lua层后的一切逻辑、资源都可热更
  • app中基本所有的资源(图片、声音、3d模型、动作、特效、文本文件)、lua代码都可热更
  • C#层代码不可热更(也不完全不能,xlua.hotfix可以修改C#代码的执行,替换原来的逻辑,但这是lua代码,不是直接修改C#)
  • 需要新增或修改的代码必须是C#代码则不可热更

6,热更新流程

6.1,更新前准备

  1. 打包AssetBundle,打包程序会比较Unity所有资源,与上次打包后对比实现增量打包,生成md5信息文件(assetbundlemd5.txt),版本信息文件(version.txt),递增资源版本号
  2. 上传AssetBundle,assetbundlemd5.txt,version.txt到网络服务器(cdn)
  3. 停服或后台通知用户在线更新

6.2,更新流程

  1. 启动app,下载版本信息文件version.txt
  2. 版本号的比较,如果版本号不同才继续以下流程
  3. 下载资源服务器上的md5对比文件(assetbundlemd5.txt)
  4. 确定下载列表,将最新下载的md5对比文件和本地旧md5对比文件对比,记录缺少或不同的文件。(assetbundlemd5.txt中的md5码实现此步骤)
  5. 根据下载列表,下载所需的资源。(一般放在Application.persistentDataPath)
  6. 保证下载成功后,用最新的md5对比文件覆盖本地的md5对比文件(更新assetbundlemd5.txt),记录最新的版本号

7,Unity热更新实现

版本信息文件version.txt

{"code":0,"data":{"isUpdateClient":0,"isUpdateRes":1,"version":"1.0","resVersion":"1.0.0.1","clientUrl":"https://aa.bb.cc.com/game/client.apk","resUrl":"https://aa.bb.cc.com/game/res/"}
}

这是version.txt的结构参考,JSON格式,字段说明:

  • isUpdateClient:是否强制更新整包,视情况是否打开
  • isUpdateRes:是否更新资源开头,无特殊情况都是打开
  • version:客户端版本号,如果此版本号对比不一样,需考虑更新整包
  • resVersion:资源版本号,如果此版本号对比不一样,则进行热更新,每次打包递增
  • clientUrl:客户端整包的更新地址,可根据后缀,跳转网站,或直接下载安装
  • resUrl:热更新资源的地址

App启动,下载version.txt,版本比较代码

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;public class GameStart : MonoBehaviour
{public string VersionUrl = "https://aa.bb.cc.com/game/version.txt"; // version.txt网络服务器地址public string appVersion = "1.0"; // 当前客户端版本号public string currentResVersion = "1.0.0.1"; // 当前最新资源版本号void Start(){// app 启动前逻辑,如读取客户端版本号,最新资源版本号//currentResVersion = PlayerPrefs.GetString("currentResVersion");StartCoroutine(RequestVersionInfo());}IEnumerator RequestVersionInfo(){// 加上时间戳,确保下载的是最新文件UnityWebRequest request = new UnityWebRequest(VersionUrl + "?time=" + System.DateTime.Now.Ticks);request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();yield return request.SendWebRequest();if (request.error == null){string text = request.downloadHandler.text;LitJson.JsonData versionInfo = LitJson.JsonMapper.ToObject(text);int isUpdateClient = (int)versionInfo["data"]["isUpdateClient"];int isUpdateRes = (int)versionInfo["data"]["isUpdateRes"];string version = (string)versionInfo["data"]["version"];string clientUrl = (string)versionInfo["data"]["clientUrl"];string resUrl = (string)versionInfo["data"]["resUrl"];string resVersion = (string)versionInfo["data"]["resVersion"];if (isUpdateClient == 1){if (compareResVersion(version, appVersion)){// 提示客户端更新// Application.OpenURL(clientUrl);}}if (isUpdateRes == 1){if (compareResVersion(resVersion, currentResVersion)){// 进入热更新;//StartHotUpdate(resUrl);}}}request.Dispose();}public bool compareResVersion(string resVersion1, string resVersion2){var arr1 = resVersion1.Split('.');var arr2 = resVersion2.Split('.');for (int i = 0; i < arr1.Length; i++){if (int.Parse(arr1[i]) > int.Parse(arr2[i])){return true;}}return false;}}

热更新流程代码

IEnumerator StartHotUpdate(string resUrl)
{bool downloadFailed = false;// 下载网络服务器最新md5信息文件string md5Url = resUrl + "assetbundlemd5.txt";UnityWebRequest md5Request = new UnityWebRequest(md5Url + "?version=" + currentResVersion); // 加上版本号,确保下载的是最新文件md5Request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();yield return md5Request.SendWebRequest();if (md5Request.error == null){AssetBundleMD5Infos remoteMd5_info = new AssetBundleMD5Infos(md5Request.downloadHandler.data); // 网络服务器最新md5信息文件AssetBundleMD5Infos tmpMd5_info; // 由于出错中断暂时保存的md5信息文件string dirPath = Application.persistentDataPath + "/" + Utility.GetPlatformName();if (!Directory.Exists(dirPath)){Directory.CreateDirectory(dirPath);}dirPath = dirPath + "/";if (File.Exists(dirPath + "assetbundlemd5.tmp")){byte[] fileContent = File.ReadAllBytes(dirPath + "assetbundlemd5.tmp");tmpMd5_info = new AssetBundleMD5Infos(fileContent);}else{tmpMd5_info = new AssetBundleMD5Infos(null);}List<string> needUpdateAbs = new List<string>(); // 需要下载更新的ab文件列表foreach (var abName in remoteMd5_info.m_AssetBundleMD5.Keys){string remoteMd5 = remoteMd5_info.GetAssetBundleMD5(abName);// 与网络服务器最新md5比较,不同则加载下载更新列表,AssetBundleManager.GetAssetBundleMD5(abName)本地最新md5if (tmpMd5_info.GetAssetBundleMD5(abName) != remoteMd5 && remoteMd5 != AssetBundleManager.GetAssetBundleMD5(abName)){needUpdateAbs.Add(abName);}}// 下载更新的ab文件foreach (string abName in needUpdateAbs){UnityWebRequest abRequest = new UnityWebRequest(resUrl + abName + "?version=" + currentResVersion); // 加上版本号,确保下载的是最新文件abRequest.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();yield return abRequest.SendWebRequest();if (abRequest.error == null){// 保存到最新的ab文件到本地File.WriteAllBytes(dirPath + abName, abRequest.downloadHandler.data);tmpMd5_info.AddAssetBundleMD5(abName, remoteMd5_info.GetAssetBundleMD5(abName), remoteMd5_info.GetAssetBundleSize(abName), remoteMd5_info.GetAssetBundleMiniGameId(abName));} else{downloadFailed = true;}abRequest.Dispose();}if (needUpdateAbs.Count > 0){if (!downloadFailed){// 保存最新的md5文件remoteMd5_info.SerializeToFile(dirPath + "assetbundlemd5.txt");File.Delete(dirPath + "assetbundlemd5.tmp");}else{tmpMd5_info.SerializeToFile(dirPath + "assetbundlemd5.tmp"); // 出错中断保存临时的md5,避免下次更新重新下载}}}else{downloadFailed = true;}md5Request.Dispose();if (downloadFailed){// 出错重新执行更新流程StartCoroutine(StartHotUpdate(resUrl));}
}

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

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

相关文章

LangChain+LLM实战---实用Prompt工程讲解

原文&#xff1a;Practical Prompt Engineering 注&#xff1a;本文中&#xff0c;提示和prompt几乎是等效的。 这是一篇非常全面介绍Prompt的文章&#xff0c;包括prompt作用于大模型的一些内在机制&#xff0c;和prompt可以如何对大模型进行“微调”。讲清楚了我们常常听到的…

flink状态不能跨算子

背景 在flink中进行状态的维护和管理应该是我们经常做的事情&#xff0c;但是有些同学认为名称一样的状态在不同算子之间的状态是同一个&#xff0c;事实是这样吗&#xff1f; flink状态在保存点中的存放示意图 事实上&#xff0c;每个状态都归属于对应的算子&#xff0c;也…

thinkphp漏洞复现

thinkphp漏洞复现 ThinkPHP 2.x 任意代码执行漏洞Thinkphp5 5.0.22/5.1.29 远程代码执行ThinkPHP5 5.0.23 远程代码执行ThinkPHP5 SQL Injection Vulnerability && Sensitive Information Disclosure VulnerabilityThinkPHP Lang Local File Inclusion ThinkPHP 2.x 任…

xlua源码分析(二)lua Call C#的无wrap实现

xlua源码分析&#xff08;二&#xff09;lua Call C#的无wrap实现 上一节我们主要分析了xlua中C# Call lua的实现思路&#xff0c;本节我们将根据Examples 03_UIEvent&#xff0c;分析lua Call C#的底层实现。例子场景里有一个简单的UI面板&#xff0c;面板中包含一个input fie…

rabbitmq的confirm模式获取correlationData为null解决办法

回调函数confirm中的correlationDatanull // 实现confirm回调,发送到和没发送到exchange,都触发 Override public void confirm(CorrelationData correlationData, boolean ack, String cause) {// 参数说明:// correlationData: 相关数据,可以在发送消息时,进行设置该参数// …

合并两个数组并排序去重 | C语言代码

题目&#xff1a; 给定2个数组&#xff0c;要求把他们合并成1个非降序序列&#xff0c;并且输出去重后的序列。 输入格式: 输入有4行。 第1行是一个正整数m&#xff0c;表示第2行有m个整数&#xff0c;这些整数构成一个非降序序列&#xff0c;每个整数之间以空…

Unity之UI、模型跟随鼠标移动(自适应屏幕分辨率、锚点、pivot中心点)

一、效果 UI跟随鼠标移动, 动态修改屏幕分辨率、锚点、pivot等参数也不会受到影响。同时脚本中包含3d物体跟随ui位置、鼠标位置移动 二、屏幕坐标、Canvas自适应、锚点、中心点 在说原理之前我们需要先了解屏幕坐标、Canvas自适应、锚点、中心的特性和之间的关系。 1.屏幕坐标…

软考 系统架构设计师之考试感悟

今天是2023年11月4号&#xff0c;是软考系统架构设计师考试的正日子。考了一天&#xff0c;身心俱疲&#xff0c;但更多的是暮鼓晨钟般的教训和感悟。下边将今天的感悟写在这里&#xff0c;以资自己及后来者借鉴。 我是从今年7月底8月初开始看教材 ——《系统架构设计师教程》…

家庭私人影院 - Windows搭建Emby媒体库服务器并远程访问 「无公网IP」

文章目录 1.前言2. Emby网站搭建2.1. Emby下载和安装2.2 Emby网页测试 3. 本地网页发布3.1 注册并安装cpolar内网穿透3.2 Cpolar云端设置3.3 Cpolar内网穿透本地设置 4.公网访问测试5.结语 1.前言 在现代五花八门的网络应用场景中&#xff0c;观看视频绝对是主力应用场景之一&…

webgoat-(A1)injection

SQL Injection (intro) SQL 命令主要分为三类&#xff1a; 数据操作语言 &#xff08;DML&#xff09;DML 语句可用于请求记录 &#xff08;SELECT&#xff09;、添加记录 &#xff08;INSERT&#xff09;、删除记录 &#xff08;DELETE&#xff09; 和修改现有记录 &#xff…

MySQL - 库的操作

目录 1.库的操作1.1创建数据库1.2创建数据库案例 2.字符集和校验规则3.操纵数据库4.备份和恢复5.查看连接情况 1.库的操作 1.1创建数据库 语法&#xff1a; CREATE DATABASE [IF NOT EXISTS] db_name [create_specification [, create_specification] ...] create_specifica…

[架构之路-254/创业之路-85]:目标系统 - 横向管理 - 源头:信息系统战略规划的常用方法论,为软件工程的实施指明方向!!!

目录 总论&#xff1a; 一、数据处理阶段的方法论 1.1 企业信息系统规划法BSP 1.1.1 概述 1.1.2 原则 1.2 关键成功因素法CSF 1.2.1 概述 1.2.2 常见的企业成功的关键因素 1.3 战略集合转化法SST&#xff1a;把战略目标转化成信息的集合 二、管理信息系统阶段的方法论…

十年JAVA搬砖路——Linux搭建Ldap服务器。

1.安装命令 yum -y install openldap compat-openldap openldap-clients openldap-servers openldap-servers-sql openldap-devel2.启动ldap systemctl start slapd systemctl enable slapd3.修改密码 slappasswd Aa123456获得返回的密码加密密码串&#xff1a; {SSHA}DkSw0…

二维码智慧门牌管理系统升级:一键报警让你的生活更安全!

文章目录 前言一、升级解决方案的特点二、实施步骤 前言 随着科技的不断进步&#xff0c;我们的生活正在逐渐变得更加智能化。可以想象一下&#xff0c;如果你家的门牌也能拥有这种智能升级&#xff0c;将会带来怎样的改变&#xff1f;今天&#xff0c;让我们一起探讨这令人兴…

MySQL基础『数据库基础』

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; MySQL 学习 &#x1f383;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 &#x1f381;软件版本&#xff1a; MySQL 5.7.44 文章目录 1.数据库概念1.1.什么是数据库1.2.数据库存储介质1.3.常见数据库 2.数…

用于3D Visual Grounding的多模态场景图

文章目录 引言方法1. Language Scene Graph Module Paper&#xff1a;《Free-form Description Guided 3D Visual Graph Network for Object Grounding in Point Cloud》【ICCV’2021】 Code&#xff1a;https://github.com/PNXD/FFL-3DOG 引言 3DVG任务有以下三个挑战&#x…

hadoop配置文件自检查(解决常见报错问题,超级详细!)

本篇文章主要的内容就是检查配置文件&#xff0c;还有一些常见的报错问题解决方法&#xff0c;希望能够帮助到大家。 一、以下是大家可能会遇到的常见问题&#xff1a; 1.是否遗漏了前置准备的相关操作配置&#xff1f; 2.是否遗的将文件夹(Hadoop安装文件夹&#xff0c;/dat…

高性能网络编程 - 关于单台服务器并发TCP连接数理论值的讨论

文章目录 概述操作系统的限制因素文件句柄限制1. 进程限制2. 全局限制 端口号范围限制 概述 单台服务器可以支持的并发TCP连接数取决于多个因素&#xff0c;包括硬件性能、操作系统限制、网络带宽和应用程序设计。以下是一些影响并发TCP连接数的因素&#xff1a; 服务器硬件性…

文本生成评估指标简单介绍BLEU+ROUGE+Perplexity+Meteor 代码实现

以下指标主要针对两种&#xff1a;机器翻译和文本生成&#xff08;文章生成&#xff09;&#xff0c;这里的文本生成并非是总结摘要那类文本生成&#xff0c;仅仅是针对生成句子/词的评价。 首先介绍BLEU&#xff0c;ROUGE, 以及BLEU的改进版本METEOR&#xff1b;后半部分介绍P…

Go 多版本管理

在日常开发工作过程中&#xff0c;很多时候我们都需要在自己的机器上安装多个go版本&#xff0c;像是go1.16引入的embed&#xff0c;go1.18引入了泛型&#xff1b;又或是自己本地使用的是最新版&#xff0c;但公司的项目中使用的go1.14、go1.13甚至是更早的版本。 那么有没有既…