☰
微信小游戏 Unity 适配器 xLua 复杂值类型(struct)GC 优化完整指南
2026/10/3 1:44:05 网站建设 项目流程
  • 游戏开发
  • 移动开发
  • WebAssembly

【免费下载链接】minigame-unity-webgl-transform

微信小游戏Unity引擎适配器文档。

项目地址:https://gitcode.com/GitHub_Trending/mi/minigame-unity-webgl-transform
点击查看免费下载

xLua 中,C# 复杂值类型(struct)默认以引用方式传递到 Lua 侧,每次传递都会产生一次 boxing 与一次 C# GC 分配。本文以 xLua 复杂值类型(struct)gc 优化指南 为骨架,结合仓库内 xLua 的配置、无 GC 示例 与生成器源码 Generator.cs,系统讲解 struct GC 优化方案的工作原理、满足条件、三种配置方式与落地步骤,帮助读者在微信小游戏场景下把 Vector 系列、Quaternion、Color 等值类型在 Lua 与 C# 之间的传递彻底做到无 GC 分配。

复杂值类型为什么会产生 GC

在 xLua 中,复杂值类型(struct)的默认传递方式是引用传递。这个机制的工作过程是:

  1. 值类型需要先进行boxing(装箱),在托管堆上创建一个新对象;
  2. 将装箱后的引用传递给 Lua 侧使用;
  3. Lua 侧使用完毕、释放该对象的引用时,触发一次 C# GC。

由于每次 boxing 都会产生一个新对象,因此只要 struct 在 Lua 与 C# 之间来回传递一次,托管堆上就会多出一个待回收的垃圾对象。在高频调用(如每帧更新位置、逐物体同步坐标)的场景下,这类分配会迅速累积,直接表现为 GC 频率上升、CPU 峰值抖动,在内存更敏感的微信小游戏运行时(WASM)上影响尤为明显。

为此,xLua 实现了一套struct 的 GC 优化方案:只要通过简单配置,满足条件的 struct 在传递到 Lua 侧时即可做到无 GC。

struct 需要满足什么条件

要让一个 struct 享受到 GC 优化,需要同时满足以下三个条件:

1. 字段类型约束

struct 允许嵌套其它 struct,但它以及它嵌套的 struct 只能包含以下基本类型:

byte、sbyte、short、ushort、int、uint、long、ulong、float、double

例如 Unity 定义的大多数值类型(Vector 系列、Quaternion、Color 等)以及用户自定义的满足上述字段约束的 struct 均符合条件。字段不能包含 string、引用类型对象或其它不符合约束的类型。

2. 配置 GCOptimize 属性

该 struct 必须配置GCOptimize属性。对于常用的 UnityEngine 值类型(Vector 系列、Quaternion、Color 等)xLua 已经内置配置好了,用户无需重复配置;用户自定义 struct 则需要自行配置。属性可以通过配置文件或C# Attribute两种方式实现(详见下文)。

3. 加入生成代码列表

使用到该 struct 的地方(struct 自身、相关 delegate、相关数组等)需要添加到生成代码列表,并执行代码生成,让 xLua 为该类型生成专门的 GC 优化包装代码。

如何配置:三种方式详解

xLua 的所有配置都支持三种方式:打标签(Attribute)、静态列表、动态列表。GCOptimize同样遵循这一规则。下面逐一展开。

方式一:C# Attribute 打标签

直接为 struct 类型打上GCOptimizeAttribute(同时建议打上LuaCallCSharp)。仓库示例 05_NoGc/NoGc.cs 中的自定义 struct 就是这样配置的:

using XLua; [GCOptimize] [LuaCallCSharp] public struct Pedding { public byte c; } [GCOptimize] [LuaCallCSharp] public struct MyStruct { public MyStruct(int p1, int p2) { a = p1; b = p2; c = p2; e.c = (byte)p1; } public int a; public int b; public decimal c; public Pedding e; }

注意MyStruct嵌套了同样打了GCOptimize标签的Pedding,且所有字段均为值类型,完全符合前述字段类型约束。从属性定义源码 GenAttributes.cs 可以看到GCOptimizeAttribute还带一个可选的OptimizeFlag参数,默认值为OptimizeFlag.Default:

[Flags] public enum OptimizeFlag { Default = 0, PackAsTable = 1 } public class GCOptimizeAttribute : Attribute { OptimizeFlag flag; public OptimizeFlag Flag { get { return flag; } } public GCOptimizeAttribute(OptimizeFlag flag = OptimizeFlag.Default) { this.flag = flag; } }

方式二:静态列表配置

有时我们无法直接给一个类型打标签(例如系统 API、没有源码的第三方库中的类型),这时可以在一个静态类里声明一个静态字段,字段类型只要实现了IEnumerable<Type>即可(BlackList和AdditionalProperties例外),再为这个字段打上GCOptimize标签:

using System; using System.Collections.Generic; using XLua; public static class MyGCOptimizeConfig { [GCOptimize] public static List<Type> gc_optimize_list = new List<Type>() { typeof(MyNamespace.MyStruct), typeof(MyNamespace.MyStruct2), }; }

字段需要放在静态类中,且建议放在Editor 目录下。这也是 xLua 对几个 UnityEngine 值类型的官方内置配置方式。在 GenAttributes.cs 的SysGenConfig类中可以看到:

public static class SysGenConfig { [GCOptimize] static List<Type> GCOptimize { get { return new List<Type>() { typeof(UnityEngine.Vector2), typeof(UnityEngine.Vector3), typeof(UnityEngine.Vector4), typeof(UnityEngine.Color), typeof(UnityEngine.Quaternion), typeof(UnityEngine.Ray), typeof(UnityEngine.Bounds), typeof(UnityEngine.Ray2D), }; } } ... }

因此 Vector2/Vector3/Vector4、Color、Quaternion、Ray、Bounds、Ray2D 这些常用值类型无需任何额外配置即可享受 GC 优化,直接满足原文档中"UnityEngine 的几个 struct 已经配置了该属性"的说明。

方式三:动态列表配置

如果希望按名字空间、按程序集等规则动态收集类型,可以声明一个打了相应标签的静态属性。GCOptimize同样支持这种方式:

using System; using System.Collections.Generic; using System.Linq; using XLua; public static class MyGCOptimizeConfig { [GCOptimize] public static IEnumerable<Type> by_property { get { return from type in Assembly.Load("Assembly-CSharp").GetTypes() where type.Namespace == "MyGame.Data" && type.IsValueType select type; } } }

Getter 是代码,可以实现很多效果,比如按名字空间配置、按程序集配置等等。该属性同样需要放到静态类中,建议放到Editor 目录。

配置通用建议(来自 xLua 的配置):列表方式均必须是static的字段/属性,且必须放到一个static类;建议尽量不用标签方式(il2cpp 下会增加不少代码量),建议列表方式配置放在 Editor 目录。

使用到该 struct 的地方也要加入生成列表

除了 struct 本身,凡是在 Lua 与 C# 之间传递该 struct 的入口,也需要加入生成代码列表,才能让无 GC 路径真正生效。这些入口包括:

  • 携带 struct 参数的delegate(C# 调用 Lua 函数时)——需要打CSharpCallLua;
  • 携带 struct 参数的 C# 方法——所在的类型需要打LuaCallCSharp;
  • struct 数组——数组访问路径同样走 GC 优化代码。

仓库 05_NoGc/NoGc.cs 给出了完整示例,把 struct、枚举、delegate、数组全部串联起来:

[GCOptimize] [LuaCallCSharp] public struct MyStruct { ... } [LuaCallCSharp] public enum MyEnum { E1, E2 } [CSharpCallLua] public delegate int IntParam(int p); [CSharpCallLua] public delegate Vector3 Vector3Param(Vector3 p); [CSharpCallLua] public delegate MyStruct CustomValueTypeParam(MyStruct p); [CSharpCallLua] public delegate MyEnum EnumParam(MyEnum p); [CSharpCallLua] public delegate decimal DecimalParam(decimal p); [CSharpCallLua] public delegate void ArrayAccess(Array arr);

配置完成后,需要执行XLua/Generate Code菜单生成代码。生成器源码 Generator.cs 中的逻辑印证了收集流程:GCOptimizeList收集所有被GCOptimizeAttribute标注的类型(包括SysGenConfig静态列表里的内置类型),随后通过GenPackUnpack(types, save_path)为这些类型生成解包/打包代码,最终输出到Assets/XLua/Gen/目录(路径可通过CSObjectWrapEditor.GenPath配置修改)。

无 GC 场景与示例验证

完成配置后,以下场景中的值类型传递均不产生(C#)GC 分配,来自 FAQ 与官方无 GC 示例的验证:

  1. 所有基本值类型(所有整数、所有浮点数、decimal)——本身无 GC;
  2. 所有枚举类型——需要把该枚举加入GCOptimize;
  3. 字段只包含值类型的 struct(可嵌套其它只包含值类型的 struct)——需要把该 struct 加入GCOptimize。

使用方式包括:

  • 用 delegate 调用 Lua 函数、传值类型参数;
  • 用LuaTable、LuaFunction的无 GC 接口(如Func<T1, T2, TResult>)传值类型;
  • 传递值类型数组(如Vector3[]、自定义 struct 数组)并允许 Lua 侧直接读写数组元素;
  • 通过 interface(CSharpCallLua)让 Lua 实现回调并交换数组内容;
  • 通过LuaEnv.Global.Set/Get在 Lua 与 C# 之间传递 struct 值。

NoGc 示例的运行时验证代码展示了几条关键路径(NoGc.cs):

// C# 通过 delegate 调用 Lua,传 Vector3 / 自定义 struct / 枚举,均无 GC f2(new Vector3(1, 2, 3)); MyStruct mystruct1 = new MyStruct(5, 6); f3(mystruct1); f4(MyEnum.E1); // Lua 访问 C# 值类型数组,无 GC(含 primitive / Vector3 / 自定义 struct / enum / decimal 数组) farr(a1); farr(a2); farr(a3); farr(a4); farr(a5); // Lua 侧也可用 table 构造 struct 参数传入 C#,例如 -- local r = monoBehaviour:Vector3ParamMethod({x = 1, y = 2, z = rnd}) -- local r = monoBehaviour:StructParamMethod({a = 1, b = rnd, e = {c = rnd}})

Lua 侧同样可以无 GC 地读写 C# 的 struct 数组元素,例如交换Vector3[]中两个元素:

function array_exchange(arr) arr[0], arr[1] = arr[1], arr[0] end

进阶:AdditionalProperties 处理私有字段

默认情况下,GCOptimize只对public 的 field打解包。如果某些 struct 把字段做成私有、通过 property 对外访问,就需要用到XLua.AdditionalProperties这一扩展配置。

标签方式比较简单(在对应属性上加标签即可);配置方式要求是Dictionary<Type, List<string>>类型,Key 是要生效的类型,Value 是属性名列表。可参考 xLua 对几个 UnityEngine 值类型的内置配置SysGenConfig类(GenAttributes.cs):

[AdditionalProperties] static Dictionary<Type, List<string>> AdditionalProperties { get { return new Dictionary<Type, List<string>>() { { typeof(UnityEngine.Ray), new List<string>() { "origin", "direction" } }, { typeof(UnityEngine.Ray2D), new List<string>() { "origin", "direction" } }, { typeof(UnityEngine.Bounds), new List<string>() { "center", "extents" } }, }; } }

可以看到Ray、Ray2D、Bounds这类 struct 虽然底层字段是私有(如m_Origin、m_Direction、m_Center、m_Extents),但通过AdditionalProperties把origin、direction、center、extents属性纳入 GC 优化的解包范围,从而让这些类型的完整属性访问也做到无 GC。

实操清单与注意事项

在微信小游戏 Unity 工程中落地 struct GC 优化,按以下步骤执行即可:

  1. 确认字段约束:struct(含嵌套 struct)只包含byte/sbyte/short/ushort/int/uint/long/ulong/float/double字段;
  2. 配置 GCOptimize:自定义 struct 打[GCOptimize]标签,或在 Editor 目录的静态类中通过静态列表/动态列表配置;UnityEngine 的 Vector 系列、Quaternion、Color 等已内置配置,无需处理;
  3. 登记使用入口:把携带该 struct 的 delegate/interface 加入CSharpCallLua,把暴露该 struct 方法的 C# 类型加入LuaCallCSharp;
  4. 执行代码生成:在 Unity 菜单执行XLua/Generate Code,生成代码默认输出到Assets/XLua/Gen/;
  5. 按无 GC 接口调用:优先使用 delegate 调用、LuaTable/LuaFunction无 GC 接口与数组访问路径;
  6. 私有字段场景:如 struct 通过 property 暴露私有字段,用AdditionalProperties补充配置。

需要注意的前提与限制:

  • 字段包含 string、引用类型等不符合约束的 struct无法启用 GC 优化,会退回默认的引用传递(boxing + 释放)路径;
  • GC 优化依赖生成代码,配置变更后必须重新执行 Generate Code;
  • 该方案解决的是C# 侧 GC 分配,Lua 侧自身的 GC 策略(如LuaEnv.FullGc、GcPause、GcStepmul调优)属于另一个话题,可参考 FAQ 中的相关说明;
  • 本仓库的 xLua 位于微信小游戏 WebGL 工程 Demo/xLua_WebGL 中,上述配置与生成流程在 Unity WebGL / 微信小游戏导出目标下同样适用。
  • 游戏开发
  • 移动开发
  • WebAssembly

【免费下载链接】minigame-unity-webgl-transform

微信小游戏Unity引擎适配器文档。

项目地址:https://gitcode.com/GitHub_Trending/mi/minigame-unity-webgl-transform
点击查看免费下载

相关推荐

上一篇:告别单元测试痛点:FakeItEasy 零基础到精通实战指南
下一篇:未来已来:2025年基于大语言模型的机器人技术发展趋势预测

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询