Files
lingua-engine/plugins/engine.render/Engine.Render/RenderPlugin.cs
T
EmilandClaude Sonnet 5 c5d3807a0f M1 (in progress): engine.windowing + engine.render, hot-reload proven live
The first plugins with a real external dependency (Silk.NET) and the
first that need a display. Both built, both verified by hand against
a real window and a live GL context — not just "compiles."

engine.windowing:
- IEngineWindow, not IWindow — Silk.NET's own windowing type already
  owns that name; same lesson as the kernel's World -> GameWorld
  rename, applied proactively this time instead of hitting the build
  error first. docs/kernel-contract.md's §3 illustrative example
  updated to match.
- Exposes the real Silk.NET IWindow directly (IEngineWindow.Native)
  rather than re-wrapping it — GL context creation and event pumping
  both need it, and hiding it buys nothing yet.

engine.render:
- Minimal: glClear + SwapBuffers against a hardcoded color, no mesh.
  Enough to prove M1's actual claim, which has nothing to do with
  triangles specifically: edit a plugin, rebuild just it, reload it
  while a real window stays open, see the change with no app restart.
- No Contracts assembly — PluginManifest.Contracts is now nullable
  rather than forcing an empty assembly into existence just to satisfy
  the schema; PluginHost.Load skips the Default-ALC step when absent.

Engine.Host:
- --windowed alongside --headless: pumps window events plus
  Stage.Update/Stage.Render each frame instead of a bounded --frames
  loop. References Engine.Windowing.Contracts directly (never the
  implementation) to know how to drive that loop — same "Contracts
  are safe to share" pattern already proven for Sandbox.Echo.Contracts.
- New: typing "r <plugin-id>" + Enter reloads that plugin live. Not a
  file watcher (still not built), but real Unload+Load through the
  same PluginHost path, against a running window — this is what
  actually exercised the hot-reload claim below.
- IServiceRegistry gained TryRequire<T> so Engine.Host can ask "is a
  window available" without treating its absence as an error; a
  plugin's own Configure()/Shutdown() should keep using Require().

Verified end to end by hand: opened the window, watched it render its
hardcoded color, edited RenderPlugin.cs's ClearColor, rebuilt only
that project, typed "r engine.render" into the running process, and
watched the color change with the same window and GL context still
alive. Also found and documented a real platform gotcha along the
way: on Wayland (unlike X11), a window with no committed buffer isn't
shown at all, not even as a black rectangle — engine.windowing alone
produces an invisible window; engine.render's first Clear+SwapBuffers
is what actually makes it appear. Noted directly on RenderPlugin.

Not covered by automated tests, deliberately: opening a real window
needs a real display, which isn't safe to assume of every environment
this runs in. ServiceRegistry.TryRequire<T> and the null-Contracts
path in PluginHost are unit-tested; the window/render/reload flow is
described here and was checked by hand instead.

README status updated: M1 in progress, not done — engine.input and an
actual drawn triangle (vs. a clear color) are still open.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N1qPfzq8TDCUMFMV3UwV5N
2026-09-02 04:19:32 +03:00

59 lines
2.1 KiB
C#

using Engine.Kernel.Plugins;
using Engine.Kernel.Scheduling;
using Engine.Kernel.World;
using Engine.Windowing.Contracts;
using Silk.NET.OpenGL;
namespace Engine.Render;
/// <summary>
/// M1's minimal render pipeline: clears the window to a color and swaps
/// buffers, once per Render stage. No Contracts assembly — nothing here is
/// a type another plugin needs to reference yet (see the null-Contracts
/// note on PluginManifest). See M1 in docs/kernel-contract.md §8.
///
/// This is the whole point of M1's "done when": change ClearColor, rebuild
/// just this plugin, and reload it while the window from engine.windowing
/// stays open — the color changes with no app restart. Verified by hand,
/// not by an automated test — opening a real window needs a real display,
/// which isn't something to assume of every environment this runs in.
///
/// engine.windowing alone produces a window that never becomes visible on
/// Wayland — unlike X11, a Wayland surface with no committed buffer simply
/// isn't shown by the compositor, so an "empty" window isn't even a black
/// rectangle, it's nothing at all. This plugin's first Clear+SwapBuffers is
/// what actually makes the window appear.
/// </summary>
public sealed class RenderPlugin : IPlugin
{
private static readonly float[] ClearColor = [0.25f, 0.55f, 0.85f, 1f];
private GL? _gl;
private IEngineWindow? _window;
public void Configure(IPluginContext ctx)
{
_window = ctx.Services.Require<IEngineWindow>();
_window.Native.GLContext!.MakeCurrent();
_gl = _window.Native.CreateOpenGL();
ctx.Schedule.Add(Stage.Render, Draw);
ctx.Log.Info("GL context created");
}
public void Shutdown(IPluginContext ctx)
{
ctx.Schedule.RemoveAllFrom("engine.render");
_gl?.Dispose();
_gl = null;
_window = null;
}
private void Draw(IWorld world)
{
_gl!.ClearColor(ClearColor[0], ClearColor[1], ClearColor[2], ClearColor[3]);
_gl.Clear(ClearBufferMask.ColorBufferBit);
_window!.Native.GLContext!.SwapBuffers();
}
}