diff --git a/src/CortexEngine.App/CortexEngine.App.csproj b/src/CortexEngine.App/CortexEngine.App.csproj index 48ecb7f..ed1d5ac 100644 --- a/src/CortexEngine.App/CortexEngine.App.csproj +++ b/src/CortexEngine.App/CortexEngine.App.csproj @@ -24,6 +24,7 @@ + diff --git a/src/CortexEngine.App/Program.cs b/src/CortexEngine.App/Program.cs index 7ea669d..cf42117 100644 --- a/src/CortexEngine.App/Program.cs +++ b/src/CortexEngine.App/Program.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Engine.AI; using Engine.Core; using Engine.Core.Components; using Engine.Graphics; @@ -7,6 +8,10 @@ using Engine.Graphics.Vulkan; using Engine.Physics; using Flecs.NET.Core; using ImGuiNET; +#if !RELEASE_AOT +using Engine.AI.Mcp; +using Microsoft.AspNetCore.Builder; +#endif namespace CortexEngine.App; @@ -14,18 +19,50 @@ class Program { static async Task Main(string[] args) { - Console.WriteLine("Cortex Engine — Vulkan Scene + Physics (pure P/Invoke)..."); + Console.WriteLine("Cortex Engine — Vulkan + Physics + AI/MCP (pure P/Invoke)..."); try { + var mcpPort = ParseMcpPort(args); + + using var world = World.Create(); + using var physicsWorld = new PhysicsWorld(); + VulkanBackendRegistrar.EnsureRegistered(); using var renderContext = RenderBackendFactory.Create("vulkan", 1280, 720, enableValidation: true); var window = renderContext.Window; var input = window.Input; using var renderer = renderContext.CreateRenderer(); - using var world = World.Create(); - using var physicsWorld = new PhysicsWorld(); + var processor = new AiCommandProcessor(world, p => LoadMesh(p, new Vector3(0.7f, 0.6f, 0.5f)), path => renderer.RequestScreenshot(path)); + var queue = new AiCommandQueue(processor, new DummyScreenshotProvider()); + +#if !RELEASE_AOT + WebApplication? mcpApp = null; + if (mcpPort > 0) + { + var mcpThread = new Thread(() => + { + Thread.Sleep(2000); + try + { + mcpApp = McpEngineServerHost.Create(Array.Empty(), queue, port: mcpPort); + mcpApp.Run(); + } + catch (Exception ex) + { + Console.WriteLine($"[App] MCP server error: {ex.Message}"); + } + }) + { IsBackground = true }; + mcpThread.Start(); + Console.WriteLine($"[App] MCP HTTP server starting on http://localhost:{mcpPort}/ (SSE)"); + } + else + { + Console.WriteLine("[App] MCP server disabled (--mcp-port 0)."); + } +#endif var cameraEntity = world.Entity("Camera") .Set(new Camera( @@ -91,6 +128,10 @@ class Program physicsWorld.Update((float)timing.DeltaTime); physicsWorld.SyncTransforms(world); + var processed = queue.ProcessPending(); + if (processed > 0) + Console.WriteLine($"[App] Processed {processed} AI command(s)"); + if (hasImGui) { renderer.BeginImGuiFrame(); @@ -108,6 +149,7 @@ class Program var entityCount = 0; world.Each((Entity e, ref Transform _) => entityCount++); ImGui.Text($"Entities: {entityCount}"); + ImGui.Text($"MCP: {(mcpPort > 0 ? $"port {mcpPort}" : "disabled")}"); ImGui.Text("WASD: move | RMB: look | Q/E: up/down | Shift: boost"); ImGui.End(); @@ -115,6 +157,7 @@ class Program } renderer.RenderWorld(world); + queue.CompletePendingScreenshots(); frames++; if (timing.TotalTime - lastFpsTime >= 1.0) @@ -126,6 +169,10 @@ class Program } Console.WriteLine("Shutting down..."); +#if !RELEASE_AOT + if (mcpApp != null) + await mcpApp.StopAsync(); +#endif } catch (Exception ex) { @@ -136,6 +183,16 @@ class Program await Task.CompletedTask; } + static int ParseMcpPort(string[] args) + { + for (var i = 0; i < args.Length; i++) + { + if (args[i] == "--mcp-port" && i + 1 < args.Length && int.TryParse(args[i + 1], out var port)) + return port; + } + return 0; + } + static void CreateScene(World world) { var torusKnot = LoadMesh("Content/torusknot.obj", new Vector3(0.9f, 0.7f, 0.3f)); @@ -207,4 +264,9 @@ class Program } return ObjLoader.Load(path, color); } + + class DummyScreenshotProvider : Engine.Core.IScreenshotProvider + { + public Task CaptureAsync(string outputPath) => Task.FromResult(Array.Empty()); + } }