From 443c35c1c37145692c11f229d46329e8c8da4b88 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:52:51 +0000 Subject: [PATCH 1/7] Initial plan From 26cf3d559738fe2593bd1bdac31da384988f69b6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:57:02 +0000 Subject: [PATCH 2/7] Initial plan for scene-less app startup Co-authored-by: EbiseLutica <7106976+EbiseLutica@users.noreply.github.com> --- "Promete.Docs/obj\\Debug/\\package.g.props" | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 "Promete.Docs/obj\\Debug/\\package.g.props" diff --git "a/Promete.Docs/obj\\Debug/\\package.g.props" "b/Promete.Docs/obj\\Debug/\\package.g.props" new file mode 100644 index 0000000..45637c6 --- /dev/null +++ "b/Promete.Docs/obj\\Debug/\\package.g.props" @@ -0,0 +1,21 @@ + + + + promete-docs + 1.0.0 + + astro dev + astro dev + astro build + astro preview + [] + + MIT + pnpm@8.15.1 + ^0.29.2 + ^4.16.14 + ^1.49.0 + ^2.1.0 + ^1.81.0 + + \ No newline at end of file From e57d22e9fa2756c5d2009808fe77700031af84af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:04:49 +0000 Subject: [PATCH 3/7] Implement PrometeApp.Run() without scene parameter Co-authored-by: EbiseLutica <7106976+EbiseLutica@users.noreply.github.com> --- Promete.Test/ScenelessExampleTests.cs | 53 ++++++++++++++++++++++++ Promete.Test/ScenelessRunTests.cs | 59 +++++++++++++++++++++++++++ Promete/PrometeApp.cs | 30 ++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 Promete.Test/ScenelessExampleTests.cs create mode 100644 Promete.Test/ScenelessRunTests.cs diff --git a/Promete.Test/ScenelessExampleTests.cs b/Promete.Test/ScenelessExampleTests.cs new file mode 100644 index 0000000..c51ac96 --- /dev/null +++ b/Promete.Test/ScenelessExampleTests.cs @@ -0,0 +1,53 @@ +using Promete.Headless; +using Promete.Input; +using Promete.Windowing; +using System.Reflection; + +namespace Promete.Test; + +/// +/// Issue で示されたコード例と同様のテスト。 +/// シーンを使わずに PrometeApp を実行できることを確認する。 +/// +public class ScenelessExampleTests +{ + [Fact] + public void Example_ScenelessHelloWorld_ShouldHaveRequiredMethods() + { + // Arrange - Issue で示されたコード例と同様のパターン + var app = PrometeApp.Create() + .Use() + .Use() + .BuildWithHeadless(); + + var keyboard = app.GetPlugin(); + var console = app.GetPlugin(); + + // Window イベントにハンドラを登録できることを確認 + app.Window.Start += () => + { + console.Print("Hello, world!"); + }; + + app.Window.Update += () => + { + // Update logic would go here + }; + + // Run() メソッドが存在することを確認 + var runMethods = typeof(PrometeApp).GetMethods(BindingFlags.Public | BindingFlags.Instance) + .Where(m => m.Name == "Run" && m.GetParameters().Length == 0 && !m.IsGenericMethod); + Assert.Single(runMethods); + + var runMethodsWithOpts = typeof(PrometeApp).GetMethods(BindingFlags.Public | BindingFlags.Instance) + .Where(m => m.Name == "Run" && + m.GetParameters().Length == 1 && + m.GetParameters()[0].ParameterType == typeof(WindowOptions) && + !m.IsGenericMethod); + Assert.Single(runMethodsWithOpts); + + // プラグインが正しく取得できることを確認 + Assert.NotNull(keyboard); + Assert.NotNull(console); + } +} diff --git a/Promete.Test/ScenelessRunTests.cs b/Promete.Test/ScenelessRunTests.cs new file mode 100644 index 0000000..5b45591 --- /dev/null +++ b/Promete.Test/ScenelessRunTests.cs @@ -0,0 +1,59 @@ +using FluentAssertions; +using Promete.Headless; + +namespace Promete.Test; + +public class ScenelessRunTests +{ + [Fact] + public void Run_WithoutScene_ShouldHaveValidRoot() + { + // Arrange + var app = PrometeApp.Create() + .BuildWithHeadless(); + + // DefaultSceneの型を取得する + var defaultSceneType = typeof(PrometeApp).GetNestedType("DefaultScene", System.Reflection.BindingFlags.NonPublic)!; + + // OnStartを手動で呼び出してDefaultSceneをロード + var onStartMethod = typeof(PrometeApp).GetMethod("OnStart", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!; + var genericOnStartMethod = onStartMethod.MakeGenericMethod(defaultSceneType); + genericOnStartMethod.Invoke(app, null); + + // Assert + app.Root.Should().NotBeNull("Root container should be initialized even without explicit scene"); + } + + [Fact] + public void GetPlugin_ShouldWork_WhenRunningWithoutScene() + { + // Arrange + var app = PrometeApp.Create() + .Use() + .BuildWithHeadless(); + + // Act + var console = app.GetPlugin(); + + // Assert + console.Should().NotBeNull("Plugins should be accessible even when running without explicit scene"); + } + + [Fact] + public void DefaultScene_ShouldBeRegistered() + { + // Arrange + var app = PrometeApp.Create() + .BuildWithHeadless(); + + // DefaultSceneの型を取得する + var defaultSceneType = typeof(PrometeApp).GetNestedType("DefaultScene", System.Reflection.BindingFlags.NonPublic)!; + + // Act - DefaultSceneのロードを試みる + var loadSceneMethod = typeof(PrometeApp).GetMethod("LoadScene", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance, [typeof(Type)])!; + + // Assert - 例外がスローされないことを確認 + var act = () => loadSceneMethod.Invoke(app, [defaultSceneType]); + act.Should().NotThrow("DefaultScene should be registered and loadable"); + } +} diff --git a/Promete/PrometeApp.cs b/Promete/PrometeApp.cs index d6a45a9..8559a0b 100644 --- a/Promete/PrometeApp.cs +++ b/Promete/PrometeApp.cs @@ -136,6 +136,25 @@ public int Run(WindowOptions opts) where TScene : Scene return _statusCode; } + /// + /// Promete アプリケーションをシーンなしで実行します。 + /// + /// 終了ステータスコード。 + public int Run() + { + return Run(WindowOptions.Default); + } + + /// + /// Promete アプリケーションをシーンなしで実行します。 + /// + /// ウィンドウのオプション。 + /// 終了ステータスコード。 + public int Run(WindowOptions opts) + { + return Run(opts); + } + /// /// 指定したステータスコードで Promete アプリケーションを終了します。 /// @@ -379,6 +398,9 @@ private void ProcessNextFrameQueue() private void RegisterAllScenes() { + // DefaultScene を明示的に登録 + _services.AddTransient(); + var asm = Assembly.GetEntryAssembly() ?? throw new InvalidOperationException("There is no entry assembly."); // Scene 派生クラスを全て取得する var types = asm.GetTypes(); @@ -403,6 +425,14 @@ private Scene GetScene(Type scene) /// public event Action? SceneWillChange; + /// + /// シーンを使用せずにアプリケーションを実行する際に使用されるデフォルトの空のシーン。 + /// + [IgnoredScene] + private sealed class DefaultScene : Scene + { + } + /// /// Promete アプリケーションを構築するためのビルダークラスです。 /// From 083a23c0763a9964ab23bd44eb7cc07dc71d60a0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:06:10 +0000 Subject: [PATCH 4/7] Add demo example for sceneless app Co-authored-by: EbiseLutica <7106976+EbiseLutica@users.noreply.github.com> --- Promete.Example/examples/ScenelessDemo.cs | 53 +++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 Promete.Example/examples/ScenelessDemo.cs diff --git a/Promete.Example/examples/ScenelessDemo.cs b/Promete.Example/examples/ScenelessDemo.cs new file mode 100644 index 0000000..b62fabd --- /dev/null +++ b/Promete.Example/examples/ScenelessDemo.cs @@ -0,0 +1,53 @@ +using Promete.Example.Kernel; +using Promete.Input; + +namespace Promete.Example.examples; + +/// +/// シーンを使わずに PrometeApp を実行するデモ +/// +[Demo("/sceneless", "シーンを使わずに起動")] +public class ScenelessDemo(Keyboard keyboard, ConsoleLayer console) : Scene +{ + private int frameCount; + + public override void OnStart() + { + console.Print("Sceneless Demo"); + console.Print("このデモは、将来的にはシーンなしで実行できます。"); + console.Print(""); + console.Print("例:"); + console.Print("var app = PrometeApp.Create()"); + console.Print(" .Use()"); + console.Print(" .Use()"); + console.Print(" .BuildWithOpenGLDesktop();"); + console.Print(""); + console.Print("app.Window.Start += () => {"); + console.Print(" console.Print(\"Hello, world!\");"); + console.Print("};"); + console.Print(""); + console.Print("app.Window.Update += () => {"); + console.Print(" if (keyboard.Escape.IsKeyDown)"); + console.Print(" app.Exit(0);"); + console.Print("};"); + console.Print(""); + console.Print("return app.Run();"); + console.Print(""); + console.Print("[ESC] 戻る"); + } + + public override void OnUpdate() + { + frameCount++; + + if (frameCount % 60 == 0) + { + console.Print($"Frame: {frameCount}"); + } + + if (keyboard.Escape.IsKeyUp) + { + App.LoadScene(); + } + } +} From 3a65e3a9d5e3130377d607931d32dc223dc2958b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:10:32 +0000 Subject: [PATCH 5/7] Refactor tests to reduce code duplication Co-authored-by: EbiseLutica <7106976+EbiseLutica@users.noreply.github.com> --- Promete.Test/ScenelessRunTests.cs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/Promete.Test/ScenelessRunTests.cs b/Promete.Test/ScenelessRunTests.cs index 5b45591..1e0adab 100644 --- a/Promete.Test/ScenelessRunTests.cs +++ b/Promete.Test/ScenelessRunTests.cs @@ -1,10 +1,19 @@ using FluentAssertions; using Promete.Headless; +using System.Reflection; namespace Promete.Test; public class ScenelessRunTests { + /// + /// DefaultScene の型を取得するヘルパーメソッド + /// + private static Type GetDefaultSceneType() + { + return typeof(PrometeApp).GetNestedType("DefaultScene", BindingFlags.NonPublic)!; + } + [Fact] public void Run_WithoutScene_ShouldHaveValidRoot() { @@ -12,12 +21,9 @@ public void Run_WithoutScene_ShouldHaveValidRoot() var app = PrometeApp.Create() .BuildWithHeadless(); - // DefaultSceneの型を取得する - var defaultSceneType = typeof(PrometeApp).GetNestedType("DefaultScene", System.Reflection.BindingFlags.NonPublic)!; - // OnStartを手動で呼び出してDefaultSceneをロード - var onStartMethod = typeof(PrometeApp).GetMethod("OnStart", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!; - var genericOnStartMethod = onStartMethod.MakeGenericMethod(defaultSceneType); + var onStartMethod = typeof(PrometeApp).GetMethod("OnStart", BindingFlags.NonPublic | BindingFlags.Instance)!; + var genericOnStartMethod = onStartMethod.MakeGenericMethod(GetDefaultSceneType()); genericOnStartMethod.Invoke(app, null); // Assert @@ -46,14 +52,11 @@ public void DefaultScene_ShouldBeRegistered() var app = PrometeApp.Create() .BuildWithHeadless(); - // DefaultSceneの型を取得する - var defaultSceneType = typeof(PrometeApp).GetNestedType("DefaultScene", System.Reflection.BindingFlags.NonPublic)!; - // Act - DefaultSceneのロードを試みる - var loadSceneMethod = typeof(PrometeApp).GetMethod("LoadScene", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance, [typeof(Type)])!; + var loadSceneMethod = typeof(PrometeApp).GetMethod("LoadScene", BindingFlags.Public | BindingFlags.Instance, [typeof(Type)])!; // Assert - 例外がスローされないことを確認 - var act = () => loadSceneMethod.Invoke(app, [defaultSceneType]); + var act = () => loadSceneMethod.Invoke(app, [GetDefaultSceneType()]); act.Should().NotThrow("DefaultScene should be registered and loadable"); } } From a185a917cb084a70d290e6ec27aea3978723138a Mon Sep 17 00:00:00 2001 From: Ebise Lutica <7106976+EbiseLutica@users.noreply.github.com> Date: Thu, 6 Nov 2025 01:43:20 +0900 Subject: [PATCH 6/7] Delete Promete.Docs/obj\Debug/\package.g.props --- "Promete.Docs/obj\\Debug/\\package.g.props" | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 "Promete.Docs/obj\\Debug/\\package.g.props" diff --git "a/Promete.Docs/obj\\Debug/\\package.g.props" "b/Promete.Docs/obj\\Debug/\\package.g.props" deleted file mode 100644 index 45637c6..0000000 --- "a/Promete.Docs/obj\\Debug/\\package.g.props" +++ /dev/null @@ -1,21 +0,0 @@ - - - - promete-docs - 1.0.0 - - astro dev - astro dev - astro build - astro preview - [] - - MIT - pnpm@8.15.1 - ^0.29.2 - ^4.16.14 - ^1.49.0 - ^2.1.0 - ^1.81.0 - - \ No newline at end of file From 15f09960e20226b118347a064ce65e2426fe47a5 Mon Sep 17 00:00:00 2001 From: Ebise Lutica <7106976+EbiseLutica@users.noreply.github.com> Date: Thu, 6 Nov 2025 01:43:34 +0900 Subject: [PATCH 7/7] Delete Promete.Example/examples/ScenelessDemo.cs --- Promete.Example/examples/ScenelessDemo.cs | 53 ----------------------- 1 file changed, 53 deletions(-) delete mode 100644 Promete.Example/examples/ScenelessDemo.cs diff --git a/Promete.Example/examples/ScenelessDemo.cs b/Promete.Example/examples/ScenelessDemo.cs deleted file mode 100644 index b62fabd..0000000 --- a/Promete.Example/examples/ScenelessDemo.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Promete.Example.Kernel; -using Promete.Input; - -namespace Promete.Example.examples; - -/// -/// シーンを使わずに PrometeApp を実行するデモ -/// -[Demo("/sceneless", "シーンを使わずに起動")] -public class ScenelessDemo(Keyboard keyboard, ConsoleLayer console) : Scene -{ - private int frameCount; - - public override void OnStart() - { - console.Print("Sceneless Demo"); - console.Print("このデモは、将来的にはシーンなしで実行できます。"); - console.Print(""); - console.Print("例:"); - console.Print("var app = PrometeApp.Create()"); - console.Print(" .Use()"); - console.Print(" .Use()"); - console.Print(" .BuildWithOpenGLDesktop();"); - console.Print(""); - console.Print("app.Window.Start += () => {"); - console.Print(" console.Print(\"Hello, world!\");"); - console.Print("};"); - console.Print(""); - console.Print("app.Window.Update += () => {"); - console.Print(" if (keyboard.Escape.IsKeyDown)"); - console.Print(" app.Exit(0);"); - console.Print("};"); - console.Print(""); - console.Print("return app.Run();"); - console.Print(""); - console.Print("[ESC] 戻る"); - } - - public override void OnUpdate() - { - frameCount++; - - if (frameCount % 60 == 0) - { - console.Print($"Frame: {frameCount}"); - } - - if (keyboard.Escape.IsKeyUp) - { - App.LoadScene(); - } - } -}