From 4fe29bb931a9e177fb04d49b7e606fae2e5d0407 Mon Sep 17 00:00:00 2001 From: GesonAnko <59220704+Geson-anko@users.noreply.github.com> Date: Thu, 25 Jun 2026 10:41:49 +0000 Subject: [PATCH 1/7] =?UTF-8?q?feat(mod/camera):=20Camera=20IPC=20queue=20?= =?UTF-8?q?=E5=90=8D=E3=82=92=20env-override=20=E5=8C=96=E3=81=97=E5=A4=9A?= =?UTF-8?q?=E9=87=8D=E8=B5=B7=E5=8B=95=E3=81=AE=E8=A1=9D=E7=AA=81=E3=82=92?= =?UTF-8?q?=E9=98=B2=E3=81=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit engine ↔ renderer の Camera frame queue 名が固定 (resonite-io-camera-frames) で、 多重起動すると engine 同士が同名 queue を奪い合い renderer がクロス接続する問題を解消。 - IpcSocketPaths.QueueName を const から env-override 付き static プロパティへ (RESONITE_IO_CAMERA_QUEUE。GrpcHost.ResolveSocketPath と同じ「env → 既定」パターン)。 未設定時は従来固定名に fallback し単一起動は完全後方互換。 - ResoniteIOPlugin.Load() で env 未設定時のみインスタンス固有 token を生成・set。 renderer は engine の子プロセス (RenderSystem.StartRenderer の Process.Start) として env を継承するため両側が同じ token を読む。token set は renderer spawn より前の 最早期 Load() で行う必要がある (OnEngineReady では間に合わない)。 - renderer prebuilt を再生成 (IpcSocketPaths は prebuilt hash の入力)。 Co-Authored-By: Claude Opus 4.8 (1M context) --- mod/prebuilt/renderer.sha256 | 2 +- mod/prebuilt/renderer/ResoniteIO.Renderer.dll | Bin 12800 -> 12800 bytes mod/prebuilt/renderer/ResoniteIO.Renderer.pdb | Bin 10332 -> 10332 bytes .../renderer/ResoniteIO.RendererShared.dll | Bin 8192 -> 8704 bytes .../IpcSocketPaths.cs | 21 ++++++++- mod/src/ResoniteIO/ResoniteIOPlugin.cs | 21 +++++++++ .../IpcSocketPaths/IpcSocketPathsTests.cs | 44 ++++++++++++++++++ 7 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 mod/tests/ResoniteIO.Core.Tests/IpcSocketPaths/IpcSocketPathsTests.cs diff --git a/mod/prebuilt/renderer.sha256 b/mod/prebuilt/renderer.sha256 index 7bec8480..4b584a04 100644 --- a/mod/prebuilt/renderer.sha256 +++ b/mod/prebuilt/renderer.sha256 @@ -1 +1 @@ -6ca5f47b4593c3c561b6f3ec1ee4a50ec645fe8a30d7a98c7a780acf6b04fc89 +3ac72c3fdc00061147c7419fb86086612876f2cec2135e72ff1a4472df20818f diff --git a/mod/prebuilt/renderer/ResoniteIO.Renderer.dll b/mod/prebuilt/renderer/ResoniteIO.Renderer.dll index 6928936e1ae1eda4ded6fceef30a5a4fa0129fd6..003066ed185efc45bd22e18a138007f6ebc66dcd 100644 GIT binary patch delta 2851 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 B94i0- diff --git a/mod/prebuilt/renderer/ResoniteIO.Renderer.pdb b/mod/prebuilt/renderer/ResoniteIO.Renderer.pdb index bbcc8224115bed9319025022c344e01558ae3d0f..e6ea817e86ae6ab0f3c4bccc23c478627688e7bf 100644 GIT binary patch delta 202 zcmV;*05$*IP~1?Ed?n+d?CTV@8A`YJ=dvS?kg~m?mAC)^0037X0tg8T00031001|U zp%(#BvAK-}AO#BoOA!E3Jsw;`nZG!eSuA3QmIR8u2>#01cuA)>W0PhERw!g*WiT~0 zV_`L6H8x>0G%z(YHZeD1V>vW6H8(aiHDoe5IWsjgGG?>n1`Y!lp?#~j06+o&nj<6} z2t|iZtuPH}?r3`=lZ+%8A1-8UYybcOaZ>dbCzi6(cuV#S2vqB0Q E9xbgz>i_@% delta 202 zcmV;*05$*IP~1?Ed?hQ;q4fhxJw(16b2x0rNCAq~@wfm00037X0tg8T00031001_T zp%(#AvAK-}AP}}KK^(B=kv$3Pp-(NQK|;;)IoTuvLI7h@!RQZrL6c?%Rwy$$IA&yH zW@R)uFg0Q^GdE;6Wim50GBz|~FgZ11G&V6aVPZ8fWj3?q1`Y!lSABWB06+o&4NJes zcmXp&q{ch2|CKchlZ+%8A1-8UYybcOc4^0>0Du4hAdGLoI#Pf}l2pUB8x5=kvqB0Q E9v~z`AOHXW diff --git a/mod/prebuilt/renderer/ResoniteIO.RendererShared.dll b/mod/prebuilt/renderer/ResoniteIO.RendererShared.dll index f1a9c510fa1cf098cfe4c962efe1a8bef8732a6e..7527aeb534f46df469b005b616e951c87ba2ac21 100644 GIT binary patch delta 2293 zcmb7`du&ui7{$-r7u#*wZXcBLlI=rU+JZ~B+ob`LRG`?}k|qMX z1OxM(ujYuI82`?K68VHXD0ullkC4`8AB*Yqws2Ct7#xv8k#6P{w{N_97`|jMi zbMMY=G#H(>eRF2hywkmi9e0uGi5FfVora^&bOt&;t8g7J2Ifd32)K=ojFg0GAc6Xn zA6Q^#Xa#nq$Z@&{)AF#T+gn7w7;2voIl^OqFEWSpas+wIV$)z^Vm1&>0)%~TaG-mx zc2+M)@oE^!v_4Y z&*^Z+eOlJ)Y_djrz;C*Y0SiBCKN=PqbjiQPH6sxkPqi>X>(DLC)mPKsrAaPOY|>Uo zD4Z&ht*jGjh$})4u|`iMKQtvD2+0GA0*QtbF2yv`!WmVwN!7ogoP#QEH)Q2_^DooF z2<2xh{>pkeSW5 z#wtvQfdN=Ywa8qBg`DhQj6$i|Bt~JI-b%I@ZK5I?jNmPHNv0I}j5Bac@tk6wkWRAV z0p&cwS~}awif98bP!Zi@tP|zYT|&k)l=GswLCp2Y8{3$EnX4nP_&=(;Uu_Z*fwq3R zO$co2SDUCh`;}ay{e8-DRo}tfp_uwyspBzq#gw_zF?C3(3o*4o$lTSK+F*7H5y7`H zxe%1N)2BG^52#Wzz@rmQ*e@J-tkgzyiz6Zq*E3~qBSx4fg%gjJdLY)Ar>T}_$=pS( zF?&QjcE(h_DBv&lnNppOk=7-VfQX(gOFJF;lrFeq>K$>N&yEilKk$KTI7RBX$;WNr zBx$0GuZ)Eqj0Yg3Do+`*zK=aB{)J}=Tx5-gJLL0tfP7bQ+2H*rv9mCQbYe8=#yG_i z#mS1Lisg!xiXp`Y(!x4&5Z6e?sW`6};2e&bUR*hFhjLu4-sr*|;Ly*isQ{ZV1&Z4`WHVrK-F&+!`(mtq7y4E?ggKUEb1t zd!W!0`+c6s6S{A^gTncI5Ia42vVgrP`Mhu3JZ@|A`4KEbDHbrUVn2tRipiKwJ&%1A z%DLT5R}DUUL~3EOKydcEM;^Z5C_A(C@V+DU(X)0z%I-LQfN&WQE|+~FWs%qWKL@vm zrGCm!l}pb>99z8ZfXn_W^;B+MP1x)A*M|Hde{slHmex%*<;~zWZVyCd>u(dPSo^E%hWx5-;q=a8O zQdzot=a!!yIqRp`m(rRFV`gP#%#>rtKfZCZ{m$v@#ZMmu@5KHAkV8L_v8N*=P3tYT z*AL!mdxr1pcBMD#bLvooFud^dua;YjU$tV=ha%!Bhlft{sGfGwE6%5 delta 1974 zcmY+Fd2AI`6vn?h%j?3UbfX)K-JpdwwY;U}u}CYGf&^Ni#a2n$*Uc1+2o!?R@LIuY zu^{b)Y9T_gR3brxv=EJuloW6w28oLPgR(@T#y^N5Vq#+a?&(X7lbqk2?>l$yJ9p;J zoA$Eyk^`@2?r8mN5aHt%GSeriLLiAoUtkg z7mOjxg+-?3b=7N4!DU|RLX6R5 zx{zg7(BEii<_OD-iV($35f7CvbjcJ)l%Ar}yu-`tY|T%U=F38bJ7zRugh`|eugI9E zW&BKWc1a$z^vJ)hGqwve#Lp&mv&9-*REWK+Sahp7c7SqRP2mc(;`&(onaya$>!|)@ zg8_LYS`CRZxyC4}ZPpGv!MhcK4yrVJ2ddd-GG@bV7pvK*GZ&KO)?;dYdkIR(Z$l#T zC^GPa@SIG4k!>UT@+E-y!Zf~Ia zF087tp^Eb6xMbh;REZhj(T6SAq$2S^R3|#sb`^!MGj**Kx2#<%8V^L>QziCps^_wF z?FvflF6U@eYUqZ&U%6psYqu}*4pj^@g)@AP2Hqr1^z)Um@DgdGj?dnOYUU9L%E%Bm zFL#KrQ#h*k0J=EFzySGQ9-+4Yx-G>QN#Fm}5KqyVL&jpBFd!@yt`M#f)(V5dR?>wI zaw>0t&J%Ia%*REv*@gImPBE_IS93A_)oK~@6l)Ep%b4q2OFi?|Y9sy7qt4c6N8B!<_mI~{IL1DYFLpUHj zCQLW;92nszX+;0?hY#JXZXt)^;aA9$ z?EI0O8T-wP%ip?p_>r=qyXRaVU;0!z*(qB*VY6*k*wk~s{a3_5?Qb{A7hFy2=?H(t z^wfuSp5Bv@WbE@hKc&1e9Fw-ytnebhUD$#qKGqgA!$(yJKN|Sbc=<2g#AxPz^wRU7 li1h~612V!#tw#mv`RO*JX!zQUfMJ~W568{CuX+ky{{rAYU$_7O diff --git a/mod/src/ResoniteIO.RendererShared/IpcSocketPaths.cs b/mod/src/ResoniteIO.RendererShared/IpcSocketPaths.cs index 0f6c7592..ee5b336f 100644 --- a/mod/src/ResoniteIO.RendererShared/IpcSocketPaths.cs +++ b/mod/src/ResoniteIO.RendererShared/IpcSocketPaths.cs @@ -1,3 +1,5 @@ +using System; + namespace ResoniteIO.RendererShared; /// @@ -13,7 +15,24 @@ public static class IpcSocketPaths { public const string OwnerId = "net.mlshukai.resonite-io.camera"; - public const string QueueName = "resonite-io-camera-frames"; + /// を上書きする環境変数名。 + /// + /// 多重起動時に各インスタンスの queue を分離するための逃げ道。engine プロセスが + /// 起動最早期 (ResoniteIOPlugin.Load) でこの env を set し、renderer は + /// engine の子プロセスとして同じ値を継承するため、両側が同じ token を読む。 + /// 未設定時は従来固定名 () に fallback し、 + /// 単一起動・既存デプロイの挙動を保つ。GrpcHost.ResolveSocketPath と同じ + /// 「env override → 既定値」パターン。 + /// + public const string QueueNameEnvVar = "RESONITE_IO_CAMERA_QUEUE"; + + private const string _defaultQueueName = "resonite-io-camera-frames"; + + /// 共有メモリ queue の名前。 で上書き可能。 + public static string QueueName => + Environment.GetEnvironmentVariable(QueueNameEnvVar) is { Length: > 0 } overridden + ? overridden + : _defaultQueueName; public const string FrameMessageId = "frame"; diff --git a/mod/src/ResoniteIO/ResoniteIOPlugin.cs b/mod/src/ResoniteIO/ResoniteIOPlugin.cs index 836eead6..8d6e68ae 100644 --- a/mod/src/ResoniteIO/ResoniteIOPlugin.cs +++ b/mod/src/ResoniteIO/ResoniteIOPlugin.cs @@ -81,6 +81,27 @@ public override void Load() { Log = base.Log; + // 多重起動時の Camera IPC queue 衝突を防ぐため、インスタンス固有の queue token を + // 生成して自プロセス env に set する。renderer は engine の子プロセス + // (RenderSystem.StartRenderer の Process.Start, UseShellExecute=false) として + // この env を継承するので両側が同じ token を読む。renderer spawn は engine 起動の + // 早い段階 (OnEngineReady より前) なので set は必ず Load() で行う必要がある。 + // launcher が外から token を注入済みならそれを尊重 (上書きしない)。 + // QueueNameEnvVar は const のためインライン化され RendererShared を早期ロードしない。 + if ( + string.IsNullOrEmpty( + Environment.GetEnvironmentVariable(RendererShared.IpcSocketPaths.QueueNameEnvVar) + ) + ) + { + var token = "resonite-io-camera-" + Guid.NewGuid().ToString("N"); + Environment.SetEnvironmentVariable( + RendererShared.IpcSocketPaths.QueueNameEnvVar, + token + ); + Log.LogInfo($"Camera IPC queue token generated: {token}"); + } + // resolver は Core 型に触れる前に attach する必要があるため、ManualLogSource を // 直接渡し ILogSink を経由しない (上記 remarks 参照)。 var pluginDirectory = diff --git a/mod/tests/ResoniteIO.Core.Tests/IpcSocketPaths/IpcSocketPathsTests.cs b/mod/tests/ResoniteIO.Core.Tests/IpcSocketPaths/IpcSocketPathsTests.cs new file mode 100644 index 00000000..a546e5f2 --- /dev/null +++ b/mod/tests/ResoniteIO.Core.Tests/IpcSocketPaths/IpcSocketPathsTests.cs @@ -0,0 +1,44 @@ +using System; +using ResoniteIO.RendererShared; +using Xunit; + +namespace ResoniteIO.Core.Tests.IpcSocketPaths; + +/// +/// の env-override 解決を +/// 検証する。多重起動時に engine が token を env へ流し、renderer が継承して同名を読む経路の +/// 単体保証。env は process-global なので単一テスト内で set/clear を直列化し、元値を復元する。 +/// +public sealed class IpcSocketPathsTests +{ + /// env 未設定なら従来固定名、設定すればその値を返す。 + [Fact] + public void QueueName_honours_env_override_and_falls_back_to_default() + { + var original = Environment.GetEnvironmentVariable( + RendererShared.IpcSocketPaths.QueueNameEnvVar + ); + try + { + Environment.SetEnvironmentVariable(RendererShared.IpcSocketPaths.QueueNameEnvVar, null); + Assert.Equal("resonite-io-camera-frames", RendererShared.IpcSocketPaths.QueueName); + + Environment.SetEnvironmentVariable( + RendererShared.IpcSocketPaths.QueueNameEnvVar, + "resonite-io-camera-instance-a" + ); + Assert.Equal("resonite-io-camera-instance-a", RendererShared.IpcSocketPaths.QueueName); + + // 空文字は「未設定」と同じ扱いで fallback する。 + Environment.SetEnvironmentVariable(RendererShared.IpcSocketPaths.QueueNameEnvVar, ""); + Assert.Equal("resonite-io-camera-frames", RendererShared.IpcSocketPaths.QueueName); + } + finally + { + Environment.SetEnvironmentVariable( + RendererShared.IpcSocketPaths.QueueNameEnvVar, + original + ); + } + } +} From 95972a7516116885e0842eed7021f2c2448e65df Mon Sep 17 00:00:00 2001 From: GesonAnko <59220704+Geson-anko@users.noreply.github.com> Date: Thu, 25 Jun 2026 10:42:23 +0000 Subject: [PATCH 2/7] =?UTF-8?q?feat(python/launcher):=20--name=20=E3=81=A7?= =?UTF-8?q?=E3=83=87=E3=83=BC=E3=82=BF=E5=88=86=E9=9B=A2=E3=81=97=E3=81=9F?= =?UTF-8?q?=E5=A4=9A=E9=87=8D=E8=B5=B7=E5=8B=95=E3=81=A8=20terminate-all?= =?UTF-8?q?=20=E3=82=92=E5=86=8D=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 以前撤回 (b8f51c6) した多重起動を、データ分離忘れと IPC 衝突を解消して再実装。 launch: - name=