OSDN Git Service

f7bbb4dcb1ff6f244af3b2db80a5dda7ced8de6f
[strokestylet/CsWin10Desktop3.git] / FDK24 / ApplicationBase.cs
1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Diagnostics;
5 using System.Linq;
6 using System.Threading.Tasks;
7 using System.Windows.Forms;
8 using Microsoft.VisualBasic.ApplicationServices;
9
10 namespace FDK
11 {
12         /// <summary>
13         /// アプリケーションフォームの基礎クラス。
14         /// </summary>
15         public class ApplicationBase : Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase
16         {
17                 /// <summary>
18                 /// 基本的に、このインスタンスを使用する任意のスレッドはこれを lock してからアクセスすること。
19                 /// </summary>
20                 protected readonly object スレッド間同期 = new object();
21
22                 public bool 全画面モードである
23                 {
24                         get;
25                         protected set;
26                 } = false;
27                 public bool ウィンドウモードである
28                 {
29                         get { return !this.全画面モードである; }
30                         protected set { this.全画面モードである = !value; }
31                 }
32
33                 public ApplicationBase() : base()
34                 {
35                         this.EnableVisualStyles = true;
36                         this.IsSingleInstance = true;   // 二重起動を禁止する
37                         this.MainForm = new Form();
38                         this.MainForm.Load += OnLoad;
39                         this.MainForm.FormClosing += OnClosing;
40                         this.MainForm.ClientSizeChanged += OnClientSizeChanged;
41                 }
42
43                 protected SharpDX.Size2F 設計画面サイズdpx = SharpDX.Size2F.Empty; // 初期化する() 内で設定すること。
44                 protected FDK.メディア.デバイスリソース デバイスリソース = null;
45                 protected System.Threading.Tasks.Task 進行描画スレッド = null;
46                 protected System.Threading.CancellationTokenSource 進行描画スレッドのキャンセルトークンソース = null;
47                 protected System.Threading.AutoResetEvent 進行描画スレッドの起動を完了した = new System.Threading.AutoResetEvent( false );
48
49                 protected virtual void 初期化する()
50                 {
51                         //----------------
52                         // 以下は実装例。
53                         //----------------
54                         lock( this.スレッド間同期 )
55                         {
56                                 Debug.Assert( null == this.デバイスリソース, "デバイスリソースの作成前であること。" );
57                                 this.設計画面サイズdpx = new SharpDX.Size2F( 640, 480 );
58                         }
59                 }
60                 protected virtual void 終了する()
61                 {
62                         //----------------
63                         // 以下は実装例。
64                         //----------------
65                         lock( this.スレッド間同期 )
66                         {
67                                 Debug.Assert( null != this.デバイスリソース, "デバイスリソースが解放される前であること。" );
68                         }
69                 }
70                 protected virtual void シーンを描画する()
71                 {
72                         //----------------
73                         // 以下は実装例。
74                         // このメソッドはGUIスレッドではなく進行描画スレッドから呼び出されるので注意する。
75                         //----------------
76
77                         lock( this.スレッド間同期 )
78                         {
79
80                                 // ここで、描画を行う。
81                                 // ...
82
83
84                                 // ここで、入力を行う。
85                                 // ...
86                         }
87
88                         // 表示する。垂直帰線待ちなどで時間がかかるので、lock しないこと。
89                         this.デバイスリソース.SwapChain.Present( 0, SharpDX.DXGI.PresentFlags.None );
90                 }
91                 protected virtual void デバイス依存リソースを解放する()
92                 {
93                         lock( this.スレッド間同期 )
94                         {
95                                 Debug.Assert( null != this.デバイスリソース );  // 解放前であること。
96
97                                 // ここで自分のデバイス依存リソースを解放する。
98                                 // ...
99                         }
100                 }
101                 protected virtual void デバイス依存リソースを再構築する()
102                 {
103                         lock( this.スレッド間同期 )
104                         {
105                                 Debug.Assert( null != this.デバイスリソース );  // 再生成済みであること。
106
107                                 // ここで自分のデバイス依存リソースを解放する。
108                                 // ...
109
110                         }
111                 }
112                 protected void 全画面モードとウィンドウモードを切り替える()
113                 {
114                         lock( this.スレッド間同期 )
115                         {
116                                 if( this.全画面モードである )
117                                 {
118                                         this.デバイスリソース.SwapChain.SetFullscreenState( false, null );  // ウィンドウモードへ。
119                                         this.ウィンドウモードである = true;
120                                 }
121                                 else
122                                 {
123                                         this.デバイスリソース.SwapChain.SetFullscreenState( true, null );    // 全画面モードへ。
124                                         this.全画面モードである = true;
125                                 }
126                         }
127                 }
128                 /// <summary>
129                 /// アプリが二重起動されたときに発生するイベント。
130                 /// </summary>
131                 /// <remarks>
132                 /// 後続のインスタンスは起動せず、既存のインスタンスに対してこのイベントが発生する。
133                 /// eventArg.CommandLine で、後続のインスタンスのコマンドライン引数を確認することができる。
134                 /// </remarks>
135                 protected override void OnStartupNextInstance( StartupNextInstanceEventArgs eventArgs )
136                 {
137                         // 必要がれば、派生クラスで実装すること。
138                 }
139
140                 private void OnLoad( object sender, EventArgs e )
141                 {
142                         FDK.Log.現在のスレッドに名前をつける( "GUI" );
143                         FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
144
145                         #region " アプリケーションを初期化する。"
146                         //----------------
147                         try
148                         {
149                                 FDK.Log.BeginInfo( "派生クラスを初期化します。" );
150                                 this.初期化する();
151                                 Debug.Assert( SharpDX.Size2F.Empty != this.設計画面サイズdpx, "初期化メソッド内で設計画面サイズを設定してあること。" );
152                                 FDK.Log.Info( $"設計画面サイズ: {this.設計画面サイズdpx}" );
153                                 FDK.Log.Info( $"物理画面サイズ: {this.MainForm.ClientSize}" );
154                         }
155                         finally
156                         {
157                                 FDK.Log.EndInfo( "派生クラスを初期化しました。" );
158                         }
159                         //----------------
160                         #endregion
161                         #region " デバイスリソースを作成する。"
162                         //----------------
163                         try
164                         {
165                                 FDK.Log.BeginInfo( "デバイスリソースを作成します。" );
166                                 lock( this.スレッド間同期 )
167                                 {
168                                         this.デバイスリソース = new メディア.デバイスリソース();
169                                         this.デバイスリソース.設計画面サイズdpx = this.設計画面サイズdpx;
170                                         this.デバイスリソース.すべてのリソースを作成する( this.MainForm.ClientSize, this.MainForm.Handle );
171                                 }
172                         }
173                         finally
174                         {
175                                 FDK.Log.EndInfo( "デバイスリソースを作成しました。" );
176                         }
177                         //----------------
178                         #endregion
179                         #region " 進行描画スレッドを開始する。"
180                         //----------------
181                         try
182                         {
183                                 FDK.Log.BeginInfo( "進行描画スレッドを開始します。" );
184                                 lock( this.スレッド間同期 )
185                                 {
186                                         this.進行描画スレッドのキャンセルトークンソース = new System.Threading.CancellationTokenSource();
187                                         this.進行描画スレッド = Task.Factory.StartNew( () => { this.進行描画スレッド処理(); }, this.進行描画スレッドのキャンセルトークンソース.Token );
188                                         this.進行描画スレッドの起動を完了した.WaitOne();        // スレッドの起動完了通知を待つ。
189                                 }
190                         }
191                         finally
192                         {
193                                 FDK.Log.EndInfo( "進行描画スレッドを開始しました。" );
194                         }
195                         //----------------
196                         #endregion
197
198                         FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
199                 }
200                 private void OnClosing( object sender, FormClosingEventArgs e )
201                 {
202                         FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
203
204                         // 進行描画スレッドを終了する。
205                         if( ( null != this.進行描画スレッドのキャンセルトークンソース ) &&
206                                 ( null != this.進行描画スレッド ) )
207                         {
208                                 try
209                                 {
210                                         this.進行描画スレッドのキャンセルトークンソース.Cancel();  // lock 内で呼び出したら絶対タイムアウトになるので注意。
211                                         FDK.Log.Info( "進行描画スレッドにキャンセルを発行しました。" );
212
213                                         bool done = this.進行描画スレッド.Wait( millisecondsTimeout: 5000 );    // タイムアウトは保険。
214                                         if( done )
215                                                 FDK.Log.Info( "進行描画スレッドの完了を確認しました。" );
216                                         else
217                                                 FDK.Log.ERROR( "進行描画スレッドの完了を時間内に確認できませんでした。" );
218                                 }
219                                 catch( AggregateException ex )
220                                 {
221                                         if( ex.InnerExceptions.Any( ( 内部例外 ) => ( 内部例外 is OperationCanceledException ) ) )
222                                         {
223                                                 // OK
224                                         }
225                                         else
226                                         {
227                                                 throw;  // NG
228                                         }
229                                 }
230                                 catch( ObjectDisposedException )
231                                 {
232                                         // タスクがすでに終わってた。
233                                 }
234                         }
235
236                         lock( this.スレッド間同期 )
237                         {
238                                 // 派生クラスの終了処理を呼び出す。
239                                 this.終了する();
240
241                                 // デバイスリソースを解放する。
242                                 this.デバイスリソース?.Dispose();
243                                 this.デバイスリソース = null;
244                         }
245
246                         FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
247                 }
248                 private void OnClientSizeChanged( object sender, EventArgs e )
249                 {
250                         FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
251                         FDK.Log.Info( $"新しいクライアントサイズ = {this.MainForm.ClientSize}" );
252
253                         lock( this.スレッド間同期 )
254                         {
255                                 #region " 実行条件チェック。"
256                                 //----------------
257                                 if( null == this.デバイスリソース )
258                                 {
259                                         FDK.Log.Info( " まだ初期化されてないので、何もしません。" );
260                                         return;
261                                 }
262                                 if( this.MainForm.WindowState == FormWindowState.Minimized )
263                                 {
264                                         FDK.Log.Info( "最小化されました。" );
265                                         return; // 何もしない
266                                 }
267                                 //----------------
268                                 #endregion
269
270                                 var dr = this.デバイスリソース;
271                                 Debug.Assert( null != dr, "デバイスリソースが作成済みであること。" );
272
273                                 // 現在の画面モードを取得しておく。(Alt+TABなど、勝手に全画面を解除されることもあるので。)
274                                 SharpDX.Mathematics.Interop.RawBool fullscreen;
275                                 SharpDX.DXGI.Output outputTarget;
276                                 dr.SwapChain.GetFullscreenState( out fullscreen, out outputTarget );
277                                 this.全画面モードである = fullscreen;
278                                 outputTarget?.Dispose();
279                                 FDK.Log.Info( $"現在、全画面モードである = {this.全画面モードである}" );
280
281                                 // (1) リソースを解放して、
282                                 this.デバイス依存リソースを解放する();
283                                 dr.サイズに依存するリソースを解放する();
284
285                                 // (2) 物理画面サイズを変更して、
286                                 dr.物理画面サイズpx = new SharpDX.Size2F( this.MainForm.ClientSize.Width, this.MainForm.ClientSize.Height );
287
288                                 // (3) リソースを再構築する。
289                                 dr.サイズに依存するリソースを作成する();
290                                 this.デバイス依存リソースを再構築する();
291                         }
292
293                         FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
294                 }
295
296                 private void 進行描画スレッド処理()
297                 {
298                         FDK.Log.現在のスレッドに名前をつける( "Main" );
299                         FDK.Log.Info( "進行描画スレッドを起動しました。" );
300
301                         this.進行描画スレッドの起動を完了した.Set();    // 起動完了通知 to 生成元
302
303                         while( true )
304                         {
305                                 bool アプリを終了せよ = false;
306
307                                 lock( this.スレッド間同期 )
308                                 {
309                                         // 別スレッドからキャンセル要求があれば、終了フラグを立てる。
310                                         if( this.進行描画スレッドのキャンセルトークンソース.Token.IsCancellationRequested )
311                                         {
312                                                 アプリを終了せよ = true;
313                                         }
314                                         else
315                                         {
316                                                 // D3Dデバイスが消失していれば再構築する。
317                                                 bool 異常発生 = false;
318                                                 this.デバイスリソース.D3Dデバイスが消失していれば再構築する( out 異常発生 );
319                                                 if( 異常発生 )
320                                                         アプリを終了せよ = true;
321                                         }
322                                 }
323
324                                 // 終了フラグがセットされていれば、ループを抜けてスレッドを終了する。
325                                 if( アプリを終了せよ )
326                                         break;
327
328                                 // それ以外なら、シーンを進行・描画する。
329                                 this.シーンを描画する();
330                         }
331
332                         FDK.Log.Info( "進行描画スレッドを終了します。" );
333                 }
334         };
335 }