# Common Pitfalls When Migrating from Other Unity Frameworks to TEngine

> Avoid common pitfalls migrating Unity frameworks to TEngine. Learn to leverage ModuleSystem, ResourceModule, int IDs, and code generators for a smoother transition.

- Repository: [ALEX/tengine](https://github.com/alex-rachel/tengine)
- Tags: migration-guide
- Published: 2026-02-24

---

**The most common pitfalls when migrating to TEngine include attempting to preserve monolithic singleton managers instead of adopting the module-based `ModuleSystem`, continuing to use Addressables or `Resources.Load` rather than the YooAsset-wrapped `ResourceModule`, relying on string-based events instead of zero-GC int IDs, and failing to run mandatory code generators for UI bindings and hot-fix assemblies.**

TEngine by alex-rachel is a high-performance Unity framework that replaces traditional singleton architectures with a modular service locator pattern. While migrating from other Unity frameworks, developers often encounter mismatches in asset pipelines, event systems, and lifecycle management that require specific architectural adjustments to leverage TEngine’s low-allocation design.

## Assuming Singleton Architecture Instead of Modules

The most fundamental mistake is expecting a global `GameManager` or static service locator. In [`Assets/TEngine/Runtime/Core/ModuleSystem.cs`](https://github.com/alex-rachel/tengine/blob/main/Assets/TEngine/Runtime/Core/ModuleSystem.cs), TEngine implements a strict module system where every service (resource, UI, audio, etc.) inherits from the `Module` base class and implements optional `IUpdateModule` for frame updates.

Modules are created lazily on first access via `ModuleSystem.GetModule<T>()` and stored in an internal dictionary. If you need a custom service, you must inherit from `Module` and register it explicitly:

```csharp
using TEngine;

public class AnalyticsModule : Module, IUpdateModule
{
    public override int Priority => 5; // Higher value = earlier execution
    
    public void Update(float elapseSeconds, float realElapseSeconds)
    {
        // Per-frame analytics logic
    }
}

// Registration during bootstrap
var analytics = new AnalyticsModule();
ModuleSystem.RegisterModule<IAnalyticsModule>(analytics);

```

**Key implementation detail:** The `ModuleSystem` maintains an internal `_updateExecuteList` that orders modules by their `Priority` property. If your module relies on execution order, you must override `Priority` in [`ModuleSystem.cs`](https://github.com/alex-rachel/tengine/blob/main/ModuleSystem.cs) rather than using Unity’s Script Execution Order settings.

## Using Addressables or Resources.Load Directly

TEngine’s `ResourceModule` located in [`Assets/TEngine/Runtime/Module/ResourceModule/ResourceModule.cs`](https://github.com/alex-rachel/tengine/blob/main/Assets/TEngine/Runtime/Module/ResourceModule/ResourceModule.cs) wraps YooAsset and completely abstracts Unity’s built-in loading mechanisms. Calling `Addressables.LoadAssetAsync` or `Resources.Load` bypasses TEngine’s reference counting and hot-update support.

The `ResourceModule` provides synchronous `LoadAsset<T>` and asynchronous `LoadAssetAsync<T>` methods that return `UniTask<T>`:

```csharp
using TEngine;
using Cysharp.Threading.Tasks;

public async UniTask<GameObject> InstantiatePlayer()
{
    var resourceModule = ModuleSystem.GetModule<IResourceModule>();
    
    // Async load with YooAsset package name "entities"
    GameObject prefab = await resourceModule.LoadAssetAsync<GameObject>(
        "Player/Avatar", 
        "entities"
    );
    
    return Object.Instantiate(prefab);
}

```

**Critical fix:** Remove all `Addressables` callback chains and convert them to `async/await` patterns using `UniTask`. The module handles asset reference counting internally, so you must release handles through `ResourceModule` rather than Addressables' release methods.

## String-Based Events Causing GC Allocations

TEngine’s **GameEvent** system in `Tools/GameEventSourceGenerator/SourceGenerator/Generator` eliminates GC pressure by using **int IDs** instead of string keys. Relying on `string` event names or UnityEvent assets creates allocations that defeat TEngine’s zero-GC design.

The framework generates a `GameEventId` enum from your event definitions. You subscribe and publish using these integer constants:

```csharp
using TEngine;

public class CombatSystem
{
    private readonly int _damageEventId = (int)GameEventId.UnitTakeDamage;

    public CombatSystem()
    {
        // Zero-allocation subscription
        GameEvent.Subscribe(_damageEventId, OnDamageReceived);
    }

    private void OnDamageReceived(object payload)
    {
        var damageData = (DamagePayload)payload;
        // Process damage...
    }

    public void ApplyDamage(DamagePayload data)
    {
        // Zero-allocation dispatch
        GameEvent.Publish(_damageEventId, data);
    }
}

```

**Migration step:** Replace `UnityEvent`, `Action<string>`, or event bus systems with `GameEvent.Publish` and `GameEvent.Subscribe`, using only the generated `GameEventId` enum values.

## Hand-Written UI MonoBehaviours Without Code Generation

TEngine requires the UI code generator located in [`Assets/Editor/UIScriptGenerator/UIComponentInspectorEditor.cs`](https://github.com/alex-rachel/tengine/blob/main/Assets/Editor/UIScriptGenerator/UIComponentInspectorEditor.cs). Manually writing `GetComponent` calls or inspector references breaks the framework’s binding automation.

UI windows must inherit from `UIWindow` and use a generated partial class (e.g., `MainWindowBinding`) that the generator produces from your prefab structure:

1. Design your UI in Unity Editor
2. Run the generator via [`ScriptGenerator.cs`](https://github.com/alex-rachel/tengine/blob/main/ScriptGenerator.cs)
3. Derive your logic from the generated binding class

This approach eliminates manual UI element lookups and ensures type-safe references that survive prefab changes.

## Custom Config Parsers Instead of Luban

TEngine uses **Luban** configuration tables accessed through [`Configs/GameConfig/CustomTemplate/ConfigSystem.cs`](https://github.com/alex-rachel/tengine/blob/main/Configs/GameConfig/CustomTemplate/ConfigSystem.cs). Maintaining custom CSV or JSON parsers misses TEngine’s lazy-loading and type-safe config classes.

Access configuration data via `ConfigSystem.GetConfig<T>()`:

```csharp
using TEngine;

public class ItemDatabase
{
    public ItemData GetItem(int id)
    {
        // Lazy-loads ItemConfig if not cached; supports async preload
        var config = ConfigSystem.GetConfig<ItemConfig>();
        return config.Get(id);
    }
}

```

**Migration requirement:** Convert your existing config files to Luban format and regenerate C# classes using the [`gen_code_bin_to_server.sh`](https://github.com/alex-rachel/tengine/blob/main/gen_code_bin_to_server.sh) script. Remove all manual JSON parsing or ScriptableObject references for configuration data.

## Incorrect Hot-Fix Assembly Structure

TEngine expects a specific **HybridCLR** layout under `Assets/GameScripts/HotFix` containing four assemblies: `GameBase`, `GameProto`, and `GameLogic`. The entry point is [`GameApp.cs`](https://github.com/alex-rachel/tengine/blob/main/GameApp.cs), where modules are registered via [`GameApp_RegisterSystem.cs`](https://github.com/alex-rachel/tengine/blob/main/GameApp_RegisterSystem.cs).

Placing hot-fix code in arbitrary folders or using different assembly names prevents the HybridCLR integration from compiling properly. Reorganize your hot-fix logic to match this structure and ensure `[HybridCLR]` assembly definitions are configured correctly.

## Ignoring Module Priority in Update Loops

Unlike Unity’s default `Update` order, TEngine’s `ModuleSystem` builds an ordered execution list at runtime based on the `Priority` property. If your module must execute before or after core systems (like `ProcedureModule` or `ResourceModule`), you must set an explicit priority:

- Core modules typically use priorities 0-4
- Custom modules should use 5+ or negative values depending on requirements

In [`ModuleSystem.cs`](https://github.com/alex-rachel/tengine/blob/main/ModuleSystem.cs), the `_updateExecuteList` sorts modules by `Priority` descending, meaning higher integers execute earlier in the frame.

## Using Third-Party Object Poolers

TEngine provides native pooling via `ObjectPoolModule` and `MemoryPoolModule` (located in the Runtime Module directory). Using external pool libraries creates API fragmentation.

Replace external pool calls with:

```csharp
var objectPool = ModuleSystem.GetModule<IObjectPoolModule>();
var pooledObject = objectPool.Get<MyPooledType>(factoryMethod);

```

Register custom pool types through the module system to ensure they respect TEngine’s lifecycle and shutdown procedures.

## Summary

- **Access all services through `ModuleSystem.GetModule<T>()`** rather than static singletons or service locators
- **Migrate asset loading to `ResourceModule`** using `LoadAssetAsync<T>` with `UniTask` instead of Addressables or Resources
- **Adopt int-based `GameEvent` IDs** from the source generator to maintain zero-GC event dispatch
- **Run UI code generators** after creating or modifying UI prefabs to produce binding classes
- **Use `ConfigSystem`** with Luban-generated classes for all configuration data access
- **Respect module `Priority`** values to control update execution order instead of Unity’s Script Execution Order

## Frequently Asked Questions

### Can I keep using Addressables alongside TEngine during migration?

No. TEngine’s `ResourceModule` in [`Assets/TEngine/Runtime/Module/ResourceModule/ResourceModule.cs`](https://github.com/alex-rachel/tengine/blob/main/Assets/TEngine/Runtime/Module/ResourceModule/ResourceModule.cs) manages asset handles through YooAsset’s reference counting system. Mixing Addressables calls bypasses TEngine’s hot-update support and can cause asset leaks because the framework cannot track external handle lifetimes. Migrate all asset loading to `LoadAssetAsync<T>` or `LoadAsset<T>` immediately.

### How do I replace my existing Singleton GameManager?

Remove your singleton instance property and inherit from `Module` instead. In [`Assets/TEngine/Runtime/Core/ModuleSystem.cs`](https://github.com/alex-rachel/tengine/blob/main/Assets/TEngine/Runtime/Core/ModuleSystem.cs), the `RegisterModule<TInterface>(Module module)` method accepts your custom module during application startup (typically in [`GameApp.cs`](https://github.com/alex-rachel/tengine/blob/main/GameApp.cs) or a bootstrap procedure). Access the service later via `ModuleSystem.GetModule<TInterface>()`, which creates the module on first call if not already registered.

### Why are my UI scripts failing to compile after migration?

TEngine requires the UI binding generator to create partial classes for `UIWindow` derivatives. If you see missing references to `XxxWindowBinding` classes, you have not run the generator located in [`Assets/Editor/UIScriptGenerator/ScriptGenerator.cs`](https://github.com/alex-rachel/tengine/blob/main/Assets/Editor/UIScriptGenerator/ScriptGenerator.cs). After designing your UI in the Editor, run the generator to produce the binding code that links your logic to the prefab’s components.

### What causes GC spikes when using events in TEngine?

Using string keys or boxing value types in event payloads causes allocations. Ensure you are using the generated `GameEventId` enum (located in the generated code under `Tools/GameEventSourceGenerator`) which maps to int IDs. The `GameEvent.Publish(int eventId, object payload)` method in the generated `GameEvent` class is optimized for zero-GC dispatch when using the integer event IDs and proper payload types.