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,54 @@
1
+ using UnityEngine;
2
+
3
+ namespace Kylin.DI
4
+ {
5
+ /// <summary>
6
+ /// KDI (Kylin Dependency Injection) - Static Facade
7
+ ///
8
+ /// 사용 패턴:
9
+ /// 1. 서비스 등록: LifetimeScope 상속 클래스에서 Configure(ScopeBuilder builder)에서 등록
10
+ /// 2. 서비스 해결: this.Inject(scope) 또는 scope.Resolve() 사용
11
+ /// 3. 자동 주입 (권장): [Inject] 어트리뷰트 + DIBehaviour
12
+ /// </summary>
13
+ public static class KDI
14
+ {
15
+ private static IScope _rootScope;
16
+
17
+ /// <summary>
18
+ /// RootScope 접근점.
19
+ /// parent가 없는 LifetimeScope가 Initialize()할 때 자동 설정됨.
20
+ /// </summary>
21
+ public static IScope RootScope
22
+ {
23
+ get
24
+ {
25
+ if (_rootScope == null)
26
+ {
27
+ Debug.LogWarning("[KDI] RootScope가 설정되지 않았습니다. 빈 RootScope를 자동 생성합니다.");
28
+ _rootScope = new ScopeBuilder().Build(parent: null);
29
+ }
30
+ return _rootScope;
31
+ }
32
+ }
33
+
34
+ /// <summary>
35
+ /// RootScope 설정. parent가 없는 LifetimeScope에서 호출.
36
+ /// </summary>
37
+ internal static void SetRootScope(IScope scope)
38
+ {
39
+ if (_rootScope != null)
40
+ {
41
+ Debug.LogWarning("[KDI] RootScope가 이미 설정되어 있습니다. 기존 RootScope를 교체합니다.");
42
+ }
43
+ _rootScope = scope;
44
+ }
45
+
46
+ [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
47
+ private static void Reset()
48
+ {
49
+ _rootScope?.Dispose();
50
+ _rootScope = null;
51
+ DependencyInjector.ClearCache();
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: b5eea526850baa745923747c4f1bed17
@@ -0,0 +1,29 @@
1
+ using System;
2
+
3
+ namespace Kylin.DI
4
+ {
5
+ public enum Lifetime
6
+ {
7
+ Transient, // 매번 새 인스턴스 생성
8
+ Singleton, // 단일 인스턴스 유지 (RootScope에서만)
9
+ Scoped // 해당 Scope 내 단일 인스턴스
10
+ }
11
+
12
+ public interface IDependencyObject { }
13
+
14
+ public interface IInjectable { }
15
+
16
+ public interface IPostInjectable
17
+ {
18
+ void PostInject();
19
+ }
20
+
21
+ public class Registration
22
+ {
23
+ public Type ServiceType { get; set; }
24
+ public Type ImplementationType { get; set; }
25
+ public object Instance { get; set; }
26
+ public Lifetime Lifetime { get; set; }
27
+ public Func<IScope, object> Factory { get; set; }
28
+ }
29
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 767481a9a1a5db543b78c3f6296cbcb7
@@ -0,0 +1,183 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using UnityEngine;
4
+
5
+ namespace Kylin.DI
6
+ {
7
+ public interface IScope : IDisposable
8
+ {
9
+ IScope Parent { get; }
10
+ T Resolve<T>() where T : class;
11
+ object Resolve(Type type);
12
+ }
13
+
14
+ public class Scope : IScope
15
+ {
16
+ [ThreadStatic]
17
+ private static HashSet<Type> _resolvingTypes;
18
+
19
+ private readonly Dictionary<Type, Registration> _registrations;
20
+ private readonly Dictionary<Type, object> _instances = new();
21
+ private readonly IScope _parent;
22
+ private readonly List<IScope> _children = new();
23
+ private readonly object _lock = new();
24
+ private bool _isDisposed;
25
+
26
+ public IScope Parent => _parent;
27
+
28
+ internal Scope(Dictionary<Type, Registration> registrations, IScope parent)
29
+ {
30
+ _registrations = registrations;
31
+ _parent = parent;
32
+
33
+ if (parent is Scope parentScope)
34
+ {
35
+ lock (parentScope._lock)
36
+ {
37
+ parentScope._children.Add(this);
38
+ }
39
+ }
40
+ }
41
+
42
+ public T Resolve<T>() where T : class
43
+ {
44
+ return (T)Resolve(typeof(T));
45
+ }
46
+
47
+ public object Resolve(Type type)
48
+ {
49
+ if (_isDisposed) throw new ObjectDisposedException(nameof(Scope));
50
+
51
+ _resolvingTypes ??= new HashSet<Type>();
52
+
53
+ if (!_resolvingTypes.Add(type))
54
+ {
55
+ var chain = string.Join(" → ", _resolvingTypes);
56
+ _resolvingTypes.Clear();
57
+ throw new InvalidOperationException($"[KDI] 순환참조 발생: {chain} → {type.Name}");
58
+ }
59
+
60
+ try
61
+ {
62
+ return ResolveInternal(type);
63
+ }
64
+ finally
65
+ {
66
+ _resolvingTypes.Remove(type);
67
+ }
68
+ }
69
+
70
+ private object ResolveInternal(Type type)
71
+ {
72
+ lock (_lock)
73
+ {
74
+ if (_instances.TryGetValue(type, out var cached))
75
+ return cached;
76
+ }
77
+
78
+ if (_registrations.TryGetValue(type, out var reg))
79
+ {
80
+ if (reg.Instance != null)
81
+ {
82
+ lock (_lock)
83
+ {
84
+ _instances[type] = reg.Instance;
85
+ }
86
+ return reg.Instance;
87
+ }
88
+
89
+ var instance = CreateInstance(reg);
90
+
91
+ if (reg.Lifetime == Lifetime.Scoped || reg.Lifetime == Lifetime.Singleton)
92
+ {
93
+ lock (_lock)
94
+ {
95
+ _instances[type] = instance;
96
+ }
97
+ }
98
+
99
+ return instance;
100
+ }
101
+
102
+ if (_parent != null)
103
+ return _parent.Resolve(type);
104
+
105
+ throw new InvalidOperationException($"[KDI] {type.Name}에 대한 등록을 찾을 수 없습니다.");
106
+ }
107
+
108
+ private object CreateInstance(Registration registration)
109
+ {
110
+ object instance;
111
+
112
+ if (registration.Factory != null)
113
+ {
114
+ instance = registration.Factory(this);
115
+ }
116
+ else
117
+ {
118
+ instance = InstanceFactory.Create(registration.ImplementationType);
119
+ }
120
+
121
+ if (instance is IInjectable injectable)
122
+ {
123
+ DependencyInjector.Inject(injectable, this);
124
+ }
125
+ else
126
+ {
127
+ DependencyInjector.WarnIfHasInjectFieldsWithoutIInjectable(instance);
128
+ }
129
+
130
+ if (instance is IUpdatable ||
131
+ instance is IFixedUpdatable ||
132
+ instance is ILateUpdatable)
133
+ {
134
+ UpdateLoopManager.Instance.Register(instance);
135
+ }
136
+
137
+ return instance;
138
+ }
139
+
140
+ public void Dispose()
141
+ {
142
+ if (_isDisposed) return;
143
+ _isDisposed = true;
144
+
145
+ if (_parent is Scope parentScope)
146
+ {
147
+ lock (parentScope._lock)
148
+ {
149
+ parentScope._children.Remove(this);
150
+ }
151
+ }
152
+
153
+ List<IScope> childrenSnapshot;
154
+ lock (_lock)
155
+ {
156
+ childrenSnapshot = new List<IScope>(_children);
157
+ _children.Clear();
158
+ }
159
+
160
+ foreach (var child in childrenSnapshot)
161
+ {
162
+ child.Dispose();
163
+ }
164
+
165
+ foreach (var instance in _instances.Values)
166
+ {
167
+ if (instance is IDisposable disposable)
168
+ {
169
+ try { disposable.Dispose(); }
170
+ catch { /* ignore */ }
171
+ }
172
+
173
+ if (instance is IUpdatable ||
174
+ instance is IFixedUpdatable ||
175
+ instance is ILateUpdatable)
176
+ {
177
+ UpdateLoopManager.Instance?.Unregister(instance);
178
+ }
179
+ }
180
+ _instances.Clear();
181
+ }
182
+ }
183
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: bb116a8d0de85884185f557d59585f3f
@@ -0,0 +1,68 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+
4
+ namespace Kylin.DI
5
+ {
6
+ public class ScopeBuilder
7
+ {
8
+ private readonly Dictionary<Type, Registration> _registrations = new();
9
+ private bool _isBuilt;
10
+
11
+ public DependencyBuilder<T> Bind<T>() where T : class
12
+ {
13
+ if (_isBuilt) throw new InvalidOperationException("[KDI] 이미 빌드된 ScopeBuilder에 등록할 수 없습니다.");
14
+ return new DependencyBuilder<T>(this, typeof(T));
15
+ }
16
+
17
+ public void RegisterFactory<T>(Func<IScope, T> factory, Lifetime lifetime) where T : class
18
+ {
19
+ if (_isBuilt) throw new InvalidOperationException("[KDI] 이미 빌드된 ScopeBuilder에 등록할 수 없습니다.");
20
+
21
+ var serviceType = typeof(T);
22
+ _registrations[serviceType] = new Registration
23
+ {
24
+ ServiceType = serviceType,
25
+ Factory = scope => factory(scope),
26
+ Lifetime = lifetime
27
+ };
28
+ }
29
+
30
+ public void RegisterInstance<T>(T instance) where T : class
31
+ {
32
+ if (_isBuilt) throw new InvalidOperationException("[KDI] 이미 빌드된 ScopeBuilder에 등록할 수 없습니다.");
33
+
34
+ var serviceType = typeof(T);
35
+ _registrations[serviceType] = new Registration
36
+ {
37
+ ServiceType = serviceType,
38
+ Instance = instance,
39
+ Lifetime = Lifetime.Scoped
40
+ };
41
+ }
42
+
43
+ internal void AddRegistration(Registration registration)
44
+ {
45
+ if (_isBuilt) throw new InvalidOperationException("[KDI] 이미 빌드된 ScopeBuilder에 등록할 수 없습니다.");
46
+ _registrations[registration.ServiceType] = registration;
47
+ }
48
+
49
+ public IScope Build(IScope parent = null)
50
+ {
51
+ if (_isBuilt) throw new InvalidOperationException("[KDI] ScopeBuilder는 한 번만 Build할 수 있습니다.");
52
+
53
+ // child scope에서 Singleton 등록 검증
54
+ if (parent != null)
55
+ {
56
+ foreach (var reg in _registrations.Values)
57
+ {
58
+ if (reg.Lifetime == Lifetime.Singleton)
59
+ throw new InvalidOperationException(
60
+ $"[KDI] Singleton은 RootScope에서만 등록 가능합니다: {reg.ServiceType.Name}");
61
+ }
62
+ }
63
+
64
+ _isBuilt = true;
65
+ return new Scope(_registrations, parent);
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 46fb707d55feb5d48a9d87df0ff76a6a
@@ -0,0 +1,70 @@
1
+ using UnityEngine;
2
+
3
+ namespace Kylin.DI
4
+ {
5
+ /// <summary>
6
+ /// IScope 확장 메서드.
7
+ /// 동적 생성 시 DI 주입을 지원한다.
8
+ /// </summary>
9
+ public static class ScopeExtensions
10
+ {
11
+ /// <summary>
12
+ /// GameObject의 하위 IInjectable MonoBehaviour에 주입.
13
+ /// 동적 생성 후 호출하거나, scope.Instantiate()가 내부적으로 호출.
14
+ /// </summary>
15
+ public static void InjectGameObject(this IScope scope, GameObject gameObject)
16
+ {
17
+ if (scope == null || gameObject == null) return;
18
+
19
+ var injectables = gameObject.GetComponentsInChildren<IInjectable>(true);
20
+ foreach (var injectable in injectables)
21
+ {
22
+ DependencyInjector.Inject(injectable, scope);
23
+
24
+ if (injectable is DIBehaviour dib)
25
+ dib.SetInjected(scope);
26
+ }
27
+ }
28
+
29
+ /// <summary>
30
+ /// 프리팹 인스턴스화 + 하위 IInjectable 자동 주입.
31
+ /// Object.Instantiate 대신 사용.
32
+ /// </summary>
33
+ public static GameObject Instantiate(this IScope scope, GameObject prefab)
34
+ {
35
+ var instance = Object.Instantiate(prefab);
36
+ scope.InjectGameObject(instance);
37
+ return instance;
38
+ }
39
+
40
+ /// <summary>
41
+ /// 프리팹 인스턴스화 (부모 지정) + 하위 IInjectable 자동 주입.
42
+ /// </summary>
43
+ public static GameObject Instantiate(this IScope scope, GameObject prefab, Transform parent)
44
+ {
45
+ var instance = Object.Instantiate(prefab, parent);
46
+ scope.InjectGameObject(instance);
47
+ return instance;
48
+ }
49
+
50
+ /// <summary>
51
+ /// 프리팹 인스턴스화 (위치/회전 지정) + 하위 IInjectable 자동 주입.
52
+ /// </summary>
53
+ public static GameObject Instantiate(this IScope scope, GameObject prefab, Vector3 position, Quaternion rotation)
54
+ {
55
+ var instance = Object.Instantiate(prefab, position, rotation);
56
+ scope.InjectGameObject(instance);
57
+ return instance;
58
+ }
59
+
60
+ /// <summary>
61
+ /// 프리팹 인스턴스화 (부모 + 위치/회전 지정) + 하위 IInjectable 자동 주입.
62
+ /// </summary>
63
+ public static GameObject Instantiate(this IScope scope, GameObject prefab, Vector3 position, Quaternion rotation, Transform parent)
64
+ {
65
+ var instance = Object.Instantiate(prefab, position, rotation, parent);
66
+ scope.InjectGameObject(instance);
67
+ return instance;
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 1654fc549fddb37409031d27a8a5cc15
@@ -0,0 +1,8 @@
1
+ fileFormatVersion: 2
2
+ guid: f33d137e7882f564095528b29e26f59f
3
+ folderAsset: yes
4
+ DefaultImporter:
5
+ externalObjects: {}
6
+ userData:
7
+ assetBundleName:
8
+ assetBundleVariant: