Code Generation Addon

← Back Optimization

Description

This article outlines the optimization settings available for the CodeGen addon. The classes generated by the CodeGen addon and the MonoBehaviour code generator can achieve better performance with these settings. If these classes serve as your primary API for database access, we highly recommend using all optimization parameters. To ensure that the generated MonoBehaviour classes also benefit from these parameters, enable them and toggle the "Use CodeGen addon generated classes" option in the MonoBehaviour generator settings, then re-generate the classes.

Below is an example of the settings (optimization-related settings are highlighted in red, please ignore all other settings).

Generate metas

Additional classes for tables (metas) are generated, eliminating the need for down-casting rows (entities). This parameter is necessary for some other settings.

Storage

The most important setting. It defines where cell values are stored and improve getters and raw setters performance

  1. Default - cell values for most fields are stored in an array within the field class. (slower)
  2. GeneratedClasses - cell values for some fields are stored in generated class (faster). Additional classes for fields are generated

To ensure that the generated classes for the database fields are in use, navigate to 'Configuration -> $TableName' and check the 'Type & Class' column. An asterisk will appear next to the field type if the generated classes are being used. Some fields are not supported (as of v. 1.9.2): nested, assets, calculated/programmable, scene object references, hashtable.

Cache relation values

Caching relation field values in generated classes can significantly enhance the performance of getter methods. This setting is ideal for scenarios with frequent reads and infrequent writes. The memory cost is as follows:

  1. Single row relations: is 9 bytes per row (1 byte for the dirty flag + pointer to the row)
  2. Multiple rows relation: depends on value size (1 byte for the dirty flag + an additional list for the value)

Important note on caching list (relationMultiple) values:

  1. Values can not be null anymore, if no value exist, an empty list will be returned
  2. There is no need to use FillXXX methods to get rid of GC overhead (except for values in the reverse direction)
  3. When you read the value, a List object is returned, which stores the cached value. If you modify this list, you must pass it back to the setter, otherwise data integrity may be compromised.
            var list = entity.someList;
            list.Add(SomeTable.GetEntity(0)); // <- this call modifies the cached value, not the database value
            entity.someList = list; // <- this call is very important
Other relations optimizations

A new setting, Ignore list order. has been added to relational fields. Use it for:

  1. Nested fields, when the order of nested rows is not important.
  2. Other relational fields, when the order of rows returned while traversing the relation in reverse is not important.
Runtime initialization

This parameter defines how the fields of generated classes are initialized:

  1. On Demand - (slow) the C# fields for database tables and fields are initialized on demand
  2. Manual - (fast) you must manually initialize the runtime by calling the BGCodeGenRuntimeInitializer.InitializeCodeGenRuntime method (fast). You need to call BGCodeGenRuntimeInitializer.InitializeCodeGenRuntime(); method before accessing database and after loading the game with the SaveLoad addon if "Reload database" parameter is used. Failing to call this method will result in a NullReferenceException when accessing the database via generated classes.
  3. On Database Load - (fast) the runtime is automatically initialized after the database is loaded. This means the database must be loaded before you can access it via generated classes. You can load the database using the following code: if (!BGRepo.DefaultRepoLoaded) BGRepo.Load();
Raw setters

Raw setters are additional properties that allow you to set values while bypassing events. Regular setters are slower due to event dispatching. If you don't need events to be triggered, using raw setters can enhance performance. Raw setters are generated for certain fields (such as primitive fields) only if those fields are not included in keys or indexes.

Garbage Collection overhead

Some methods may allocate heap memory, leading to garbage collection (GC) overhead. If these methods are called during infrequent events, like scene loading, the impact is negligible. However, if called in a tight loop, such as in the Update or FixedUpdate methods, it's advisable to avoid heap memory allocation. Typically, using a reusable list addresses this issue. If you use Unity profiler while running the game inside Unity Editor, close BGDatabase window to get accurate results.

# Method Solution
1 FindEntities method Use corresponding FillXXX method and pass a reusable list to this method.
Code example
using System.Collections.Generic;
public class Temp : UnityEngine.MonoBehaviour
{
    private static readonly List<D_Temp> reusableList = new List<D_Temp>();

    private void Update()
    {
        // List<D_Temp> entity = D_Temp.FindEntities(entity => entity.intField > 0);// <- produce garbage
        D_Temp.FindEntities(entity => entity.intField > 0, reusableList); // <- no garbage
    }
}
2 nested field getter The same as #1 (FillXXX method)
Code example
using System.Collections.Generic;

public class Temp : UnityEngine.MonoBehaviour
{
    private static readonly List<D_nested> reusableList = new List<D_nested<();

    private void Update()
    {
        var entity = D_Temp.GetEntity(0);
        //List<D_nested< nestedRows = entity.nested; // <- produce garbage
        entity.Fillnested(reusableList); // <- no garbage
    }
}
3 The getters for relations with List value (relationMultiple / viewRelationMultiple)
    Option #1: The same as #1 (FillXXX method)
    Option #2: Use "cache relation value" optimization parameter. Note, it has some memory cost (the same as using non-static reusable list, added to the generated class)
Code example for option #1
using System.Collections.Generic;
public class Temp : UnityEngine.MonoBehaviour
{
    private static readonly List<D_Related1> reusableList = new List<D_Related1>();

    private void Update()
    {
        D_Temp entity = D_Temp.GetEntity(0);
        // List<D_Related1> result = entity.mRel; // <- produce garbage
        entity.FillmRel(reusableList); // <- no garbage
    }
}
4 The getters for List values while traversing relations in reverse direction The same as #1 (FillXXX method)
Code example for option #1
using System.Collections.Generic;
public class Temp : UnityEngine.MonoBehaviour
{
    private static readonly List<D_Temp> reusableList = new List<D_Temp>();

    private void Update()
    {
        D_Related1 entity = D_Related1.GetEntity(0);
        // List<D_Temp> result = entity.RelatedTempListUsingsRelRelation; // <- produce garbage
        entity.FillRelatedTempListUsingsRelRelation(reusableList); // <- no garbage
    }
}

Important note: You can add reusable lists directly to the generated class and include your own getters and setters that do not produce garbage. For example

Adding your own members to generated class
using System.Collections.Generic;
//BansheeGz.BGDatabase.Example is the namespace of generated class
namespace BansheeGz.BGDatabase.Example
{
    //BGE_AllFields is the name of generated class
    partial class BGE_AllFields
    {
        //reusable list for enumList field
        private static readonly List<BGUtilForTest.TestEnumInt> reusableEnumList = new();

        //property without GC
        public List<BGUtilForTest.TestEnumInt> enumListNoGC
        {
            get
            {
                Fillf_enumList(reusableEnumList);
                return reusableEnumList;
            }
        }
    }
}

Keeping a list static is much more memory efficient, but you need to remember that it can only contain the result for the last row for which the property is called. You can make it non-static if you prefer.

General performance tips
  1. Events degrade performance- database events can have negative impact on setter methods performance. If you can do without events, don't use them.
  2. If you need to delete multiple entities, use BGMetaEntity.DeleteEntities method instead of BGEntity.Delete. Version 1.9.2 delivers a significant performance boost for this method.

"Slow" fields to be aware of
Field Description
asset fields Reading a value requires loading an asset, which can be slow on the first attempt. Use Addressables with asynchronous loading for these fields.
calculated fields Try to not read the values of such fields in hot loops. Replace them with programmable fields or custom C# properties
relation fields These fields are the most problematic. Caching their values can address slow getters in "frequent reads and infrequent writes" scenarios. However, setters may still be relatively slow, especially in certain situations. Be cautious when using relation field setters in hot loops.
Misc
  1. Check for deletion- If you toggle on this parameter, additional checks for entity deletion is added to every getter/setter methods. Without these checks, accessing or modifying field values of deleted entity are not detected. If you do not use database in read-only mode, we recommend to use this parameter during development to detect accidental access to deleted entity.
Conclusion

Test your game's performance on the minimum required hardware before release. Contact us if you experience performance issues with our asset.

← Back to Code Generation Addon