[NUI] Add IsUsingXaml
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / Common / WeakEvent.cs
1 /*
2  * Copyright (c) 2020 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.ComponentModel;
20 using System.Collections.Generic;
21 using System.Reflection;
22
23 namespace Tizen.NUI
24 {
25     /// <summary>
26     /// The WeakEvent without holding strong reference of event handler.
27     /// </summary>
28     [EditorBrowsable(EditorBrowsableState.Never)]
29     public class WeakEvent<T> where T : Delegate
30     {
31         private const int addThreshold = 1000; // Experimetal constant
32         private const int listLengthThreshold = 1000; // Experimetal constant
33         private int cleanUpAddCount = 0;
34         private List<WeakHandler<T>> handlers = new List<WeakHandler<T>>();
35
36         /// <summary>
37         /// The count of currently added event handlers.
38         /// </summary>
39         [EditorBrowsable(EditorBrowsableState.Never)]
40         protected int Count => handlers.Count;
41
42         /// <summary>
43         /// Add an event handler.
44         /// </summary>
45         [EditorBrowsable(EditorBrowsableState.Never)]
46         public virtual void Add(T handler)
47         {
48             handlers.Add(new WeakHandler<T>(handler));
49             OnCountIncreased();
50
51             CleanUpDeadHandlersIfNeeds();
52         }
53
54         /// <summary>
55         /// Remove last added event handler.
56         /// </summary>
57         [EditorBrowsable(EditorBrowsableState.Never)]
58         public virtual void Remove(T handler)
59         {
60             int lastIndex = handlers.FindLastIndex(item => item.Equals(handler));
61
62             if (lastIndex >= 0)
63             {
64                 handlers.RemoveAt(lastIndex);
65                 OnCountDicreased();
66             }
67         }
68
69         /// <summary>
70         /// Invoke event handlers.
71         /// </summary>
72         [EditorBrowsable(EditorBrowsableState.Never)]
73         public void Invoke(object sender, EventArgs args)
74         {
75             // Iterate copied one to prevent addition/removal item in the handler call.
76             var copiedArray = handlers.ToArray();
77             foreach (var item in copiedArray)
78             {
79                 item.Invoke(sender, args);
80             }
81
82             // Clean up GC items
83             CleanUpDeadHandlers();
84         }
85
86         /// <summary>
87         /// Invoked when the event handler count is increased.
88         /// </summary>
89         [EditorBrowsable(EditorBrowsableState.Never)]
90         protected virtual void OnCountIncreased()
91         {
92         }
93
94         /// <summary>
95         /// Invoked when the event handler count is decreased.
96         /// </summary>
97         protected virtual void OnCountDicreased()
98         {
99         }
100
101         private void CleanUpDeadHandlersIfNeeds()
102         {
103             // Once the list count exceed 'listLengthThreshold', do the clean-up every 'addThreshold' add operations.
104             // When list count go below `listLengthThreshold` before clean-up, pause operation counting.
105             if (handlers.Count > listLengthThreshold && ++cleanUpAddCount > addThreshold)
106             {
107                 CleanUpDeadHandlers();
108             }
109         }
110
111         private void CleanUpDeadHandlers()
112         {
113             cleanUpAddCount = 0;
114             int count = handlers.Count;
115             handlers.RemoveAll(item => !item.IsAlive);
116             if (count > handlers.Count) OnCountDicreased();
117         }
118
119         internal class WeakHandler<U> where U : Delegate
120         {
121             private WeakReference weakTarget; // Null value means the method is static.
122             private MethodInfo methodInfo;
123
124             public WeakHandler(U handler)
125             {
126                 Delegate d = (Delegate)(object)handler;
127                 if (d.Target != null) weakTarget = new WeakReference(d.Target);
128                 methodInfo = d.Method;
129             }
130
131             private bool IsStatic => weakTarget == null;
132
133             public bool IsAlive
134             {
135                 get
136                 {
137                     var rooting = weakTarget?.Target;
138
139                     return IsStatic || !IsDisposed(rooting);
140                 }
141             }
142
143             private static bool IsDisposed(object target)
144             {
145                 if (target == null) return true;
146
147                 if (target is BaseHandle basehandle) return basehandle.Disposed || basehandle.IsDisposeQueued;
148
149                 if (target is Disposable disposable) return disposable.Disposed || disposable.IsDisposeQueued;
150
151                 return false;
152             }
153
154             public bool Equals(U handler)
155             {
156                 Delegate other = (Delegate)(object)handler;
157                 bool isOtherStatic = other.Target == null;
158                 return (isOtherStatic || weakTarget?.Target == other.Target) && methodInfo.Equals(other.Method);
159             }
160
161             public void Invoke(params object[] args)
162             {
163                 if (IsStatic)
164                 {
165                     Delegate.CreateDelegate(typeof(U), methodInfo).DynamicInvoke(args);
166                 }
167                 else
168                 {
169                     // Because GC is done in other thread,
170                     // it needs to check again that the reference is still alive before calling method.
171                     // To do that, the reference should be assigned to the local variable first.
172                     var rooting = weakTarget.Target;
173
174                     if (IsAlive)
175                     {
176                         Delegate.CreateDelegate(typeof(U), rooting, methodInfo).DynamicInvoke(args);
177                     }
178                 }
179             }
180         }
181     }
182 }