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.
Files changed (71) hide show
  1. package/.github/workflows/publish.yml +22 -0
  2. package/CHANGELOG.md +17 -0
  3. package/CLAUDE.md +58 -0
  4. package/LICENSE +21 -0
  5. package/README.md +704 -0
  6. package/Runtime/Attributes/InjectAttribute.cs +11 -0
  7. package/Runtime/Attributes/InjectAttribute.cs.meta +2 -0
  8. package/Runtime/Attributes/ViewModelAttribute.cs +17 -0
  9. package/Runtime/Attributes/ViewModelAttribute.cs.meta +2 -0
  10. package/Runtime/Attributes.meta +8 -0
  11. package/Runtime/Core/DIBehaviour.cs +70 -0
  12. package/Runtime/Core/DIBehaviour.cs.meta +2 -0
  13. package/Runtime/Core/LifetimeScope.cs +264 -0
  14. package/Runtime/Core/LifetimeScope.cs.meta +2 -0
  15. package/Runtime/Core.meta +8 -0
  16. package/Runtime/DI/DependencyBuilder.cs +114 -0
  17. package/Runtime/DI/DependencyBuilder.cs.meta +2 -0
  18. package/Runtime/DI/DependencyInjector.cs +104 -0
  19. package/Runtime/DI/DependencyInjector.cs.meta +2 -0
  20. package/Runtime/DI/InstanceFactory.cs +36 -0
  21. package/Runtime/DI/InstanceFactory.cs.meta +2 -0
  22. package/Runtime/DI/KDI.cs +54 -0
  23. package/Runtime/DI/KDI.cs.meta +2 -0
  24. package/Runtime/DI/Registration.cs +29 -0
  25. package/Runtime/DI/Registration.cs.meta +2 -0
  26. package/Runtime/DI/Scope.cs +183 -0
  27. package/Runtime/DI/Scope.cs.meta +2 -0
  28. package/Runtime/DI/ScopeBuilder.cs +68 -0
  29. package/Runtime/DI/ScopeBuilder.cs.meta +2 -0
  30. package/Runtime/DI/ScopeExtensions.cs +70 -0
  31. package/Runtime/DI/ScopeExtensions.cs.meta +2 -0
  32. package/Runtime/DI.meta +8 -0
  33. package/Runtime/Debug/ClosureAnalyzer.cs +377 -0
  34. package/Runtime/Debug/ClosureAnalyzer.cs.meta +2 -0
  35. package/Runtime/Debug/ClosureProfilerWindow.cs +435 -0
  36. package/Runtime/Debug/ClosureProfilerWindow.cs.meta +2 -0
  37. package/Runtime/Debug/SubscriberInfo.cs +661 -0
  38. package/Runtime/Debug/SubscriberInfo.cs.meta +3 -0
  39. package/Runtime/Debug.meta +3 -0
  40. package/Runtime/Kylin.DI.asmdef +14 -0
  41. package/Runtime/Kylin.DI.asmdef.meta +7 -0
  42. package/Runtime/SubscribableProperty/Reaction.cs +61 -0
  43. package/Runtime/SubscribableProperty/Reaction.cs.meta +2 -0
  44. package/Runtime/SubscribableProperty/SubscribableCollection.cs +325 -0
  45. package/Runtime/SubscribableProperty/SubscribableCollection.cs.meta +3 -0
  46. package/Runtime/SubscribableProperty/SubscribableCollectionExtensions.cs +24 -0
  47. package/Runtime/SubscribableProperty/SubscribableCollectionExtensions.cs.meta +3 -0
  48. package/Runtime/SubscribableProperty/SubscribableCommand.cs +52 -0
  49. package/Runtime/SubscribableProperty/SubscribableCommand.cs.meta +2 -0
  50. package/Runtime/SubscribableProperty/SubscribableDictionary.cs +350 -0
  51. package/Runtime/SubscribableProperty/SubscribableDictionary.cs.meta +3 -0
  52. package/Runtime/SubscribableProperty/SubscribableProperty.cs +119 -0
  53. package/Runtime/SubscribableProperty/SubscribableProperty.cs.meta +2 -0
  54. package/Runtime/SubscribableProperty/SubscribablePropertyExtensions.cs +39 -0
  55. package/Runtime/SubscribableProperty/SubscribablePropertyExtensions.cs.meta +3 -0
  56. package/Runtime/SubscribableProperty/SubscribablePropertyLinq.cs +86 -0
  57. package/Runtime/SubscribableProperty/SubscribablePropertyLinq.cs.meta +2 -0
  58. package/Runtime/SubscribableProperty.meta +8 -0
  59. package/Runtime/Update/IFixedUpdatable.cs +16 -0
  60. package/Runtime/Update/IFixedUpdatable.cs.meta +2 -0
  61. package/Runtime/Update/ILateUpdatable.cs +16 -0
  62. package/Runtime/Update/ILateUpdatable.cs.meta +2 -0
  63. package/Runtime/Update/IUpdatable.cs +16 -0
  64. package/Runtime/Update/IUpdatable.cs.meta +2 -0
  65. package/Runtime/Update/IUpdatePriority.cs +15 -0
  66. package/Runtime/Update/IUpdatePriority.cs.meta +2 -0
  67. package/Runtime/Update/UpdateLoopManager.cs +240 -0
  68. package/Runtime/Update/UpdateLoopManager.cs.meta +2 -0
  69. package/Runtime/Update.meta +8 -0
  70. package/Runtime.meta +8 -0
  71. package/package.json +19 -0
@@ -0,0 +1,61 @@
1
+ using System.Collections.Generic;
2
+
3
+ namespace Kylin.SubscribableProperty
4
+ {
5
+ /// <summary>
6
+ /// Subscribable프로퍼티의 구독자 invoke 를 한 트랜잭션 끝 시점으로 미루기 위한 단위
7
+ /// </summary>
8
+ public static class Reaction
9
+ {
10
+ private static int _depth;
11
+
12
+ private static readonly List<ISubscribablePending> _pending = new(16);
13
+
14
+ private static readonly List<ISubscribablePending> _flushBuffer = new(16);
15
+
16
+ public static bool IsActive => _depth > 0;
17
+
18
+ public static Handle Begin()
19
+ {
20
+ _depth++;
21
+ return default;
22
+ }
23
+
24
+ /// 동일 인스턴스가 같은 트랜잭션 안에서 여러 번 등록되어도 1회만
25
+ internal static void RegisterPending(ISubscribablePending sp)
26
+ {
27
+ if (sp == null) return;
28
+ if (_depth == 0) return;
29
+ if (!_pending.Contains(sp))
30
+ _pending.Add(sp);
31
+ }
32
+
33
+ internal static void End()
34
+ {
35
+ _depth--;
36
+ if (_depth > 0) return;
37
+
38
+ if (_pending.Count == 0) return;
39
+
40
+ _flushBuffer.Clear();
41
+ for (int i = 0; i < _pending.Count; i++)
42
+ _flushBuffer.Add(_pending[i]);
43
+ _pending.Clear();
44
+
45
+ for (int i = 0; i < _flushBuffer.Count; i++)
46
+ _flushBuffer[i].FlushPendingNotification();
47
+
48
+ _flushBuffer.Clear();
49
+ }
50
+
51
+ public readonly ref struct Handle
52
+ {
53
+ public void Dispose() => Reaction.End();
54
+ }
55
+ }
56
+
57
+ internal interface ISubscribablePending
58
+ {
59
+ void FlushPendingNotification();
60
+ }
61
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: fe9027cebb424c549d3e5d8d8804aff0
@@ -0,0 +1,325 @@
1
+ using System;
2
+ using System.Collections;
3
+ using System.Collections.Generic;
4
+ using UnityEngine;
5
+
6
+ namespace Kylin.SubscribableProperty
7
+ {
8
+ public enum CollectionChangeType
9
+ {
10
+ Add,
11
+ Remove,
12
+ Replace,
13
+ Move,
14
+ Clear,
15
+ Initialize,
16
+ }
17
+
18
+ [Serializable]
19
+ public struct CollectionChangeEvent<T>
20
+ {
21
+ public CollectionChangeType Type { get; } // 변경 유형
22
+ public int Index { get; } // 변경이 발생한 인덱스
23
+ public T OldValue { get; } // 제거되거나 교체되서 삭제된 값
24
+ public T NewValue { get; } // 추가되거나 교체되서 추가된 값
25
+ public int OldIndex { get; } // Move 시 이전 인덱스
26
+ public int NewIndex { get; } // Move 시 새로운 인덱스
27
+
28
+ private CollectionChangeEvent(CollectionChangeType type, int index, T oldValue, T newValue, int oldIndex = -1, int newIndex = -1)
29
+ {
30
+ Type = type;
31
+ Index = index;
32
+ OldValue = oldValue;
33
+ NewValue = newValue;
34
+ OldIndex = oldIndex;
35
+ NewIndex = newIndex;
36
+ }
37
+
38
+ public static CollectionChangeEvent<T> Initialize(int index, T value)
39
+ => new CollectionChangeEvent<T>(CollectionChangeType.Initialize, index, default, value);
40
+ public static CollectionChangeEvent<T> Add(int index, T value)
41
+ => new CollectionChangeEvent<T>(CollectionChangeType.Add, index, default, value);
42
+
43
+ public static CollectionChangeEvent<T> Remove(int index, T value)
44
+ => new CollectionChangeEvent<T>(CollectionChangeType.Remove, index, value, default);
45
+
46
+ public static CollectionChangeEvent<T> Replace(int index, T oldValue, T newValue)
47
+ => new CollectionChangeEvent<T>(CollectionChangeType.Replace, index, oldValue, newValue);
48
+
49
+ public static CollectionChangeEvent<T> Move(T value, int oldIndex, int newIndex)
50
+ => new CollectionChangeEvent<T>(CollectionChangeType.Move, newIndex, value, value, oldIndex, newIndex);
51
+
52
+ public static CollectionChangeEvent<T> Clear()
53
+ => new CollectionChangeEvent<T>(CollectionChangeType.Clear, -1, default, default);
54
+ }
55
+ public interface IReadOnlySubscribableCollection<T> : IReadOnlyCollection<T>
56
+ {
57
+ public int IndexOf(T item);
58
+ public bool Contains(T item);
59
+ public void CopyTo(T[] array, int arrayIndex);
60
+ public T Find(Predicate<T> match);
61
+ public bool Exists(Predicate<T> match);
62
+ public IEnumerator<T> GetEnumerator();
63
+
64
+ public int Count { get; }
65
+
66
+ public bool IsReadOnly{ get; }
67
+
68
+ T this[int index] { get; }
69
+ /// <summary>
70
+ /// invo
71
+ /// </summary>
72
+ /// <param name="onChanged"> 각 타입별 지정을 해줄것 </param>
73
+ /// <param name="invokeForExisting"> true로 지정하면 지금 존재하는 collection을 foreach문으로 돌아가면서 실행 (Initialize타입)</param>
74
+ /// <returns></returns>
75
+ IDisposable Subscribe(Action<CollectionChangeEvent<T>> onChanged, bool invokeForExisting = false);
76
+
77
+ IDisposable SubscribeCount(Action<int> onCountChanged, bool invokeInitial = false);
78
+ IDisposable SubscribeAdd(Action<int, T> onAdd);
79
+ }
80
+
81
+ public interface ISubscribableCollection<T> : IReadOnlySubscribableCollection<T>, ICollection<T>
82
+ {
83
+ new T this[int index] { get; set; }
84
+ void Insert(int index, T item);
85
+ void RemoveAt(int index);
86
+ void Move(int oldIndex, int newIndex);
87
+
88
+ }
89
+ [Serializable]
90
+ public class SubscribableCollection<T> : ISubscribableCollection<T>, ISerializationCallbackReceiver, ISubscribablePending
91
+ {
92
+ [SerializeField]
93
+ private List<T> _items = new List<T>();
94
+
95
+ private event Action<CollectionChangeEvent<T>> _collectionChanged;
96
+ private event Action<int> _countChanged;
97
+
98
+ // 트랜잭션 모드 pending 상태. 변경 이벤트는 순서를 보존해주기 위해 리스트 사용.
99
+ [NonSerialized] private List<CollectionChangeEvent<T>> _pendingChanges;
100
+ [NonSerialized] private bool _hasPendingCountChange;
101
+
102
+ public SubscribableCollection()
103
+ {
104
+ }
105
+
106
+ public SubscribableCollection(IEnumerable<T> collection)
107
+ {
108
+ if (collection != null)
109
+ {
110
+ _items.AddRange(collection);
111
+ }
112
+ }
113
+
114
+ public SubscribableCollection(int capacity)
115
+ {
116
+ _items = new List<T>(capacity);
117
+ }
118
+
119
+ public T this[int index]
120
+ {
121
+ get => _items[index];
122
+ set
123
+ {
124
+ var oldValue = _items[index];
125
+ if (!EqualityComparer<T>.Default.Equals(oldValue, value))
126
+ {
127
+ _items[index] = value;
128
+ NotifyCollectionChanged(CollectionChangeEvent<T>.Replace(index, oldValue, value));
129
+ }
130
+ }
131
+ }
132
+
133
+ public int Count => _items.Count;
134
+ public bool IsReadOnly => false;
135
+
136
+ public void Add(T item)
137
+ {
138
+ var index = _items.Count;
139
+ _items.Add(item);
140
+ NotifyCollectionChanged(CollectionChangeEvent<T>.Add(index, item));
141
+ NotifyCountChanged();
142
+ }
143
+
144
+ public void Insert(int index, T item)
145
+ {
146
+ _items.Insert(index, item);
147
+ NotifyCollectionChanged(CollectionChangeEvent<T>.Add(index, item));
148
+ NotifyCountChanged();
149
+ }
150
+
151
+ public bool Remove(T item)
152
+ {
153
+ var index = _items.IndexOf(item);
154
+ if (index >= 0)
155
+ {
156
+ RemoveAt(index);
157
+ return true;
158
+ }
159
+ return false;
160
+ }
161
+
162
+ public void RemoveAt(int index)
163
+ {
164
+ var item = _items[index];
165
+ _items.RemoveAt(index);
166
+ NotifyCollectionChanged(CollectionChangeEvent<T>.Remove(index, item));
167
+ NotifyCountChanged();
168
+ }
169
+
170
+ public void Clear()
171
+ {
172
+ if (_items.Count > 0)
173
+ {
174
+ _items.Clear();
175
+ NotifyCollectionChanged(CollectionChangeEvent<T>.Clear());
176
+ NotifyCountChanged();
177
+ }
178
+ }
179
+
180
+ public int IndexOf(T item) => _items.IndexOf(item);
181
+ public bool Contains(T item) => _items.Contains(item);
182
+
183
+ public T Find(Predicate<T> match) => _items.Find(match);
184
+ public bool Exists(Predicate<T> match) => _items.Exists(match);
185
+ public void CopyTo(T[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex);
186
+ public IEnumerator<T> GetEnumerator() => _items.GetEnumerator();
187
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
188
+
189
+
190
+ public void AddRange(IEnumerable<T> collection)
191
+ {
192
+ if (collection == null) return;
193
+
194
+ foreach (var item in collection)
195
+ {
196
+ Add(item); // 각각 개별 이벤트 발생
197
+ }
198
+ }
199
+
200
+ public void Move(int oldIndex, int newIndex)
201
+ {
202
+ if (oldIndex == newIndex) return;
203
+
204
+ var item = _items[oldIndex];
205
+ _items.RemoveAt(oldIndex);
206
+ _items.Insert(newIndex, item);
207
+ NotifyCollectionChanged(CollectionChangeEvent<T>.Move(item, oldIndex, newIndex));
208
+ }
209
+
210
+ public List<T> ToList() => new List<T>(_items);
211
+ public T[] ToArray() => _items.ToArray();
212
+
213
+
214
+ public IDisposable Subscribe(Action<CollectionChangeEvent<T>> onChanged, bool invokeForCurrentItems = false)
215
+ {
216
+ if (onChanged == null) throw new ArgumentNullException(nameof(onChanged));
217
+
218
+ if (invokeForCurrentItems)
219
+ {
220
+ for (int i = 0; i < _items.Count; i++)
221
+ {
222
+ onChanged(CollectionChangeEvent<T>.Initialize(i, _items[i]));
223
+ }
224
+ }
225
+
226
+ _collectionChanged += onChanged;
227
+ return new Disposable(() => _collectionChanged -= onChanged);
228
+ }
229
+
230
+ public IDisposable SubscribeCount(Action<int> onCountChanged, bool invokeInitial = false)
231
+ {
232
+ if (onCountChanged == null) throw new ArgumentNullException(nameof(onCountChanged));
233
+
234
+ if (invokeInitial)
235
+ onCountChanged(Count);
236
+
237
+ _countChanged += onCountChanged;
238
+ return new Disposable(() => _countChanged -= onCountChanged);
239
+ }
240
+
241
+ public IDisposable SubscribeAdd(Action<int, T> onAdd)
242
+ {
243
+ return Subscribe(change =>
244
+ {
245
+ if (change.Type == CollectionChangeType.Add || change.Type == CollectionChangeType.Initialize)
246
+ onAdd(change.Index, change.NewValue);
247
+ });
248
+ }
249
+
250
+ public IDisposable SubscribeRemove(Action<int, T> onRemove)
251
+ {
252
+ return Subscribe(change =>
253
+ {
254
+ if (change.Type == CollectionChangeType.Remove)
255
+ onRemove(change.Index, change.OldValue);
256
+ });
257
+ }
258
+
259
+
260
+ private void NotifyCollectionChanged(CollectionChangeEvent<T> changeEvent)
261
+ {
262
+ if (Reaction.IsActive)
263
+ {
264
+ _pendingChanges ??= new List<CollectionChangeEvent<T>>();
265
+ _pendingChanges.Add(changeEvent);
266
+ Reaction.RegisterPending(this);
267
+ }
268
+ else
269
+ {
270
+ _collectionChanged?.Invoke(changeEvent);
271
+ }
272
+ }
273
+
274
+ private void NotifyCountChanged()
275
+ {
276
+ if (Reaction.IsActive)
277
+ {
278
+ _hasPendingCountChange = true;
279
+ Reaction.RegisterPending(this);
280
+ }
281
+ else
282
+ {
283
+ _countChanged?.Invoke(Count);
284
+ }
285
+ }
286
+
287
+ void ISubscribablePending.FlushPendingNotification()
288
+ {
289
+ // 변경 이벤트: 트랜잭션 내 발생 순서대로 invoke (Add/Remove 시퀀스 의미 보존).
290
+ // List 는 영구 보유 + Clear() 재사용 -- 매 트랜잭션 alloc 0.
291
+ if (_pendingChanges != null && _pendingChanges.Count > 0)
292
+ {
293
+ int count = _pendingChanges.Count;
294
+ for (int i = 0; i < count; i++)
295
+ {
296
+ _collectionChanged?.Invoke(_pendingChanges[i]);
297
+ }
298
+ _pendingChanges.Clear();
299
+ }
300
+
301
+ // Count 변경: 트랜잭션 내 다중 발생을 합쳐 *최종 Count* 로 1회만 invoke.
302
+ if (_hasPendingCountChange)
303
+ {
304
+ _hasPendingCountChange = false;
305
+ _countChanged?.Invoke(Count);
306
+ }
307
+ }
308
+
309
+ void ISerializationCallbackReceiver.OnBeforeSerialize() { }
310
+
311
+ void ISerializationCallbackReceiver.OnAfterDeserialize()
312
+ {
313
+ // 역직렬화 후 이벤트 재설정 (필요시)
314
+ }
315
+
316
+
317
+ private class Disposable : IDisposable
318
+ {
319
+ private readonly Action _onDispose;
320
+ public Disposable(Action onDispose) => _onDispose = onDispose;
321
+ public void Dispose() => _onDispose?.Invoke();
322
+ }
323
+ }
324
+
325
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 36392a1533654219a8a1968fea2bd8c9
3
+ timeCreated: 1755591349
@@ -0,0 +1,24 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+
4
+ namespace Kylin.SubscribableProperty
5
+ {
6
+ public static class SubscribableCollectionExtensions
7
+ {
8
+ // /// <summary>
9
+ // /// 컬렉션의 변경 사항을 다른 CompositeDisposable에 추가합니다.
10
+ // /// </summary>
11
+ // public static IDisposable AddTo<T>(this IReadOnlySubscribableCollection<T> collection,
12
+ // CompositeDisposable compositeDisposable,
13
+ // Action<CollectionChangeEvent<T>> onChanged,
14
+ // bool invokeForExisting = false)
15
+ // {
16
+ // if (collection == null || compositeDisposable == null || onChanged == null)
17
+ // return null;
18
+ //
19
+ // var subscription = collection.Subscribe(onChanged, invokeForExisting);
20
+ // compositeDisposable.Add(subscription);
21
+ // return subscription;
22
+ // }
23
+ }
24
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: be8948c18f4c4f27acc77a77f40c8646
3
+ timeCreated: 1756885186
@@ -0,0 +1,52 @@
1
+ using System;
2
+
3
+ namespace Kylin.SubscribableProperty
4
+ {
5
+ public interface ISubscribableCommand : IDisposable
6
+ {
7
+ IReadOnlySubscribableProperty<bool> CanExecute { get; }
8
+ void Execute();
9
+ }
10
+
11
+ public class SubscribableCommand : ISubscribableCommand
12
+ {
13
+ private readonly Action _execute;
14
+ private readonly CompositeDisposable _disposables = new CompositeDisposable();
15
+ private bool _disposed;
16
+
17
+ public IReadOnlySubscribableProperty<bool> CanExecute { get; }
18
+
19
+ public SubscribableCommand(IReadOnlySubscribableProperty<bool> canExecute, Action execute)
20
+ {
21
+ CanExecute = canExecute ?? throw new ArgumentNullException(nameof(canExecute));
22
+ _execute = execute ?? throw new ArgumentNullException(nameof(execute));
23
+ }
24
+
25
+ public SubscribableCommand(Action execute)
26
+ {
27
+ _execute = execute ?? throw new ArgumentNullException(nameof(execute));
28
+ CanExecute = new SubscribableProperty<bool>(true);
29
+ }
30
+
31
+ public void Execute()
32
+ {
33
+ if (_disposed)
34
+ {
35
+ return;
36
+ }
37
+
38
+ if (CanExecute.Value)
39
+ {
40
+ _execute();
41
+ }
42
+ }
43
+
44
+ public void Dispose()
45
+ {
46
+ if (_disposed) return;
47
+ _disposed = true;
48
+
49
+ _disposables.Dispose();
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 4f2916b18f5ac764d90eeff635c7874c