RenderingServer: создать экземпляр и освободить RID
Отображение без MeshInstance3D, сильные ссылки на ресурсы и очистка при выходе из дерева.
Когда нужен серверный API
Обычная MeshInstance3D уже управляет отображаемым экземпляром и интеграцией с деревом. RenderingServer позволяет вести такие экземпляры самостоятельно через RID. Это полезно при собственной системе отображения, но добавляет ответственность за ресурс, сценарий, преобразование, видимость и освобождение. Сам выбор низкоуровневого API не доказывает ускорение.
Запустить экземпляр без MeshInstance3D
Добавьте GeometryStage3D.cs и ServerBoxLab3D.cs. Прикрепите ServerBoxLab3D к Entity3D-корню отдельного C#-уровня 1.0.0, задайте viewport 640×420, соберите и нажмите F6. Появится синий куб. Пол стенда использует обычную MeshInstance3D, а сам синий куб создан через RenderingServer.InstanceCreate.
GeometryStage3D.cs
using Reckfell;
public static class GeometryStage3D
{
public static StandardMaterial3D Material(bool vertexColors = false, bool unshaded = false)
{
return new StandardMaterial3D
{
AlbedoColor = vertexColors ? Colors.White : new Color(0.24f, 0.44f, 1),
VertexColorUseAsAlbedo = vertexColors,
ShadingMode = unshaded ? BaseMaterial3D.ShadingModeEnum.Unshaded : BaseMaterial3D.ShadingModeEnum.PerPixel,
Roughness = 0.8f
};
}
public static void Setup(Entity3D root, string ru, string en)
{
root.AddChild(new WorldEnvironment { Environment = new Reckfell.Environment
{
BackgroundMode = Reckfell.Environment.BGMode.Color,
BackgroundColor = new Color(0.035f, 0.04f, 0.05f),
AmbientLightSource = Reckfell.Environment.AmbientSource.Color,
AmbientLightColor = Colors.White, AmbientLightEnergy = 0.5f
}});
root.AddChild(new DirectionalLight3D { RotationDegrees = new Vector3(-55, -25, 0), LightEnergy = 1.2f });
root.AddChild(new MeshInstance3D
{
Mesh = new BoxMesh { Size = new Vector3(8, 0.05f, 6) }, Position = new Vector3(0, -0.12f, 0),
MaterialOverride = new StandardMaterial3D { AlbedoColor = new Color(0.10f, 0.11f, 0.13f), Roughness = 1 }
});
var camera = new Camera3D { Position = new Vector3(4, 4.8f, 6), Current = true, Fov = 45 };
root.AddChild(camera); camera.LookAt(new Vector3(0, 0.35f, 0), Vector3.Up);
var canvas = new CanvasLayer(); root.AddChild(canvas);
var label = new Label { Position = new Vector2(24, 20), Text = TranslationServer.GetLocale().StartsWith("ru") ? ru : en };
label.AddThemeFontSizeOverride("font_size", 22); canvas.AddChild(label);
}
}ServerBoxLab3D.cs
using Reckfell;
public partial class ServerBoxLab3D : Entity3D
{
public bool Active => _instance.IsValid;
public Rid Instance => _instance;
private Rid _instance;
private BoxMesh? _mesh;
private StandardMaterial3D? _material;
public Error Open()
{
if (!ReckfellThread.IsMainThread()) return Error.Unavailable;
if (!IsInsideTree()) return Error.Unconfigured;
if (Active) return Error.Busy;
_material = GeometryStage3D.Material();
_mesh = new BoxMesh { Size = new Vector3(1.5f, 1.5f, 1.5f), Material = _material };
_instance = RenderingServer.InstanceCreate();
RenderingServer.InstanceSetBase(_instance, _mesh.GetRid());
RenderingServer.InstanceSetScenario(_instance, GetWorld3D().Scenario);
UpdateTransform();
SetProcess(true);
return Error.Ok;
}
private void UpdateTransform()
{
RenderingServer.InstanceSetTransform(_instance, GlobalTransform.TranslatedLocal(new Vector3(0, 0.75f, 0)));
}
public void Close()
{
if (Active) RenderingServer.FreeRid(_instance);
_instance = default;
_mesh?.Dispose(); _mesh = null;
_material?.Dispose(); _material = null;
SetProcess(false);
}
public override void _Ready()
{
GeometryStage3D.Setup(this, "RenderingServer: экземпляр без MeshInstance3D", "RenderingServer: an instance without MeshInstance3D");
Error error = Open();
if (error != Error.Ok) ReckfellApi.PrintErr("ServerBox open: " + error);
}
public override void _Process(double delta)
{
if (Active) UpdateTransform();
}
public override void _ExitTree() => Close();
}Цепочка владения
- Сохраните сильные C#-ссылки на Mesh и Material. Их RID не заменяют эти ссылки.
- InstanceCreate создаёт собственный экземпляр.
- InstanceSetBase указывает Mesh RID; InstanceSetScenario подключает экземпляр к сценарию GetWorld3D().Scenario.
- InstanceSetTransform передаёт мировое преобразование. Оно не синхронизируется с Entity автоматически — код обновляет его сам.
- При завершении FreeRid освобождает созданный экземпляр. Затем можно отпустить собственные ресурсы и обнулить сохранённый RID.
RID — значение-идентификатор, а не Resource с автоматическим временем жизни. Копия RID не владеет объектом. IsValid означает ненулевое значение; после FreeRid сохранённая отдельно копия всё ещё может иметь IsValid = true, хотя нативного экземпляра уже нет. Не используйте такую копию для последующих вызовов.
Нельзя FreeRid для заимствованного World3D.Scenario или вручную освобождать RID живого Mesh вместо его ресурсного владельца. В примере освобождается только собственный instance RID; Material и Mesh удерживаются до этого и освобождаются через свои C#-обёртки.
Отключение и повторный вход
Close идемпотентен, сбрасывает RID и ссылки. _ExitTree вызывает его. Повторный вход той же Entity сам по себе не повторяет _Ready; после добавления вызовите Open явно, если нужно открыть новый экземпляр. В стенде проверены повторное открытие и новый RID, выход из дерева и отсутствие двойного освобождения. В графическом тесте после Close куб действительно исчезает из viewport, а не только меняется флаг Active.
Open/Close и управление Entity выполняйте в главном потоке в этой архитектуре. Отдельные потоки server API не дают общего разрешения обращаться к дереву или общим ресурсам; см. потокобезопасность.
PhysicsServer2D/3D и другие серверы имеют собственные типы RID и контракты: тело, shape, space, callbacks и шаг симуляции не создаются этим рендер-примером. Он не реализует ECS, физику, игровые скрипты для каждого экземпляра, сетевую репликацию или стриминг. Для многих одинаковых мешей сначала оцените более простой MultiMesh.