@@ -20,9 +20,20 @@ public abstract class ExclusiveTouchBase(int playerNo, int vid, int pid, [CanBeN
2020 int timeoutMilliseconds = 20 )
2121{
2222 private UsbDevice device ;
23+ private readonly object deviceLock = new ( ) ;
24+ private volatile bool stopping ;
2325 private TouchSensorMapper touchSensorMapper ;
2426
25- public bool IsConnected => device != null ;
27+ public bool IsConnected
28+ {
29+ get
30+ {
31+ lock ( deviceLock )
32+ {
33+ return device != null ;
34+ }
35+ }
36+ }
2637
2738 protected int PlayerNo => playerNo ;
2839
@@ -63,116 +74,217 @@ public TouchUpdate(ushort x, ushort y, int fingerId, bool isPressed)
6374
6475 protected virtual string DiagnosticName => "ExclusiveTouch" ;
6576
66- public bool Start ( )
77+ public bool Start ( bool logConnectionFailure = true )
6778 {
68- // 方便组 2P
69- UsbDeviceFinder finder ;
70-
71- if ( ! string . IsNullOrWhiteSpace ( serialNumber ) )
79+ stopping = false ;
80+ if ( ! TryConnectDevice ( ) )
7281 {
73- // 优先使用序列号
74- finder = new UsbDeviceFinder ( vid , pid , serialNumber ) ;
82+ if ( logConnectionFailure )
83+ {
84+ MelonLogger . Msg ( $ "[ExclusiveTouch] Cannot connect { playerNo + 1 } P") ;
85+ }
86+ return false ;
7587 }
76- else if ( ! string . IsNullOrWhiteSpace ( locationPath ) )
88+
89+ try
7790 {
78- // 使用位置路径匹配
79- finder = new UsbDeviceLocationFinder ( vid , pid , locationPath ) ;
91+ touchSensorMapper = new TouchSensorMapper ( minX , minY , maxX , maxY , radius , flip ,
92+ aExtraRadius , bExtraRadius , cExtraRadius , dExtraRadius , eExtraRadius ) ;
93+
94+ for ( int i = 0 ; i < 256 ; i ++ )
95+ {
96+ allFingerPoints [ i ] = new TouchPoint ( ) ;
97+ }
98+
99+ Thread readThread = new ( ReadThread ) ;
100+ readThread . IsBackground = true ;
101+ readThread . Start ( ) ;
102+ Application . quitting += ( ) =>
103+ {
104+ stopping = true ;
105+ CloseCurrentDevice ( ) ;
106+ } ;
107+ return true ;
80108 }
81- else
109+ catch ( Exception e )
82110 {
83- // 使用第一个匹配的设备
84- finder = new UsbDeviceFinder ( vid , pid ) ;
111+ MelonLogger . Error ( $ "[ExclusiveTouch] Cannot initialize { playerNo + 1 } P: { e } ") ;
112+ CloseCurrentDevice ( ) ;
113+ return false ;
85114 }
86-
87- device = UsbDevice . OpenUsbDevice ( finder ) ;
88- if ( device == null )
115+ }
116+
117+ protected virtual void InitializeDevice ( UsbDevice usbDevice ) { }
118+
119+ private UsbDeviceFinder CreateFinder ( )
120+ {
121+ // 方便组 2P
122+ if ( ! string . IsNullOrWhiteSpace ( serialNumber ) )
89123 {
90- MelonLogger . Msg ( $ "[ExclusiveTouch] Cannot connect { playerNo + 1 } P") ;
91- return false ;
124+ // 优先使用序列号
125+ return new UsbDeviceFinder ( vid , pid , serialNumber ) ;
126+ }
127+
128+ if ( ! string . IsNullOrWhiteSpace ( locationPath ) )
129+ {
130+ // 使用位置路径匹配
131+ return new UsbDeviceLocationFinder ( vid , pid , locationPath ) ;
92132 }
93133
134+ // 使用第一个匹配的设备
135+ return new UsbDeviceFinder ( vid , pid ) ;
136+ }
137+
138+ private bool TryConnectDevice ( )
139+ {
140+ UsbDevice newDevice = null ;
94141 try
95142 {
96- if ( device is WinUsbDevice winUsbDevice )
143+ newDevice = UsbDevice . OpenUsbDevice ( CreateFinder ( ) ) ;
144+ if ( newDevice == null ) return false ;
145+
146+ if ( newDevice is WinUsbDevice winUsbDevice )
97147 {
98148 // 触摸屏固件不能可靠处理 WinUSB 的选择性挂起
99149 winUsbDevice . PowerPolicy . AutoSuspend = false ;
100150 }
101151
102- IUsbDevice wholeDevice = device as IUsbDevice ;
152+ IUsbDevice wholeDevice = newDevice as IUsbDevice ;
103153 if ( wholeDevice != null &&
104154 ( ! wholeDevice . SetConfiguration ( configuration ) || ! wholeDevice . ClaimInterface ( interfaceNumber ) ) )
105155 {
106- MelonLogger . Error ( $ "[ExclusiveTouch] Cannot initialize { playerNo + 1 } P") ;
107- device . Close ( ) ;
108- device = null ;
109- return false ;
110- }
111- InitializeDevice ( device ) ;
112- touchSensorMapper = new TouchSensorMapper ( minX , minY , maxX , maxY , radius , flip ,
113- aExtraRadius , bExtraRadius , cExtraRadius , dExtraRadius , eExtraRadius ) ;
114-
115- for ( int i = 0 ; i < 256 ; i ++ )
116- {
117- allFingerPoints [ i ] = new TouchPoint ( ) ;
156+ throw new InvalidOperationException ( "USB interface setup failed" ) ;
118157 }
119158
120- Thread readThread = new ( ReadThread ) ;
121- readThread . Start ( ) ;
122- TouchStatusProvider . RegisterTouchStatusProvider ( playerNo , GetTouchState ) ;
123- Application . quitting += ( ) =>
159+ InitializeDevice ( newDevice ) ;
160+ lock ( deviceLock )
124161 {
125- var tmpDevice = device ;
126- device = null ;
127- if ( wholeDevice != null )
162+ if ( stopping )
128163 {
129- wholeDevice . ReleaseInterface ( interfaceNumber ) ;
164+ CloseDevice ( newDevice ) ;
165+ return false ;
130166 }
131- tmpDevice . Close ( ) ;
132- } ;
167+
168+ device = newDevice ;
169+ }
170+ ExclusiveTouchDiagnostics . Log (
171+ "{0} player={1} connected driver={2}" ,
172+ DiagnosticName , playerNo + 1 , newDevice . DriverMode ) ;
133173 return true ;
134174 }
135175 catch ( Exception e )
136176 {
137- MelonLogger . Error ( $ "[ExclusiveTouch] Cannot initialize { playerNo + 1 } P: { e } ") ;
138- device ? . Close ( ) ;
139- device = null ;
177+ MelonLogger . Error ( $ "[ExclusiveTouch] Cannot initialize { playerNo + 1 } P: { e . Message } ") ;
178+ if ( newDevice != null ) CloseDevice ( newDevice ) ;
140179 return false ;
141180 }
142181 }
143182
144- protected virtual void InitializeDevice ( UsbDevice usbDevice ) { }
183+ private void CloseCurrentDevice ( )
184+ {
185+ UsbDevice oldDevice ;
186+ lock ( deviceLock )
187+ {
188+ oldDevice = device ;
189+ device = null ;
190+ }
191+
192+ if ( oldDevice != null ) CloseDevice ( oldDevice ) ;
193+ }
194+
195+ private bool IsCurrentDevice ( UsbDevice target )
196+ {
197+ lock ( deviceLock )
198+ {
199+ return ReferenceEquals ( target , device ) ;
200+ }
201+ }
202+
203+ private void CloseDevice ( UsbDevice target )
204+ {
205+ try
206+ {
207+ if ( target is IUsbDevice wholeDevice )
208+ {
209+ wholeDevice . ReleaseInterface ( interfaceNumber ) ;
210+ }
211+ }
212+ catch ( Exception e )
213+ {
214+ MelonLogger . Warning ( $ "[ExclusiveTouch] Cannot release { playerNo + 1 } P interface: { e . Message } ") ;
215+ }
216+
217+ try
218+ {
219+ target . Close ( ) ;
220+ }
221+ catch ( Exception e )
222+ {
223+ MelonLogger . Warning ( $ "[ExclusiveTouch] Cannot close { playerNo + 1 } P device: { e . Message } ") ;
224+ }
225+ }
145226
146227 private void ReadThread ( )
147228 {
148229 byte [ ] buffer = new byte [ packetSize ] ;
149- var reader = device . OpenEndpointReader ( endpoint ) ;
150230 using var pinnedBuffer = new PinnedHandle ( buffer ) ;
151-
231+
152232 try
153233 {
154- while ( device != null )
234+ while ( ! stopping )
155235 {
156- int bytesRead ;
157- ErrorCode ec = reader . Read ( pinnedBuffer . Handle , 0 , buffer . Length , 100 , out bytesRead ) ; // 100ms 超时
236+ UsbDevice currentDevice ;
237+ lock ( deviceLock )
238+ {
239+ currentDevice = device ;
240+ }
158241
159- if ( ec != ErrorCode . None )
242+ if ( currentDevice == null )
160243 {
161- if ( ec == ErrorCode . IoTimedOut ) continue ; // 超时就继续等
162- MelonLogger . Msg ( $ "[ExclusiveTouch] { playerNo + 1 } P: 读取错误: { ec } " ) ;
163- break ;
244+ Thread . Sleep ( 1000 ) ;
245+ TryConnectDevice ( ) ;
246+ continue ;
164247 }
165248
166- if ( bytesRead > 0 )
249+ try
250+ {
251+ using var reader = currentDevice . OpenEndpointReader ( endpoint ) ;
252+ while ( ! stopping )
253+ {
254+ int bytesRead ;
255+ ErrorCode ec = reader . Read ( pinnedBuffer . Handle , 0 , buffer . Length , 100 , out bytesRead ) ; // 100ms 超时
256+
257+ if ( ec == ErrorCode . IoTimedOut ) continue ; // 超时就继续等
258+ if ( ec != ErrorCode . None )
259+ {
260+ MelonLogger . Msg ( $ "[ExclusiveTouch] { playerNo + 1 } P: 读取错误: { ec } ,尝试重连") ;
261+ ExclusiveTouchDiagnostics . Log (
262+ "{0} player={1} reconnect reason=read-error code={2}" ,
263+ DiagnosticName , playerNo + 1 , ec ) ;
264+ CloseCurrentDevice ( ) ;
265+ break ;
266+ }
267+
268+ if ( bytesRead > 0 && IsCurrentDevice ( currentDevice ) )
269+ {
270+ OnTouchData ( buffer ) ;
271+ }
272+ }
273+ }
274+ catch ( Exception e )
167275 {
168- OnTouchData ( buffer ) ;
276+ if ( stopping ) break ;
277+ MelonLogger . Msg ( $ "[ExclusiveTouch] { playerNo + 1 } P: 读取异常: { e . Message } ,尝试重连") ;
278+ ExclusiveTouchDiagnostics . Log (
279+ "{0} player={1} reconnect reason=read-exception error={2}" ,
280+ DiagnosticName , playerNo + 1 , e . Message ) ;
281+ CloseCurrentDevice ( ) ;
169282 }
170283 }
171284 }
172285 finally
173286 {
174- // 确保 reader 被正确释放
175- reader ? . Dispose ( ) ;
287+ CloseCurrentDevice ( ) ;
176288 }
177289 }
178290
@@ -261,7 +373,7 @@ private ulong ComputeActiveMask()
261373 }
262374 return mask ;
263375 }
264- private ulong GetTouchState ( int player )
376+ internal ulong GetTouchState ( int player )
265377 {
266378 if ( player != playerNo ) return 0 ;
267379 lock ( touchLock )
0 commit comments