com.kylin.di 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +22 -0
- package/CHANGELOG.md +17 -0
- package/CLAUDE.md +58 -0
- package/LICENSE +21 -0
- package/README.md +704 -0
- package/Runtime/Attributes/InjectAttribute.cs +11 -0
- package/Runtime/Attributes/InjectAttribute.cs.meta +2 -0
- package/Runtime/Attributes/ViewModelAttribute.cs +17 -0
- package/Runtime/Attributes/ViewModelAttribute.cs.meta +2 -0
- package/Runtime/Attributes.meta +8 -0
- package/Runtime/Core/DIBehaviour.cs +70 -0
- package/Runtime/Core/DIBehaviour.cs.meta +2 -0
- package/Runtime/Core/LifetimeScope.cs +264 -0
- package/Runtime/Core/LifetimeScope.cs.meta +2 -0
- package/Runtime/Core.meta +8 -0
- package/Runtime/DI/DependencyBuilder.cs +114 -0
- package/Runtime/DI/DependencyBuilder.cs.meta +2 -0
- package/Runtime/DI/DependencyInjector.cs +104 -0
- package/Runtime/DI/DependencyInjector.cs.meta +2 -0
- package/Runtime/DI/InstanceFactory.cs +36 -0
- package/Runtime/DI/InstanceFactory.cs.meta +2 -0
- package/Runtime/DI/KDI.cs +54 -0
- package/Runtime/DI/KDI.cs.meta +2 -0
- package/Runtime/DI/Registration.cs +29 -0
- package/Runtime/DI/Registration.cs.meta +2 -0
- package/Runtime/DI/Scope.cs +183 -0
- package/Runtime/DI/Scope.cs.meta +2 -0
- package/Runtime/DI/ScopeBuilder.cs +68 -0
- package/Runtime/DI/ScopeBuilder.cs.meta +2 -0
- package/Runtime/DI/ScopeExtensions.cs +70 -0
- package/Runtime/DI/ScopeExtensions.cs.meta +2 -0
- package/Runtime/DI.meta +8 -0
- package/Runtime/Debug/ClosureAnalyzer.cs +377 -0
- package/Runtime/Debug/ClosureAnalyzer.cs.meta +2 -0
- package/Runtime/Debug/ClosureProfilerWindow.cs +435 -0
- package/Runtime/Debug/ClosureProfilerWindow.cs.meta +2 -0
- package/Runtime/Debug/SubscriberInfo.cs +661 -0
- package/Runtime/Debug/SubscriberInfo.cs.meta +3 -0
- package/Runtime/Debug.meta +3 -0
- package/Runtime/Kylin.DI.asmdef +14 -0
- package/Runtime/Kylin.DI.asmdef.meta +7 -0
- package/Runtime/SubscribableProperty/Reaction.cs +61 -0
- package/Runtime/SubscribableProperty/Reaction.cs.meta +2 -0
- package/Runtime/SubscribableProperty/SubscribableCollection.cs +325 -0
- package/Runtime/SubscribableProperty/SubscribableCollection.cs.meta +3 -0
- package/Runtime/SubscribableProperty/SubscribableCollectionExtensions.cs +24 -0
- package/Runtime/SubscribableProperty/SubscribableCollectionExtensions.cs.meta +3 -0
- package/Runtime/SubscribableProperty/SubscribableCommand.cs +52 -0
- package/Runtime/SubscribableProperty/SubscribableCommand.cs.meta +2 -0
- package/Runtime/SubscribableProperty/SubscribableDictionary.cs +350 -0
- package/Runtime/SubscribableProperty/SubscribableDictionary.cs.meta +3 -0
- package/Runtime/SubscribableProperty/SubscribableProperty.cs +119 -0
- package/Runtime/SubscribableProperty/SubscribableProperty.cs.meta +2 -0
- package/Runtime/SubscribableProperty/SubscribablePropertyExtensions.cs +39 -0
- package/Runtime/SubscribableProperty/SubscribablePropertyExtensions.cs.meta +3 -0
- package/Runtime/SubscribableProperty/SubscribablePropertyLinq.cs +86 -0
- package/Runtime/SubscribableProperty/SubscribablePropertyLinq.cs.meta +2 -0
- package/Runtime/SubscribableProperty.meta +8 -0
- package/Runtime/Update/IFixedUpdatable.cs +16 -0
- package/Runtime/Update/IFixedUpdatable.cs.meta +2 -0
- package/Runtime/Update/ILateUpdatable.cs +16 -0
- package/Runtime/Update/ILateUpdatable.cs.meta +2 -0
- package/Runtime/Update/IUpdatable.cs +16 -0
- package/Runtime/Update/IUpdatable.cs.meta +2 -0
- package/Runtime/Update/IUpdatePriority.cs +15 -0
- package/Runtime/Update/IUpdatePriority.cs.meta +2 -0
- package/Runtime/Update/UpdateLoopManager.cs +240 -0
- package/Runtime/Update/UpdateLoopManager.cs.meta +2 -0
- package/Runtime/Update.meta +8 -0
- package/Runtime.meta +8 -0
- package/package.json +19 -0
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.Linq;
|
|
4
|
+
using System.Reflection;
|
|
5
|
+
using System.Diagnostics;
|
|
6
|
+
using UnityEngine;
|
|
7
|
+
using Debug = UnityEngine.Debug;
|
|
8
|
+
#if UNITY_EDITOR
|
|
9
|
+
using UnityEditor;
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
namespace Kylin.SubscribableProperty
|
|
13
|
+
{
|
|
14
|
+
// 디버그 정보를 담는 클래스
|
|
15
|
+
public class SPSubscriberInfo
|
|
16
|
+
{
|
|
17
|
+
public WeakReference SubscriberRef { get; }
|
|
18
|
+
public string SubscriberTypeName { get; }
|
|
19
|
+
public string SubscriberInstanceName { get; }
|
|
20
|
+
public string MethodName { get; }
|
|
21
|
+
public DateTime SubscribeTime { get; }
|
|
22
|
+
public string SubscribeCallStack { get; }
|
|
23
|
+
|
|
24
|
+
public SPSubscriberInfo(object subscriber, string methodName)
|
|
25
|
+
{
|
|
26
|
+
SubscriberRef = new WeakReference(subscriber);
|
|
27
|
+
SubscriberTypeName = subscriber?.GetType().Name ?? "Unknown";
|
|
28
|
+
SubscriberInstanceName = GetInstanceName(subscriber);
|
|
29
|
+
MethodName = methodName;
|
|
30
|
+
SubscribeTime = DateTime.Now;
|
|
31
|
+
|
|
32
|
+
#if UNITY_EDITOR
|
|
33
|
+
// 호출 스택 저장 (성능을 위해 간단히)
|
|
34
|
+
var stackTrace = new StackTrace(3, true);
|
|
35
|
+
var frames = stackTrace.GetFrames()?.Take(5).ToArray();
|
|
36
|
+
if (frames != null && frames.Length > 0)
|
|
37
|
+
{
|
|
38
|
+
SubscribeCallStack = string.Join("\n", frames.Select(f =>
|
|
39
|
+
$"{f.GetMethod()?.DeclaringType?.Name}.{f.GetMethod()?.Name} (Line: {f.GetFileLineNumber()})"));
|
|
40
|
+
}
|
|
41
|
+
#endif
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private string GetInstanceName(object subscriber)
|
|
45
|
+
{
|
|
46
|
+
if (subscriber == null) return "null";
|
|
47
|
+
|
|
48
|
+
// Unity 컴포넌트인 경우
|
|
49
|
+
if (subscriber is MonoBehaviour mono && mono != null)
|
|
50
|
+
return $"{mono.name} ({mono.GetType().Name})";
|
|
51
|
+
|
|
52
|
+
// 일반 객체인 경우 이름 속성 찾기
|
|
53
|
+
try
|
|
54
|
+
{
|
|
55
|
+
var nameProperty = subscriber.GetType().GetProperty("Name") ??
|
|
56
|
+
subscriber.GetType().GetProperty("name");
|
|
57
|
+
if (nameProperty != null)
|
|
58
|
+
{
|
|
59
|
+
var name = nameProperty.GetValue(subscriber)?.ToString();
|
|
60
|
+
if (!string.IsNullOrEmpty(name))
|
|
61
|
+
return $"{name} ({subscriber.GetType().Name})";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch { /* ignore */ }
|
|
65
|
+
|
|
66
|
+
return $"{subscriber.GetType().Name}#{subscriber.GetHashCode()}";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public bool IsAlive => SubscriberRef.IsAlive;
|
|
70
|
+
public object Subscriber => SubscriberRef.Target;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 개별 프로퍼티의 디버그 정보
|
|
74
|
+
public class PropertyDebugInfo
|
|
75
|
+
{
|
|
76
|
+
public WeakReference PropertyRef { get; }
|
|
77
|
+
public string PropertyName { get; }
|
|
78
|
+
public string OwnerTypeName { get; }
|
|
79
|
+
public string OwnerInstanceName { get; }
|
|
80
|
+
public Type PropertyType { get; }
|
|
81
|
+
public DateTime CreatedTime { get; }
|
|
82
|
+
|
|
83
|
+
private readonly List<SPSubscriberInfo> _subscribers = new List<SPSubscriberInfo>();
|
|
84
|
+
private readonly object _lock = new object();
|
|
85
|
+
|
|
86
|
+
public PropertyDebugInfo(object property, string propertyName, object owner)
|
|
87
|
+
{
|
|
88
|
+
PropertyRef = new WeakReference(property);
|
|
89
|
+
PropertyType = property.GetType();
|
|
90
|
+
PropertyName = propertyName;
|
|
91
|
+
CreatedTime = DateTime.Now;
|
|
92
|
+
|
|
93
|
+
if (owner != null)
|
|
94
|
+
{
|
|
95
|
+
OwnerTypeName = owner.GetType().Name;
|
|
96
|
+
OwnerInstanceName = GetOwnerInstanceName(owner);
|
|
97
|
+
}
|
|
98
|
+
else
|
|
99
|
+
{
|
|
100
|
+
OwnerTypeName = "Unknown";
|
|
101
|
+
OwnerInstanceName = "Unknown";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private string GetOwnerInstanceName(object owner)
|
|
106
|
+
{
|
|
107
|
+
if (owner == null) return "null";
|
|
108
|
+
|
|
109
|
+
if (owner is MonoBehaviour mono && mono != null)
|
|
110
|
+
return $"{mono.name} ({mono.GetType().Name})";
|
|
111
|
+
|
|
112
|
+
try
|
|
113
|
+
{
|
|
114
|
+
var nameProperty = owner.GetType().GetProperty("Name") ??
|
|
115
|
+
owner.GetType().GetProperty("name");
|
|
116
|
+
if (nameProperty != null)
|
|
117
|
+
{
|
|
118
|
+
var name = nameProperty.GetValue(owner)?.ToString();
|
|
119
|
+
if (!string.IsNullOrEmpty(name))
|
|
120
|
+
return $"{name} ({owner.GetType().Name})";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch { /* ignore */ }
|
|
124
|
+
|
|
125
|
+
return $"{owner.GetType().Name}#{owner.GetHashCode()}";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
public void AddSubscriber(object subscriber, string methodName)
|
|
129
|
+
{
|
|
130
|
+
lock (_lock)
|
|
131
|
+
{
|
|
132
|
+
_subscribers.Add(new SPSubscriberInfo(subscriber, methodName));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public void RemoveSubscriber(object subscriber)
|
|
137
|
+
{
|
|
138
|
+
lock (_lock)
|
|
139
|
+
{
|
|
140
|
+
_subscribers.RemoveAll(s => ReferenceEquals(s.Subscriber, subscriber));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
public IEnumerable<SPSubscriberInfo> GetAliveSubscribers()
|
|
145
|
+
{
|
|
146
|
+
lock (_lock)
|
|
147
|
+
{
|
|
148
|
+
// 죽은 구독자들 제거
|
|
149
|
+
_subscribers.RemoveAll(s => !s.IsAlive);
|
|
150
|
+
return _subscribers.ToList();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public int SubscriberCount => GetAliveSubscribers().Count();
|
|
155
|
+
public bool IsAlive => PropertyRef.IsAlive;
|
|
156
|
+
public object Property => PropertyRef.Target;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 디버그 정보 관리 클래스
|
|
160
|
+
public static class SubscribablePropertyDebugger
|
|
161
|
+
{
|
|
162
|
+
private static readonly Dictionary<object, PropertyDebugInfo> _debugInfos
|
|
163
|
+
= new Dictionary<object, PropertyDebugInfo>();
|
|
164
|
+
private static readonly object _lock = new object();
|
|
165
|
+
|
|
166
|
+
public static bool IsTestModeEnabled =>
|
|
167
|
+
#if UNITY_EDITOR
|
|
168
|
+
SubscribablePropertyDebugWindow.IsWindowOpen &&
|
|
169
|
+
SubscribablePropertyDebugWindow.IsTestModeOn;
|
|
170
|
+
#else
|
|
171
|
+
false;
|
|
172
|
+
#endif
|
|
173
|
+
|
|
174
|
+
public static void RegisterProperty(object property, string propertyName, object owner)
|
|
175
|
+
{
|
|
176
|
+
if (!IsTestModeEnabled) return;
|
|
177
|
+
|
|
178
|
+
lock (_lock)
|
|
179
|
+
{
|
|
180
|
+
if (!_debugInfos.ContainsKey(property))
|
|
181
|
+
{
|
|
182
|
+
_debugInfos[property] = new PropertyDebugInfo(property, propertyName, owner);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public static void UnregisterProperty(object property)
|
|
188
|
+
{
|
|
189
|
+
lock (_lock)
|
|
190
|
+
{
|
|
191
|
+
_debugInfos.Remove(property);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public static void AddSubscriber(object property, object subscriber, string methodName)
|
|
196
|
+
{
|
|
197
|
+
if (!IsTestModeEnabled) return;
|
|
198
|
+
|
|
199
|
+
lock (_lock)
|
|
200
|
+
{
|
|
201
|
+
if (_debugInfos.TryGetValue(property, out var info))
|
|
202
|
+
{
|
|
203
|
+
info.AddSubscriber(subscriber, methodName);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
public static void RemoveSubscriber(object property, object subscriber)
|
|
209
|
+
{
|
|
210
|
+
lock (_lock)
|
|
211
|
+
{
|
|
212
|
+
if (_debugInfos.TryGetValue(property, out var info))
|
|
213
|
+
{
|
|
214
|
+
info.RemoveSubscriber(subscriber);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
public static IEnumerable<PropertyDebugInfo> GetAllPropertyInfos()
|
|
220
|
+
{
|
|
221
|
+
lock (_lock)
|
|
222
|
+
{
|
|
223
|
+
// 죽은 프로퍼티들 정리
|
|
224
|
+
var deadProperties = _debugInfos.Keys.Where(p => p == null || !_debugInfos[p].IsAlive).ToList();
|
|
225
|
+
foreach (var dead in deadProperties)
|
|
226
|
+
{
|
|
227
|
+
_debugInfos.Remove(dead);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return _debugInfos.Values.ToList();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
public static void Clear()
|
|
235
|
+
{
|
|
236
|
+
lock (_lock)
|
|
237
|
+
{
|
|
238
|
+
_debugInfos.Clear();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Owner 찾기 헬퍼 메서드
|
|
243
|
+
public static object FindOwner(object property)
|
|
244
|
+
{
|
|
245
|
+
if (property == null) return null;
|
|
246
|
+
|
|
247
|
+
try
|
|
248
|
+
{
|
|
249
|
+
// 스택 트레이스를 통해 호출자 찾기
|
|
250
|
+
var stackTrace = new StackTrace();
|
|
251
|
+
var frames = stackTrace.GetFrames();
|
|
252
|
+
|
|
253
|
+
if (frames != null)
|
|
254
|
+
{
|
|
255
|
+
foreach (var frame in frames.Skip(1).Take(10)) // 첫 번째 프레임은 현재 메서드
|
|
256
|
+
{
|
|
257
|
+
var method = frame.GetMethod();
|
|
258
|
+
if (method?.DeclaringType != null &&
|
|
259
|
+
!method.DeclaringType.IsSubclassOf(typeof(SubscribableProperty<>)) &&
|
|
260
|
+
method.DeclaringType != typeof(SubscribablePropertyDebugger))
|
|
261
|
+
{
|
|
262
|
+
// 생성자나 필드 초기화인 경우, 해당 타입의 인스턴스를 찾아야 함
|
|
263
|
+
if (method.IsConstructor || method.Name.Contains("ctor"))
|
|
264
|
+
{
|
|
265
|
+
return method.DeclaringType; // 타입 정보만 반환
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch { /* ignore */ }
|
|
272
|
+
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// SubscribableProperty에 디버그 기능 추가 (Partial)
|
|
279
|
+
namespace Kylin.SubscribableProperty
|
|
280
|
+
{
|
|
281
|
+
public partial class SubscribableProperty<T>
|
|
282
|
+
{
|
|
283
|
+
private PropertyDebugInfo _debugInfo;
|
|
284
|
+
private bool _debugInitialized = false;
|
|
285
|
+
|
|
286
|
+
partial void OnPropertyCreated()
|
|
287
|
+
{
|
|
288
|
+
InitializeDebugInfo();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
partial void OnSubscribe(Action<T> onNext)
|
|
292
|
+
{
|
|
293
|
+
#if UNITY_EDITOR
|
|
294
|
+
if (SubscribablePropertyDebugger.IsTestModeEnabled)
|
|
295
|
+
{
|
|
296
|
+
EnsureDebugInfo();
|
|
297
|
+
SubscribablePropertyDebugger.AddSubscriber(this, onNext.Target, onNext.Method.Name);
|
|
298
|
+
|
|
299
|
+
// 클로저 캡처 감지
|
|
300
|
+
if (ClosureProfiler.IsEnabled && onNext.Target != null)
|
|
301
|
+
{
|
|
302
|
+
var targetType = onNext.Target.GetType();
|
|
303
|
+
if (targetType.Name.Contains("DisplayClass") || targetType.Name.Contains("<>c"))
|
|
304
|
+
{
|
|
305
|
+
var location = $"{PropertyName ?? "Unknown"} in {OwnerTypeName ?? "Unknown"}";
|
|
306
|
+
ClosureProfiler.RecordCapture(onNext.Target, location);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
#endif
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
partial void OnUnsubscribe(Action<T> onNext)
|
|
314
|
+
{
|
|
315
|
+
#if UNITY_EDITOR
|
|
316
|
+
if (SubscribablePropertyDebugger.IsTestModeEnabled)
|
|
317
|
+
{
|
|
318
|
+
SubscribablePropertyDebugger.RemoveSubscriber(this, onNext.Target);
|
|
319
|
+
|
|
320
|
+
// 클로저 구독 해제 기록
|
|
321
|
+
if (ClosureProfiler.IsEnabled && onNext.Target != null)
|
|
322
|
+
{
|
|
323
|
+
ClosureProfiler.RecordUnsubscribe(onNext.Target);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
#endif
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private string PropertyName => _debugInfo?.PropertyName;
|
|
330
|
+
private string OwnerTypeName => _debugInfo?.OwnerTypeName;
|
|
331
|
+
|
|
332
|
+
private void InitializeDebugInfo()
|
|
333
|
+
{
|
|
334
|
+
if (!SubscribablePropertyDebugger.IsTestModeEnabled || _debugInitialized) return;
|
|
335
|
+
|
|
336
|
+
_debugInitialized = true;
|
|
337
|
+
|
|
338
|
+
// Owner와 프로퍼티 이름 찾기
|
|
339
|
+
var (owner, propertyName) = FindOwnerAndPropertyName();
|
|
340
|
+
|
|
341
|
+
SubscribablePropertyDebugger.RegisterProperty(this, propertyName, owner);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private void EnsureDebugInfo()
|
|
345
|
+
{
|
|
346
|
+
if (!_debugInitialized)
|
|
347
|
+
{
|
|
348
|
+
InitializeDebugInfo();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private (object owner, string propertyName) FindOwnerAndPropertyName()
|
|
353
|
+
{
|
|
354
|
+
try
|
|
355
|
+
{
|
|
356
|
+
var stackTrace = new StackTrace();
|
|
357
|
+
var frames = stackTrace.GetFrames();
|
|
358
|
+
|
|
359
|
+
if (frames != null)
|
|
360
|
+
{
|
|
361
|
+
foreach (var frame in frames.Skip(1).Take(15))
|
|
362
|
+
{
|
|
363
|
+
var method = frame.GetMethod();
|
|
364
|
+
if (method?.DeclaringType != null &&
|
|
365
|
+
method.DeclaringType != typeof(SubscribableProperty<T>) &&
|
|
366
|
+
method.DeclaringType != typeof(SubscribablePropertyDebugger) &&
|
|
367
|
+
!method.DeclaringType.FullName.Contains("SubscribableProperty"))
|
|
368
|
+
{
|
|
369
|
+
var declaringType = method.DeclaringType;
|
|
370
|
+
|
|
371
|
+
// 생성자인 경우 - 필드에서 생성된 것으로 추정
|
|
372
|
+
if (method.IsConstructor || method.Name.Contains("ctor"))
|
|
373
|
+
{
|
|
374
|
+
var propertyName = FindPropertyNameInType(declaringType);
|
|
375
|
+
return (declaringType, propertyName);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// 일반 메서드인 경우 - 인스턴스를 찾을 수 없으므로 타입만
|
|
379
|
+
if (!method.IsStatic)
|
|
380
|
+
{
|
|
381
|
+
var propertyName = FindPropertyNameInType(declaringType);
|
|
382
|
+
return (declaringType, propertyName);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
catch { /* ignore */ }
|
|
389
|
+
|
|
390
|
+
return (null, $"Unknown_{GetHashCode()}");
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private string FindPropertyNameInType(Type type)
|
|
394
|
+
{
|
|
395
|
+
try
|
|
396
|
+
{
|
|
397
|
+
// 타입의 SubscribableProperty 필드들 찾기
|
|
398
|
+
var fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
|
399
|
+
var spFields = fields.Where(f => f.FieldType.IsGenericType &&
|
|
400
|
+
f.FieldType.GetGenericTypeDefinition() == typeof(SubscribableProperty<>))
|
|
401
|
+
.ToList();
|
|
402
|
+
|
|
403
|
+
if (spFields.Count == 1)
|
|
404
|
+
{
|
|
405
|
+
return CleanFieldName(spFields[0].Name);
|
|
406
|
+
}
|
|
407
|
+
else if (spFields.Count > 1)
|
|
408
|
+
{
|
|
409
|
+
// 여러 개인 경우, 타입을 기준으로 추정
|
|
410
|
+
var matchingField = spFields.FirstOrDefault(f => f.FieldType == typeof(SubscribableProperty<T>));
|
|
411
|
+
if (matchingField != null)
|
|
412
|
+
{
|
|
413
|
+
return CleanFieldName(matchingField.Name);
|
|
414
|
+
}
|
|
415
|
+
return $"Property_{typeof(T).Name}_{GetHashCode()}";
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch { /* ignore */ }
|
|
419
|
+
|
|
420
|
+
return $"Property_{typeof(T).Name}";
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private string CleanFieldName(string fieldName)
|
|
424
|
+
{
|
|
425
|
+
// _progressSP -> Progress, m_statusProperty -> Status 등
|
|
426
|
+
return fieldName.TrimStart('_', 'm')
|
|
427
|
+
.Replace("SP", "")
|
|
428
|
+
.Replace("Property", "")
|
|
429
|
+
.Replace("Field", "");
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
#if UNITY_EDITOR
|
|
435
|
+
namespace Kylin.SubscribableProperty
|
|
436
|
+
{
|
|
437
|
+
// 디버그 윈도우
|
|
438
|
+
public class SubscribablePropertyDebugWindow : EditorWindow
|
|
439
|
+
{
|
|
440
|
+
private static SubscribablePropertyDebugWindow _instance;
|
|
441
|
+
private static bool _isTestModeOn = false;
|
|
442
|
+
|
|
443
|
+
private Vector2 _scrollPosition;
|
|
444
|
+
private readonly Dictionary<PropertyDebugInfo, bool> _foldoutStates = new Dictionary<PropertyDebugInfo, bool>();
|
|
445
|
+
private string _searchFilter = "";
|
|
446
|
+
private bool _showOnlyWithSubscribers = true;
|
|
447
|
+
private bool _autoRefresh = true;
|
|
448
|
+
private double _lastRefreshTime;
|
|
449
|
+
|
|
450
|
+
public static bool IsWindowOpen => _instance != null;
|
|
451
|
+
public static bool IsTestModeOn => _isTestModeOn;
|
|
452
|
+
|
|
453
|
+
[MenuItem("Tools/Subscribable Property Debugger")]
|
|
454
|
+
public static void ShowWindow()
|
|
455
|
+
{
|
|
456
|
+
_instance = GetWindow<SubscribablePropertyDebugWindow>("구독 프로퍼티 디버거");
|
|
457
|
+
_instance.Show();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
private void OnEnable()
|
|
461
|
+
{
|
|
462
|
+
_instance = this;
|
|
463
|
+
EditorApplication.update += OnEditorUpdate;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
private void OnDisable()
|
|
467
|
+
{
|
|
468
|
+
_instance = null;
|
|
469
|
+
EditorApplication.update -= OnEditorUpdate;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
private void OnEditorUpdate()
|
|
473
|
+
{
|
|
474
|
+
if (_autoRefresh && EditorApplication.timeSinceStartup - _lastRefreshTime > 1.0)
|
|
475
|
+
{
|
|
476
|
+
_lastRefreshTime = EditorApplication.timeSinceStartup;
|
|
477
|
+
Repaint();
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
private void OnGUI()
|
|
482
|
+
{
|
|
483
|
+
DrawHeader();
|
|
484
|
+
DrawFilters();
|
|
485
|
+
DrawPropertyList();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
private void DrawHeader()
|
|
489
|
+
{
|
|
490
|
+
EditorGUILayout.BeginVertical("Box");
|
|
491
|
+
|
|
492
|
+
EditorGUILayout.LabelField("구독 프로퍼티 디버그 도구", EditorStyles.boldLabel);
|
|
493
|
+
|
|
494
|
+
EditorGUI.BeginDisabledGroup(Application.isPlaying);
|
|
495
|
+
bool newTestMode = EditorGUILayout.Toggle("테스트 모드 활성화", _isTestModeOn);
|
|
496
|
+
if (newTestMode != _isTestModeOn)
|
|
497
|
+
{
|
|
498
|
+
_isTestModeOn = newTestMode;
|
|
499
|
+
if (!_isTestModeOn)
|
|
500
|
+
{
|
|
501
|
+
SubscribablePropertyDebugger.Clear();
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
EditorGUI.EndDisabledGroup();
|
|
505
|
+
|
|
506
|
+
_autoRefresh = EditorGUILayout.Toggle("자동 새로고침", _autoRefresh);
|
|
507
|
+
|
|
508
|
+
if (Application.isPlaying && _isTestModeOn)
|
|
509
|
+
{
|
|
510
|
+
EditorGUILayout.HelpBox("테스트 모드가 활성화되었습니다. 구독 활동을 모니터링 중입니다.", MessageType.Info);
|
|
511
|
+
}
|
|
512
|
+
else if (Application.isPlaying)
|
|
513
|
+
{
|
|
514
|
+
EditorGUILayout.HelpBox("플레이 모드 중에는 테스트 모드가 비활성화됩니다. 플레이 모드 진입 전에 활성화하세요.", MessageType.Warning);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
EditorGUILayout.EndVertical();
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
private void DrawFilters()
|
|
521
|
+
{
|
|
522
|
+
EditorGUILayout.BeginVertical("Box");
|
|
523
|
+
EditorGUILayout.LabelField("필터", EditorStyles.boldLabel);
|
|
524
|
+
|
|
525
|
+
_searchFilter = EditorGUILayout.TextField("검색", _searchFilter);
|
|
526
|
+
_showOnlyWithSubscribers = EditorGUILayout.Toggle("구독자가 있는 항목만 표시", _showOnlyWithSubscribers);
|
|
527
|
+
|
|
528
|
+
EditorGUILayout.BeginHorizontal();
|
|
529
|
+
if (GUILayout.Button("새로고침"))
|
|
530
|
+
{
|
|
531
|
+
Repaint();
|
|
532
|
+
}
|
|
533
|
+
if (GUILayout.Button("모두 지우기"))
|
|
534
|
+
{
|
|
535
|
+
SubscribablePropertyDebugger.Clear();
|
|
536
|
+
_foldoutStates.Clear();
|
|
537
|
+
}
|
|
538
|
+
if (GUILayout.Button("강제 GC"))
|
|
539
|
+
{
|
|
540
|
+
System.GC.Collect();
|
|
541
|
+
System.GC.WaitForPendingFinalizers();
|
|
542
|
+
System.GC.Collect();
|
|
543
|
+
}
|
|
544
|
+
EditorGUILayout.EndHorizontal();
|
|
545
|
+
|
|
546
|
+
EditorGUILayout.EndVertical();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
private void DrawPropertyList()
|
|
550
|
+
{
|
|
551
|
+
if (!_isTestModeOn)
|
|
552
|
+
{
|
|
553
|
+
EditorGUILayout.HelpBox("모니터링을 시작하려면 테스트 모드를 활성화하세요.", MessageType.Info);
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
var properties = SubscribablePropertyDebugger.GetAllPropertyInfos()
|
|
558
|
+
.Where(p => p.IsAlive)
|
|
559
|
+
.Where(p => !_showOnlyWithSubscribers || p.SubscriberCount > 0)
|
|
560
|
+
.Where(p => string.IsNullOrEmpty(_searchFilter) ||
|
|
561
|
+
p.PropertyName.ToLower().Contains(_searchFilter.ToLower()) ||
|
|
562
|
+
p.OwnerTypeName.ToLower().Contains(_searchFilter.ToLower()))
|
|
563
|
+
.OrderByDescending(p => p.SubscriberCount)
|
|
564
|
+
.ThenBy(p => p.OwnerTypeName)
|
|
565
|
+
.ThenBy(p => p.PropertyName)
|
|
566
|
+
.ToList();
|
|
567
|
+
|
|
568
|
+
var totalSubscribers = properties.Sum(p => p.SubscriberCount);
|
|
569
|
+
EditorGUILayout.LabelField($"프로퍼티: {properties.Count} | 전체 구독자: {totalSubscribers}", EditorStyles.boldLabel);
|
|
570
|
+
|
|
571
|
+
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
|
|
572
|
+
|
|
573
|
+
foreach (var propertyInfo in properties)
|
|
574
|
+
{
|
|
575
|
+
DrawPropertyInfo(propertyInfo);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
EditorGUILayout.EndScrollView();
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
private void DrawPropertyInfo(PropertyDebugInfo propertyInfo)
|
|
582
|
+
{
|
|
583
|
+
if (!_foldoutStates.ContainsKey(propertyInfo))
|
|
584
|
+
_foldoutStates[propertyInfo] = false;
|
|
585
|
+
|
|
586
|
+
EditorGUILayout.BeginVertical("Box");
|
|
587
|
+
|
|
588
|
+
// 헤더
|
|
589
|
+
EditorGUILayout.BeginHorizontal();
|
|
590
|
+
|
|
591
|
+
var subscriberCount = propertyInfo.SubscriberCount;
|
|
592
|
+
var headerText = $"[{subscriberCount}] {propertyInfo.OwnerTypeName}.{propertyInfo.PropertyName}";
|
|
593
|
+
|
|
594
|
+
// 구독자 수에 따른 색상
|
|
595
|
+
var prevColor = GUI.color;
|
|
596
|
+
if (subscriberCount > 5)
|
|
597
|
+
GUI.color = Color.red;
|
|
598
|
+
else if (subscriberCount > 2)
|
|
599
|
+
GUI.color = Color.yellow;
|
|
600
|
+
else if (subscriberCount > 0)
|
|
601
|
+
GUI.color = Color.green;
|
|
602
|
+
|
|
603
|
+
_foldoutStates[propertyInfo] = EditorGUILayout.Foldout(_foldoutStates[propertyInfo], headerText, true);
|
|
604
|
+
|
|
605
|
+
GUI.color = prevColor;
|
|
606
|
+
|
|
607
|
+
EditorGUILayout.LabelField($"({propertyInfo.PropertyType.Name})", EditorStyles.miniLabel, GUILayout.Width(100));
|
|
608
|
+
|
|
609
|
+
EditorGUILayout.EndHorizontal();
|
|
610
|
+
|
|
611
|
+
// 상세 정보
|
|
612
|
+
if (_foldoutStates[propertyInfo])
|
|
613
|
+
{
|
|
614
|
+
EditorGUI.indentLevel++;
|
|
615
|
+
|
|
616
|
+
EditorGUILayout.LabelField("소유자:", propertyInfo.OwnerInstanceName);
|
|
617
|
+
EditorGUILayout.LabelField("생성 시간:", propertyInfo.CreatedTime.ToString("HH:mm:ss"));
|
|
618
|
+
|
|
619
|
+
if (subscriberCount > 0)
|
|
620
|
+
{
|
|
621
|
+
EditorGUILayout.LabelField("구독자:", EditorStyles.boldLabel);
|
|
622
|
+
EditorGUI.indentLevel++;
|
|
623
|
+
|
|
624
|
+
foreach (var subscriber in propertyInfo.GetAliveSubscribers())
|
|
625
|
+
{
|
|
626
|
+
DrawSubscriberInfo(subscriber);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
EditorGUI.indentLevel--;
|
|
630
|
+
}
|
|
631
|
+
else
|
|
632
|
+
{
|
|
633
|
+
EditorGUILayout.LabelField("활성 구독자 없음", EditorStyles.miniLabel);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
EditorGUI.indentLevel--;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
EditorGUILayout.EndVertical();
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
private void DrawSubscriberInfo(SPSubscriberInfo subscriberInfo)
|
|
643
|
+
{
|
|
644
|
+
EditorGUILayout.BeginVertical("Box");
|
|
645
|
+
|
|
646
|
+
EditorGUILayout.LabelField($"• {subscriberInfo.SubscriberInstanceName}.{subscriberInfo.MethodName}", EditorStyles.label);
|
|
647
|
+
EditorGUILayout.LabelField($" 구독 시간: {subscriberInfo.SubscribeTime:HH:mm:ss}", EditorStyles.miniLabel);
|
|
648
|
+
|
|
649
|
+
if (!string.IsNullOrEmpty(subscriberInfo.SubscribeCallStack))
|
|
650
|
+
{
|
|
651
|
+
if (GUILayout.Button("호출 스택 표시", EditorStyles.miniButton))
|
|
652
|
+
{
|
|
653
|
+
Debug.Log($"{subscriberInfo.SubscriberInstanceName}의 구독 호출 스택:\n{subscriberInfo.SubscribeCallStack}");
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
EditorGUILayout.EndVertical();
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
#endif
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Kylin.DI",
|
|
3
|
+
"rootNamespace": "Kylin",
|
|
4
|
+
"references": [],
|
|
5
|
+
"includePlatforms": [],
|
|
6
|
+
"excludePlatforms": [],
|
|
7
|
+
"allowUnsafeCode": false,
|
|
8
|
+
"overrideReferences": false,
|
|
9
|
+
"precompiledReferences": [],
|
|
10
|
+
"autoReferenced": true,
|
|
11
|
+
"defineConstraints": [],
|
|
12
|
+
"versionDefines": [],
|
|
13
|
+
"noEngineReferences": false
|
|
14
|
+
}
|