OSDN Git Service

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