[NUI] Add API for ProcessController without Initialize
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / internal / Common / DisposeQueue.cs
1 /*
2  * Copyright(c) 2021 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 using System;
19 using System.Collections.Generic;
20 using System.Diagnostics.CodeAnalysis;
21 using System.Linq;
22
23 namespace Tizen.NUI
24 {
25     [SuppressMessage("Microsoft.Design", "CA1001:Types that own disposable fields should be disposable", Justification = "This is a singleton class and is not disposed")]
26     internal class DisposeQueue
27     {
28         private static readonly DisposeQueue disposableQueue = new DisposeQueue();
29         private List<IDisposable> disposables = new List<IDisposable>();
30         private System.Object listLock = new object();
31
32         // Dispose incrementally at least max(100, incrementallyDisposedQueue * 20%) for each 1 event callback
33         private const long minimumIncrementalCount = 100;
34         private const long minimumIncrementalRate = 20;
35         private List<IDisposable> incrementallyDisposedQueue = new List<IDisposable>();
36
37         private EventThreadCallback eventThreadCallback;
38         private EventThreadCallback.CallbackDelegate disposeQueueProcessDisposablesDelegate;
39
40         private bool initialized = false;
41         private bool processorRegistered = false;
42         private bool eventThreadCallbackTriggered = false;
43
44         private bool incrementalDisposeSupported = false;
45         private bool fullCollectRequested = false;
46
47         private DisposeQueue()
48         {
49         }
50
51         ~DisposeQueue()
52         {
53             Tizen.Log.Debug("NUI", $"DisposeQueue is destroyed\n");
54             initialized = false;
55             if (processorRegistered && ProcessorController.Instance.Initialized)
56             {
57                 processorRegistered = false;
58                 ProcessorController.Instance.ProcessorOnceEvent -= TriggerProcessDisposables;
59             }
60         }
61
62         public static DisposeQueue Instance
63         {
64             get { return disposableQueue; }
65         }
66
67         public bool IncrementalDisposeSupported
68         {
69             get => incrementalDisposeSupported;
70             set => incrementalDisposeSupported = value;
71         }
72
73         public bool FullyDisposeNextCollect
74         {
75             get => fullCollectRequested;
76             set => fullCollectRequested = value;
77         }
78
79         public void Initialize()
80         {
81             if (!initialized)
82             {
83                 disposeQueueProcessDisposablesDelegate = new EventThreadCallback.CallbackDelegate(ProcessDisposables);
84                 eventThreadCallback = new EventThreadCallback(disposeQueueProcessDisposablesDelegate);
85                 initialized = true;
86
87                 DebugFileLogging.Instance.WriteLog("DiposeTest START");
88             }
89         }
90
91         public void Add(IDisposable disposable)
92         {
93             lock (listLock)
94             {
95                 disposables.Add(disposable);
96             }
97
98             if (initialized && eventThreadCallback != null)
99             {
100                 if (!eventThreadCallbackTriggered)
101                 {
102                     eventThreadCallbackTriggered = true;
103                     eventThreadCallback.Trigger();
104                 }
105             }
106             else
107             {
108                 // Flush Disposable queue synchronously if it is not initialized yet.
109                 // TODO : Need to check thread here if we need.
110                 ProcessDisposables();
111             }
112         }
113
114         public void TriggerProcessDisposables(object o, EventArgs e)
115         {
116             processorRegistered = false;
117
118             if (initialized && eventThreadCallback != null)
119             {
120                 if (!eventThreadCallbackTriggered)
121                 {
122                     eventThreadCallbackTriggered = true;
123                     eventThreadCallback.Trigger();
124                 }
125             }
126             else
127             {
128                 // Flush Disposable queue synchronously if it is not initialized yet.
129                 // TODO : Need to check thread here if we need.
130                 ProcessDisposables();
131             }
132         }
133
134         public void ProcessDisposables()
135         {
136             eventThreadCallbackTriggered = false;
137
138             lock (listLock)
139             {
140                 if (disposables.Count > 0)
141                 {
142                     DebugFileLogging.Instance.WriteLog($"Newly add {disposables.Count} count of disposables. Total disposables count is {incrementallyDisposedQueue.Count + disposables.Count}.\n");
143                     // Move item from end, due to the performance issue.
144                     while (disposables.Count > 0)
145                     {
146                         var disposable = disposables.Last();
147                         disposables.RemoveAt(disposables.Count - 1);
148                         incrementallyDisposedQueue.Add(disposable);
149                     }
150                     disposables.Clear();
151                 }
152             }
153
154             if (incrementallyDisposedQueue.Count > 0)
155             {
156                 if (!incrementalDisposeSupported ||
157                     (!fullCollectRequested && !ProcessorController.Instance.Initialized))
158                 {
159                     // Full Dispose if IncrementalDisposeSupported is false, or ProcessorController is not initialized yet.
160                     fullCollectRequested = true;
161                 }
162                 ProcessDisposablesIncrementally();
163             }
164         }
165
166         private void ProcessDisposablesIncrementally()
167         {
168             var disposeCount = fullCollectRequested ? incrementallyDisposedQueue.Count
169                                                     : Math.Min(incrementallyDisposedQueue.Count, Math.Max(minimumIncrementalCount, incrementallyDisposedQueue.Count * minimumIncrementalRate / 100));
170
171             DebugFileLogging.Instance.WriteLog((fullCollectRequested ? "Fully" : "Incrementally") + $" dispose {disposeCount} disposables. Will remained disposables count is {incrementallyDisposedQueue.Count - disposeCount}.\n");
172
173             fullCollectRequested = false;
174
175             // Dispose item from end, due to the performance issue.
176             while (disposeCount > 0 && incrementallyDisposedQueue.Count > 0)
177             {
178                 --disposeCount;
179                 var disposable = incrementallyDisposedQueue.Last();
180                 incrementallyDisposedQueue.RemoveAt(incrementallyDisposedQueue.Count - 1);
181
182                 DebugFileLogging.Instance.WriteLog($"disposable.Dispose(); type={disposable.GetType().FullName}, hash={disposable.GetHashCode()}");
183                 disposable.Dispose();
184             }
185
186             if (incrementallyDisposedQueue.Count > 0)
187             {
188                 if (ProcessorController.Instance.Initialized && !processorRegistered)
189                 {
190                     processorRegistered = true;
191                     ProcessorController.Instance.ProcessorOnceEvent += TriggerProcessDisposables;
192                     ProcessorController.Instance.Awake();
193                 }
194             }
195
196             DebugFileLogging.Instance.WriteLog($"Incrementally dispose finished.\n");
197         }
198     }
199 }