Skip to main content
Version: Next

Best Practices

Unity Version Recommendation

Recommend using 2020.3.x(x >= 21) series and 2021.3.x series, which are the most stable.

Don't Save assemblyBytes After Assembly.Load

Don't save the assembly's byte[] data after calling Assembly.Load, as Assembly.Load automatically makes a copy internally.

Recommend Mounting Startup Scripts to the First Hot Update Scene Loaded After Hot Update Completion

Recommend mounting startup scripts to the startup hot update scene, which allows zero-modification conversion of non-hot update projects to hot update projects without requiring any reflection operations.

Timing of RuntimeApi.LoadMetadataForAOTAssembly Calls

You only need to call it before using AOT generics (only needs to be called once), and theoretically the earlier the better. In practice, reasonable timing is after hot update completion, or after hot update dll loading but before executing any code. If supplemental metadata dlls are also packaged into the main package as additional data files, loading them during main project startup is better. Refer to the HybridCLR_trial project

Assembly.Load or RuntimeApi.LoadMetadataForAOTAssembly Taking Too Long, Causing Game Stuttering

You can put them in other threads for asynchronous loading.

For Performance-Sensitive Interactions Between Native and Interpreter Parts, Don't Use Reflection - Use Delegates or Virtual Functions Instead

Taking the Update function as an example, most people would think of interaction between the main project and hot update part like this:

var klass = ass.GetType("App");
var method = klass.GetMethod("Update");
method.Invoke(null, new object[] {deltaTime});

The disadvantage of this approach is high reflection cost, and if parameters are involved, additional GC occurs. Actually, there are more efficient methods. There are mainly two approaches:

Hot Update Layer Returns a Delegate

// Hotfix.asmdf hot update part 
class App
{
public static Action<float> GetUpdateDelegate()
{
return Update;
}

public static void Update(float deltaTime)
{
}
}

// Main.asmdf main project
var klass = ass.GetType("App");
var method = klass.GetMethod("GetUpdateDelegate");
var updateDel = (Action<float>)method.Invoke(null, null);

updateDel(deltaTime);

Use Delegate.Create to Create Corresponding Delegate Based on MethodInfo

var klass = ass.GetType("App");
var method = klass.GetMethod("Update");
updateDel = (Action<float>)System.Delegate.CreateDelegate(typeof(Action<float>), null, method);
updateDel(deltaTime);

Don't Use faster(smaller) builds Option in 2021 Versions

Starting from 2021.3.x LTS versions, il2cpp fully supports full generic sharing technology. When the Il2Cpp Code Generation option in Build Settings is faster runtime, it uses the standard generic sharing mechanism; when it's faster(smaller) builds, it enables the full generic sharing mechanism.

When full generic sharing is enabled, each generic function (regardless of whether generic parameters are value types or class types) completely shares a single copy of code. The advantage is saving package size, but the disadvantage is severely hurting generic function performance. Fully generic shared code compared to standard generic shared code can sometimes be several to dozens of times slower, even worse than pure interpretation versions. Therefore, it's strongly recommended to not enable the faster(smaller) builds option.