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