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,350 @@
1
+ using System;
2
+ using System.Collections;
3
+ using System.Collections.Generic;
4
+ using System.Linq;
5
+ using UnityEngine;
6
+
7
+ namespace Kylin.SubscribableProperty
8
+ {
9
+ [Serializable]
10
+ public struct DictionaryChangeEvent<TKey, TValue>
11
+ {
12
+ public CollectionChangeType Type { get; }
13
+ public TKey Key { get; }
14
+ public TValue OldValue { get; }
15
+ public TValue NewValue { get; }
16
+
17
+ private DictionaryChangeEvent(CollectionChangeType type, TKey key, TValue oldValue, TValue newValue)
18
+ {
19
+ Type = type;
20
+ Key = key;
21
+ OldValue = oldValue;
22
+ NewValue = newValue;
23
+ }
24
+
25
+ public static DictionaryChangeEvent<TKey, TValue> Add(TKey key, TValue value)
26
+ => new DictionaryChangeEvent<TKey, TValue>(CollectionChangeType.Add, key, default, value);
27
+
28
+ public static DictionaryChangeEvent<TKey, TValue> Remove(TKey key, TValue value)
29
+ => new DictionaryChangeEvent<TKey, TValue>(CollectionChangeType.Remove, key, value, default);
30
+
31
+ public static DictionaryChangeEvent<TKey, TValue> Replace(TKey key, TValue oldValue, TValue newValue)
32
+ => new DictionaryChangeEvent<TKey, TValue>(CollectionChangeType.Replace, key, oldValue, newValue);
33
+
34
+ public static DictionaryChangeEvent<TKey, TValue> Clear()
35
+ => new DictionaryChangeEvent<TKey, TValue>(CollectionChangeType.Clear, default, default, default);
36
+ }
37
+ public interface IReadOnlySubscribableDictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>
38
+ {
39
+ // 구독 메서드들
40
+ IDisposable Subscribe(Action<DictionaryChangeEvent<TKey, TValue>> onChanged, bool invokeForCurrentItems = false);
41
+ IDisposable SubscribeCount(Action<int> onCountChanged, bool invokeInitial = false);
42
+ IDisposable SubscribeAdd(Action<TKey, TValue> onAdd);
43
+ IDisposable SubscribeRemove(Action<TKey, TValue> onRemove);
44
+ IDisposable SubscribeReplace(Action<TKey, TValue, TValue> onReplace);
45
+ }
46
+ public interface ISubscribableDictionary<TKey, TValue> : IReadOnlySubscribableDictionary<TKey, TValue>, IDictionary<TKey, TValue>
47
+ {
48
+ }
49
+ [Serializable]
50
+ public class SubscribableDictionary<TKey, TValue> : ISubscribableDictionary<TKey, TValue>, ISerializationCallbackReceiver, ISubscribablePending
51
+ {
52
+ [SerializeField]
53
+ private List<TKey> _keys = new List<TKey>();
54
+
55
+ [SerializeField]
56
+ private List<TValue> _values = new List<TValue>();
57
+
58
+ private Dictionary<TKey, TValue> _dictionary = new Dictionary<TKey, TValue>();
59
+
60
+ private event Action<DictionaryChangeEvent<TKey, TValue>> _dictionaryChanged;
61
+ private event Action<int> _countChanged;
62
+
63
+ [NonSerialized] private List<DictionaryChangeEvent<TKey, TValue>> _pendingChanges;
64
+ [NonSerialized] private bool _hasPendingCountChange;
65
+
66
+ public SubscribableDictionary()
67
+ {
68
+ }
69
+
70
+ public SubscribableDictionary(IDictionary<TKey, TValue> dictionary)
71
+ {
72
+ if (dictionary != null)
73
+ {
74
+ foreach (var kvp in dictionary)
75
+ {
76
+ _dictionary[kvp.Key] = kvp.Value;
77
+ }
78
+ FullSyncToSerializedLists();
79
+ }
80
+ }
81
+
82
+ public SubscribableDictionary(int capacity)
83
+ {
84
+ _dictionary = new Dictionary<TKey, TValue>(capacity);
85
+ }
86
+
87
+ #region IDictionary<TKey, TValue> Implementation
88
+
89
+ public TValue this[TKey key]
90
+ {
91
+ get => _dictionary[key];
92
+ set
93
+ {
94
+ if (_dictionary.TryGetValue(key, out var oldValue))
95
+ {
96
+ if (!EqualityComparer<TValue>.Default.Equals(oldValue, value))
97
+ {
98
+ _dictionary[key] = value;
99
+ UpdateSerializedValue(key, value);
100
+ NotifyDictionaryChanged(DictionaryChangeEvent<TKey, TValue>.Replace(key, oldValue, value));
101
+ }
102
+ }
103
+ else
104
+ {
105
+ Add(key, value);
106
+ }
107
+ }
108
+ }
109
+
110
+ public ICollection<TKey> Keys => _dictionary.Keys;
111
+ public ICollection<TValue> Values => _dictionary.Values;
112
+ public int Count => _dictionary.Count;
113
+ public bool IsReadOnly => false;
114
+
115
+ IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys;
116
+ IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;
117
+
118
+ public void Add(TKey key, TValue value)
119
+ {
120
+ _dictionary.Add(key, value);
121
+ AddToSerializedLists(key, value);
122
+ NotifyDictionaryChanged(DictionaryChangeEvent<TKey, TValue>.Add(key, value));
123
+ NotifyCountChanged();
124
+ }
125
+
126
+ public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
127
+
128
+ public bool Remove(TKey key)
129
+ {
130
+ if (_dictionary.TryGetValue(key, out var value))
131
+ {
132
+ _dictionary.Remove(key);
133
+ RemoveFromSerializedLists(key);
134
+ NotifyDictionaryChanged(DictionaryChangeEvent<TKey, TValue>.Remove(key, value));
135
+ NotifyCountChanged();
136
+ return true;
137
+ }
138
+ return false;
139
+ }
140
+
141
+ public bool Remove(KeyValuePair<TKey, TValue> item)
142
+ {
143
+ if (_dictionary.TryGetValue(item.Key, out var value) &&
144
+ EqualityComparer<TValue>.Default.Equals(value, item.Value))
145
+ {
146
+ return Remove(item.Key);
147
+ }
148
+ return false;
149
+ }
150
+
151
+ public void Clear()
152
+ {
153
+ if (_dictionary.Count > 0)
154
+ {
155
+ _dictionary.Clear();
156
+ ClearSerializedLists();
157
+ NotifyDictionaryChanged(DictionaryChangeEvent<TKey, TValue>.Clear());
158
+ NotifyCountChanged();
159
+ }
160
+ }
161
+
162
+ public bool ContainsKey(TKey key) => _dictionary.ContainsKey(key);
163
+ public bool Contains(KeyValuePair<TKey, TValue> item) => _dictionary.Contains(item);
164
+ public bool TryGetValue(TKey key, out TValue value) => _dictionary.TryGetValue(key, out value);
165
+
166
+ public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
167
+ {
168
+ ((IDictionary<TKey, TValue>)_dictionary).CopyTo(array, arrayIndex);
169
+ }
170
+
171
+ public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => _dictionary.GetEnumerator();
172
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
173
+
174
+ #endregion
175
+
176
+ #region Subscription Methods
177
+
178
+ public IDisposable Subscribe(Action<DictionaryChangeEvent<TKey, TValue>> onChanged, bool invokeForCurrentItems = false)
179
+ {
180
+ if (onChanged == null) throw new ArgumentNullException(nameof(onChanged));
181
+
182
+ if (invokeForCurrentItems)
183
+ {
184
+ foreach (var kvp in _dictionary)
185
+ {
186
+ onChanged(DictionaryChangeEvent<TKey, TValue>.Add(kvp.Key, kvp.Value));
187
+ }
188
+ }
189
+
190
+ _dictionaryChanged += onChanged;
191
+ return new Disposable(() => _dictionaryChanged -= onChanged);
192
+ }
193
+
194
+ public IDisposable SubscribeCount(Action<int> onCountChanged, bool invokeInitial = false)
195
+ {
196
+ if (onCountChanged == null) throw new ArgumentNullException(nameof(onCountChanged));
197
+
198
+ if (invokeInitial)
199
+ onCountChanged(Count);
200
+
201
+ _countChanged += onCountChanged;
202
+ return new Disposable(() => _countChanged -= onCountChanged);
203
+ }
204
+
205
+ public IDisposable SubscribeAdd(Action<TKey, TValue> onAdd)
206
+ {
207
+ return Subscribe(change =>
208
+ {
209
+ if (change.Type == CollectionChangeType.Add)
210
+ onAdd(change.Key, change.NewValue);
211
+ });
212
+ }
213
+
214
+ public IDisposable SubscribeRemove(Action<TKey, TValue> onRemove)
215
+ {
216
+ return Subscribe(change =>
217
+ {
218
+ if (change.Type == CollectionChangeType.Remove)
219
+ onRemove(change.Key, change.OldValue);
220
+ });
221
+ }
222
+ public IDisposable SubscribeReplace(Action<TKey, TValue, TValue> onReplace)
223
+ {
224
+ return Subscribe(change =>
225
+ {
226
+ if (change.Type == CollectionChangeType.Replace)
227
+ onReplace(change.Key, change.OldValue, change.NewValue);
228
+ });
229
+ }
230
+ #endregion
231
+
232
+ private void NotifyDictionaryChanged(DictionaryChangeEvent<TKey, TValue> changeEvent)
233
+ {
234
+ if (Reaction.IsActive)
235
+ {
236
+ _pendingChanges ??= new List<DictionaryChangeEvent<TKey, TValue>>();
237
+ _pendingChanges.Add(changeEvent);
238
+ Reaction.RegisterPending(this);
239
+ }
240
+ else
241
+ {
242
+ _dictionaryChanged?.Invoke(changeEvent);
243
+ }
244
+ }
245
+
246
+ private void NotifyCountChanged()
247
+ {
248
+ if (Reaction.IsActive)
249
+ {
250
+ _hasPendingCountChange = true;
251
+ Reaction.RegisterPending(this);
252
+ }
253
+ else
254
+ {
255
+ _countChanged?.Invoke(Count);
256
+ }
257
+ }
258
+
259
+ void ISubscribablePending.FlushPendingNotification()
260
+ {
261
+ if (_pendingChanges != null && _pendingChanges.Count > 0)
262
+ {
263
+ int count = _pendingChanges.Count;
264
+ for (int i = 0; i < count; i++)
265
+ {
266
+ _dictionaryChanged?.Invoke(_pendingChanges[i]);
267
+ }
268
+ _pendingChanges.Clear();
269
+ }
270
+
271
+ if (_hasPendingCountChange)
272
+ {
273
+ _hasPendingCountChange = false;
274
+ _countChanged?.Invoke(Count);
275
+ }
276
+ }
277
+
278
+ #region Optimized Serialization Support
279
+
280
+ private void AddToSerializedLists(TKey key, TValue value)
281
+ {
282
+ _keys.Add(key);
283
+ _values.Add(value);
284
+ }
285
+
286
+ private void UpdateSerializedValue(TKey key, TValue newValue)
287
+ {
288
+ var index = _keys.IndexOf(key);
289
+ if (index >= 0)
290
+ {
291
+ _values[index] = newValue;
292
+ }
293
+ else
294
+ {
295
+ AddToSerializedLists(key, newValue);
296
+ }
297
+ }
298
+
299
+ private void RemoveFromSerializedLists(TKey key)
300
+ {
301
+ var index = _keys.IndexOf(key);
302
+ if (index >= 0)
303
+ {
304
+ _keys.RemoveAt(index);
305
+ _values.RemoveAt(index);
306
+ }
307
+ }
308
+
309
+ private void ClearSerializedLists()
310
+ {
311
+ _keys.Clear();
312
+ _values.Clear();
313
+ }
314
+
315
+ private void FullSyncToSerializedLists()
316
+ {
317
+ _keys.Clear();
318
+ _values.Clear();
319
+
320
+ foreach (var kvp in _dictionary)
321
+ {
322
+ _keys.Add(kvp.Key);
323
+ _values.Add(kvp.Value);
324
+ }
325
+ }
326
+
327
+ public void OnBeforeSerialize()
328
+ {
329
+ }
330
+
331
+ void ISerializationCallbackReceiver.OnAfterDeserialize()
332
+ {
333
+ _dictionary.Clear();
334
+
335
+ for (int i = 0; i < _keys.Count && i < _values.Count; i++)
336
+ {
337
+ _dictionary[_keys[i]] = _values[i];
338
+ }
339
+ }
340
+
341
+ #endregion
342
+
343
+ private class Disposable : IDisposable
344
+ {
345
+ private readonly Action _onDispose;
346
+ public Disposable(Action onDispose) => _onDispose = onDispose;
347
+ public void Dispose() => _onDispose?.Invoke();
348
+ }
349
+ }
350
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 3cea4bebd8c84c8696eb0874aefbad0a
3
+ timeCreated: 1755591584
@@ -0,0 +1,119 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using UnityEngine;
4
+
5
+ namespace Kylin.SubscribableProperty
6
+ {
7
+ public interface IReadOnlySubscribableProperty<T>
8
+ {
9
+ T Value { get; }
10
+ IDisposable Subscribe(Action<T> onNext, bool invokeInitial = false);
11
+ }
12
+
13
+ public interface ISubscribableProperty<T> : IReadOnlySubscribableProperty<T>
14
+ {
15
+ new T Value { get; set; }
16
+ }
17
+ [Serializable]
18
+ public partial class SubscribableProperty<T> : ISubscribableProperty<T>, ISerializationCallbackReceiver, ISubscribablePending
19
+ {
20
+ [SerializeField]
21
+ private T value;
22
+
23
+ // Reaction 안에서 변경되는지 표시.
24
+ // 같은 스코프 내 다중 변경이 발생해도 outermost Dispose 시 최종 값으로 1회만 invoke
25
+ [NonSerialized]
26
+ private bool _hasPendingNotification;
27
+
28
+ public SubscribableProperty()
29
+ {
30
+ OnPropertyCreated();
31
+ }
32
+
33
+ public SubscribableProperty(T initValue)
34
+ {
35
+ value = initValue;
36
+ OnPropertyCreated();
37
+ }
38
+
39
+ public event Action<T> ValueChanged;
40
+
41
+ public T Value
42
+ {
43
+ get => value;
44
+ set
45
+ {
46
+ if (!EqualityComparer<T>.Default.Equals(this.value, value))
47
+ {
48
+ this.value = value;
49
+ if (Reaction.IsActive)
50
+ {
51
+ _hasPendingNotification = true;
52
+ Reaction.RegisterPending(this);
53
+ }
54
+ else
55
+ {
56
+ ValueChanged?.Invoke(this.value);
57
+ }
58
+ }
59
+ }
60
+ }
61
+
62
+ void ISubscribablePending.FlushPendingNotification()
63
+ {
64
+ if (!_hasPendingNotification) return;
65
+ _hasPendingNotification = false;
66
+ ValueChanged?.Invoke(value);
67
+ }
68
+
69
+ public IDisposable Subscribe(Action<T> onNext, bool invokeInitial = false)
70
+ {
71
+ if (onNext == null) throw new ArgumentNullException(nameof(onNext));
72
+
73
+ if (invokeInitial)
74
+ onNext(Value);
75
+
76
+ ValueChanged += onNext;
77
+ OnSubscribe(onNext);
78
+
79
+ return new Disposable(() =>
80
+ {
81
+ ValueChanged -= onNext;
82
+ OnUnsubscribe(onNext);
83
+ });
84
+ }
85
+
86
+ void ISerializationCallbackReceiver.OnBeforeSerialize() { }
87
+ void ISerializationCallbackReceiver.OnAfterDeserialize()
88
+ => ValueChanged?.Invoke(value);
89
+
90
+ partial void OnPropertyCreated();
91
+ partial void OnSubscribe(Action<T> onNext);
92
+ partial void OnUnsubscribe(Action<T> onNext);
93
+
94
+ private class Disposable : IDisposable
95
+ {
96
+ private readonly Action _onDispose;
97
+ public Disposable(Action onDispose) => _onDispose = onDispose;
98
+ public void Dispose() => _onDispose();
99
+ }
100
+ }
101
+
102
+ public class ReadOnlySubscribableProperty<T> : IReadOnlySubscribableProperty<T>
103
+ {
104
+ private readonly ISubscribableProperty<T> _source;
105
+
106
+ public ReadOnlySubscribableProperty(ISubscribableProperty<T> source)
107
+ {
108
+ _source = source ?? throw new ArgumentNullException(nameof(source));
109
+ }
110
+
111
+ public T Value => _source.Value;
112
+
113
+ public IDisposable Subscribe(Action<T> onNext, bool invokeInitial = false)
114
+ {
115
+ return _source.Subscribe(onNext, invokeInitial);
116
+ }
117
+ }
118
+
119
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: f504ac0a2c572854b836b95512758cee
@@ -0,0 +1,39 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Runtime.CompilerServices;
4
+ using UnityEngine;
5
+
6
+ namespace Kylin.SubscribableProperty
7
+ {
8
+ public static class SubscribablePropertyExtensions
9
+ {
10
+ public static T AddTo<T>(this T disposable, CompositeDisposable cd) where T : IDisposable
11
+ {
12
+ if (disposable == null || cd == null)
13
+ return disposable;
14
+ cd.Add(disposable);
15
+ return disposable;
16
+ }
17
+ }
18
+
19
+ public class CompositeDisposable : IDisposable
20
+ {
21
+ private readonly List<IDisposable> _disposables = new();
22
+ public bool IsDisposed { get; private set; }
23
+ public void Add(IDisposable d) => _disposables.Add(d);
24
+ public void Dispose()
25
+ {
26
+ if (IsDisposed) return;
27
+ foreach (var d in _disposables) d.Dispose();
28
+ _disposables.Clear();
29
+ IsDisposed = true;
30
+ }
31
+
32
+ public void Clear()
33
+ {
34
+ if (IsDisposed) return;
35
+ foreach (var d in _disposables.ToArray()) d.Dispose();
36
+ _disposables.Clear();
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 6d803b42e5424c46af66949d75ebb29f
3
+ timeCreated: 1755326510
@@ -0,0 +1,86 @@
1
+ using System;
2
+
3
+ namespace Kylin.SubscribableProperty
4
+ {
5
+ public static class SubscribablePropertyLinq
6
+ {
7
+ public static IReadOnlySubscribableProperty<TResult> Select<T, TResult>(this IReadOnlySubscribableProperty<T> source, Func<T, TResult> selector)
8
+ {
9
+ return new SelectSubscribableProperty<T, TResult>(source, selector);
10
+ }
11
+
12
+ public static IReadOnlySubscribableProperty<T> Where<T>(this IReadOnlySubscribableProperty<T> source, Func<T, bool> predicate)
13
+ {
14
+ return new WhereSubscribableProperty<T>(source, predicate);
15
+ }
16
+ }
17
+
18
+ internal class SelectSubscribableProperty<TSource, TResult> : IReadOnlySubscribableProperty<TResult>, IDisposable
19
+ {
20
+ private readonly IReadOnlySubscribableProperty<TSource> _source;
21
+ private readonly Func<TSource, TResult> _selector;
22
+ private readonly SubscribableProperty<TResult> _property;
23
+ private readonly IDisposable _subscription;
24
+
25
+ public TResult Value => _property.Value;
26
+
27
+ public SelectSubscribableProperty(IReadOnlySubscribableProperty<TSource> source, Func<TSource, TResult> selector)
28
+ {
29
+ _source = source;
30
+ _selector = selector;
31
+ _property = new SubscribableProperty<TResult>(_selector(_source.Value));
32
+ _subscription = _source.Subscribe(OnSourceValueChanged, invokeInitial: false);
33
+ }
34
+
35
+ private void OnSourceValueChanged(TSource value)
36
+ {
37
+ _property.Value = _selector(value);
38
+ }
39
+
40
+ public IDisposable Subscribe(Action<TResult> onNext, bool invokeInitial = false)
41
+ {
42
+ return _property.Subscribe(onNext, invokeInitial);
43
+ }
44
+
45
+ public void Dispose()
46
+ {
47
+ _subscription?.Dispose();
48
+ }
49
+ }
50
+
51
+ internal class WhereSubscribableProperty<T> : IReadOnlySubscribableProperty<T>, IDisposable
52
+ {
53
+ private readonly IReadOnlySubscribableProperty<T> _source;
54
+ private readonly Func<T, bool> _predicate;
55
+ private readonly SubscribableProperty<T> _property;
56
+ private readonly IDisposable _subscription;
57
+
58
+ public T Value => _property.Value;
59
+
60
+ public WhereSubscribableProperty(IReadOnlySubscribableProperty<T> source, Func<T, bool> predicate)
61
+ {
62
+ _source = source;
63
+ _predicate = predicate;
64
+ _property = new SubscribableProperty<T>(source.Value);
65
+ _subscription = _source.Subscribe(OnSourceValueChanged, invokeInitial: true);
66
+ }
67
+
68
+ private void OnSourceValueChanged(T value)
69
+ {
70
+ if (_predicate(value))
71
+ {
72
+ _property.Value = value;
73
+ }
74
+ }
75
+
76
+ public IDisposable Subscribe(Action<T> onNext, bool invokeInitial = false)
77
+ {
78
+ return _property.Subscribe(onNext, invokeInitial);
79
+ }
80
+
81
+ public void Dispose()
82
+ {
83
+ _subscription?.Dispose();
84
+ }
85
+ }
86
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 26289af3cb5e6de45a424314e45f6fe6
@@ -0,0 +1,8 @@
1
+ fileFormatVersion: 2
2
+ guid: 8a72ff9c421ce624da7e5804eb8cddfa
3
+ folderAsset: yes
4
+ DefaultImporter:
5
+ externalObjects: {}
6
+ userData:
7
+ assetBundleName:
8
+ assetBundleVariant:
@@ -0,0 +1,16 @@
1
+ namespace Kylin.DI
2
+ {
3
+ /// <summary>
4
+ /// DI 기반 FixedUpdate 루프 인터페이스
5
+ /// - MonoBehaviour.FixedUpdate() 대신 사용
6
+ /// - UpdateLoopManager에서 고정 시간 간격으로 호출
7
+ /// </summary>
8
+ public interface IFixedUpdatable
9
+ {
10
+ /// <summary>
11
+ /// 고정 시간 간격으로 호출 (FixedUpdate와 동일한 타이밍)
12
+ /// </summary>
13
+ /// <param name="fixedDeltaTime">Time.fixedDeltaTime</param>
14
+ void KDIFixedUpdate(float fixedDeltaTime);
15
+ }
16
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 71d494350be8be747b69c543a7a73c64
@@ -0,0 +1,16 @@
1
+ namespace Kylin.DI
2
+ {
3
+ /// <summary>
4
+ /// DI 기반 LateUpdate 루프 인터페이스
5
+ /// - MonoBehaviour.LateUpdate() 대신 사용
6
+ /// - UpdateLoopManager에서 Update 이후 호출
7
+ /// </summary>
8
+ public interface ILateUpdatable
9
+ {
10
+ /// <summary>
11
+ /// Update 이후 호출 (LateUpdate와 동일한 타이밍)
12
+ /// </summary>
13
+ /// <param name="deltaTime">Time.deltaTime</param>
14
+ void KDILateUpdate(float deltaTime);
15
+ }
16
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: bf7236655a9df214cacbe5129a0ae621
@@ -0,0 +1,16 @@
1
+ namespace Kylin.DI
2
+ {
3
+ /// <summary>
4
+ /// DI 기반 Update 루프 인터페이스
5
+ /// - MonoBehaviour.Update() 대신 사용
6
+ /// - UpdateLoopManager에서 매 프레임 호출
7
+ /// </summary>
8
+ public interface IUpdatable
9
+ {
10
+ /// <summary>
11
+ /// 매 프레임 호출 (Update와 동일한 타이밍)
12
+ /// </summary>
13
+ /// <param name="deltaTime">Time.deltaTime</param>
14
+ void KDIUpdate(float deltaTime);
15
+ }
16
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 4fae7fa43ac3e8345a88cfba8acf1380