reckfellENGINE

Retain a level in memory while switching

A two-room host: detaching without deletion, preserved fields, reentry and cache cleanup.

On this page

Choose a strategy

StrategyRetained dataResponsibility
EntityTree.ChangeSceneToFileData outside the replaced rootRecreate level state from its file/save data
Detach and retain an EntityThe same objects and in-memory fieldsExplicitly free detached roots
Hide a CanvasItemThe object remains in the active hierarchySeparately define processing, input, audio and physics

Ordinary EntityTree replacement immediately detaches the old root and deletes it when deferred replacement finishes. It is not a free cache of previous levels. Hide changes visibility rather than lifecycle. ProcessMode.Disabled is not a universal stop-everything operation either: account for explicit child modes and physics-object DisableMode.

Run two retained rooms

Add both C# files at the root of a 1.0.0 project and create Rooms/A.rscene and Rooms/B.rscene at the shown paths. Attach RetainedLevelHost to an Entity2D in a separate level, set a 640×420 viewport, build and run with F6. Press +1, switch to B, then return to A: Value remains, Enter increases and Ready stays 1.

Room A returned with value 42, two entries and one Ready call

RetainedRoom2D.cs
C#
using Reckfell;

public partial class RetainedRoom2D : Entity2D
{
    [Export] public int Value { get; set; }
    public int Entries { get; private set; }
    public int ReadyCalls { get; private set; }

    public override void _EnterTree() => Entries++;
    public override void _Ready() => ReadyCalls++;
    public void Increase() { Value++; QueueRedraw(); }
    public override void _Draw()
    {
        DrawRect(new Rect2(60, 150, 500, 170), new Color(.10f, .17f, .38f));
        bool ru = TranslationServer.GetLocale().StartsWith("ru");
        DrawString(ThemeDB.FallbackFont, new Vector2(84, 204), Name + (ru ? " · значение: " : " · value: ") + Value, fontSize: 23);
        DrawString(ThemeDB.FallbackFont, new Vector2(84, 254), "Enter: " + Entries + " · Ready: " + ReadyCalls, fontSize: 19);
    }
}
Rooms/A.rscene
Configuration
[reckfell_tree format=3]
[ext_resource type="Script" path="asset://RetainedRoom2D.cs" id="Room"]
[entity name="RoomA" type="Entity2D"]
script = ExtResource("Room")
Value = 10
Rooms/B.rscene
Configuration
[reckfell_tree format=3]
[ext_resource type="Script" path="asset://RetainedRoom2D.cs" id="Room"]
[entity name="RoomB" type="Entity2D"]
script = ExtResource("Room")
Value = 20
RetainedLevelHost.cs
C#
using System.Collections.Generic;
using System.Linq;
using Reckfell;

public partial class RetainedLevelHost : Entity2D
{
    public const string RoomA = "asset://Rooms/A.rscene";
    public const string RoomB = "asset://Rooms/B.rscene";
    public RetainedRoom2D? ActiveRoom { get; private set; }
    public int CachedCount => _rooms.Count;
    private readonly Dictionary<string, RetainedRoom2D> _rooms = new();
    private bool _switching;

    public Error SwitchTo(string path)
    {
        if (!IsInsideTree()) return Error.Unconfigured;
        if (_switching) return Error.Busy;
        if (path != RoomA && path != RoomB) return Error.InvalidParameter;
        _switching = true;
        try
        {
            if (!_rooms.TryGetValue(path, out var next))
            {
                var packed = ResourceLoader.Load<PackedEntity>(path);
                if (packed is null || !packed.CanInstantiate()) return Error.CantOpen;
                Entity instance = packed.Instantiate();
                if (instance is not RetainedRoom2D room) { instance.Free(); return Error.InvalidData; }
                next = room;
                _rooms.Add(path, next);
            }
            if (next == ActiveRoom) return Error.Ok;
            if (ActiveRoom is not null) RemoveChild(ActiveRoom);
            ActiveRoom = next;
            AddChild(next);
            next.QueueRedraw();
            return Error.Ok;
        }
        finally { _switching = false; }
    }

    public override void _Ready()
    {
        bool ru = TranslationServer.GetLocale().StartsWith("ru");
        AddChild(new Label { Position = new Vector2(28, 24), Text = ru ? "Комнаты: состояние сохраняется в памяти" : "Rooms: state stays in memory" });
        foreach (var item in new[] { ("A", RoomA), ("B", RoomB) })
        {
            string path = item.Item2;
            var button = new Button { Text = item.Item1, Position = new Vector2(item.Item1 == "A" ? 28 : 104, 82) };
            button.Pressed += () => { Error error = SwitchTo(path); if (error != Error.Ok) ReckfellApi.PrintErr(error); };
            AddChild(button);
        }
        var increase = new Button { Text = "+1", Position = new Vector2(180, 82) };
        increase.Pressed += () => ActiveRoom?.Increase();
        AddChild(increase);
        Error initial = SwitchTo(RoomA);
        if (initial != Error.Ok) ReckfellApi.PrintErr(initial);
    }

    public override void _ExitTree()
    {
        foreach (var pair in _rooms.ToArray())
        {
            if (pair.Value == ActiveRoom) continue;
            pair.Value.Free();
            _rooms.Remove(pair.Key);
        }
    }
}

The host intentionally accepts only these two rooms and RetainedRoom2D. A new instance is prepared before detaching the previous one. An inactive room stays in the dictionary without a Parent and receives no ordinary tree processing. These are the same native objects, not a Pack-based reconstruction.

Ownership and exit

RemoveChild does not free a room. A C# reference also does not automatically manage an ordinary Entity lifetime. The host must Free detached instances; normal parent-tree deletion releases the active child. Here _ExitTree clears the inactive cache even when the host is temporarily detached. Its active room remains a child and enters again when the host returns.

Reentry invokes _EnterTree/_ExitTree without automatically repeating ordinary _Ready. Rooms with external subscriptions must restore and disconnect them symmetrically. A detached object can still receive a direct call or an external service signal if you keep such a connection.

The host does not assign every room to CurrentScene: EntityTree current level remains the containing root. ReloadCurrentScene targets that root and can recreate the host and its cache. Use EntityTree for standard full replacement.

When to choose something else

The cache consumes memory: live rooms may retain meshes, textures and other resources. This example has no memory budget, eviction, background streaming or disk persistence. Do not free rooms outside the host or treat it as a finished seamless-world system. Choose from project measurements rather than assuming manual replacement is always faster.

Reckfell Engine documentationBack to top ↑
QUICK LINKSGetting startedEntity hierarchyC# & .NET 10Scenes and prefabs
Reckfell guides and full API K
reckfellENGINE
RECKFELL ACCOUNT

Sign in

Sign in to your Reckfell account.

One account for the entire Reckfell platform.