com.kylin.di 1.0.0 → 1.1.1
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 +8 -4
- package/.omc/project-memory.json +229 -0
- package/.omc/state/hud-state.json +6 -0
- package/README.md +1 -1
- package/Runtime/Kylin.DI.asmdef +3 -1
- package/package.json +10 -4
- package/Runtime/Debug/ClosureAnalyzer.cs +0 -377
- package/Runtime/Debug/ClosureAnalyzer.cs.meta +0 -2
- package/Runtime/Debug/ClosureProfilerWindow.cs +0 -435
- package/Runtime/Debug/ClosureProfilerWindow.cs.meta +0 -2
- package/Runtime/Debug/SubscriberInfo.cs +0 -661
- package/Runtime/Debug/SubscriberInfo.cs.meta +0 -3
- package/Runtime/Debug.meta +0 -3
- package/Runtime/SubscribableProperty/Reaction.cs +0 -61
- package/Runtime/SubscribableProperty/Reaction.cs.meta +0 -2
- package/Runtime/SubscribableProperty/SubscribableCollection.cs +0 -325
- package/Runtime/SubscribableProperty/SubscribableCollection.cs.meta +0 -3
- package/Runtime/SubscribableProperty/SubscribableCollectionExtensions.cs +0 -24
- package/Runtime/SubscribableProperty/SubscribableCollectionExtensions.cs.meta +0 -3
- package/Runtime/SubscribableProperty/SubscribableCommand.cs +0 -52
- package/Runtime/SubscribableProperty/SubscribableCommand.cs.meta +0 -2
- package/Runtime/SubscribableProperty/SubscribableDictionary.cs +0 -350
- package/Runtime/SubscribableProperty/SubscribableDictionary.cs.meta +0 -3
- package/Runtime/SubscribableProperty/SubscribableProperty.cs +0 -119
- package/Runtime/SubscribableProperty/SubscribableProperty.cs.meta +0 -2
- package/Runtime/SubscribableProperty/SubscribablePropertyExtensions.cs +0 -39
- package/Runtime/SubscribableProperty/SubscribablePropertyExtensions.cs.meta +0 -3
- package/Runtime/SubscribableProperty/SubscribablePropertyLinq.cs +0 -86
- package/Runtime/SubscribableProperty/SubscribablePropertyLinq.cs.meta +0 -2
- package/Runtime/SubscribableProperty.meta +0 -8
|
@@ -8,6 +8,9 @@ on:
|
|
|
8
8
|
jobs:
|
|
9
9
|
publish:
|
|
10
10
|
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
id-token: write
|
|
11
14
|
steps:
|
|
12
15
|
- uses: actions/checkout@v4
|
|
13
16
|
|
|
@@ -16,7 +19,8 @@ jobs:
|
|
|
16
19
|
node-version: '20'
|
|
17
20
|
registry-url: 'https://registry.npmjs.org'
|
|
18
21
|
|
|
19
|
-
- name:
|
|
20
|
-
run: npm
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
- name: Upgrade npm for OIDC support
|
|
23
|
+
run: npm install -g npm@latest
|
|
24
|
+
|
|
25
|
+
- name: Publish with trusted publishing
|
|
26
|
+
run: npm publish --access public
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"lastScanned": 1773710696675,
|
|
4
|
+
"projectRoot": "C:\\ProjectControl\\KDIPackage",
|
|
5
|
+
"techStack": {
|
|
6
|
+
"languages": [
|
|
7
|
+
{
|
|
8
|
+
"name": "JavaScript/TypeScript",
|
|
9
|
+
"version": null,
|
|
10
|
+
"confidence": "high",
|
|
11
|
+
"markers": [
|
|
12
|
+
"package.json"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"frameworks": [],
|
|
17
|
+
"packageManager": "npm",
|
|
18
|
+
"runtime": null
|
|
19
|
+
},
|
|
20
|
+
"build": {
|
|
21
|
+
"buildCommand": null,
|
|
22
|
+
"testCommand": null,
|
|
23
|
+
"lintCommand": null,
|
|
24
|
+
"devCommand": null,
|
|
25
|
+
"scripts": {}
|
|
26
|
+
},
|
|
27
|
+
"conventions": {
|
|
28
|
+
"namingStyle": null,
|
|
29
|
+
"importStyle": null,
|
|
30
|
+
"testPattern": null,
|
|
31
|
+
"fileOrganization": null
|
|
32
|
+
},
|
|
33
|
+
"structure": {
|
|
34
|
+
"isMonorepo": false,
|
|
35
|
+
"workspaces": [],
|
|
36
|
+
"mainDirectories": [],
|
|
37
|
+
"gitBranches": {
|
|
38
|
+
"defaultBranch": "master",
|
|
39
|
+
"branchingStrategy": null
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"customNotes": [],
|
|
43
|
+
"directoryMap": {
|
|
44
|
+
"Runtime": {
|
|
45
|
+
"path": "Runtime",
|
|
46
|
+
"purpose": null,
|
|
47
|
+
"fileCount": 8,
|
|
48
|
+
"lastAccessed": 1773710696650,
|
|
49
|
+
"keyFiles": [
|
|
50
|
+
"Attributes.meta",
|
|
51
|
+
"Core.meta",
|
|
52
|
+
"Debug.meta",
|
|
53
|
+
"DI.meta",
|
|
54
|
+
"Kylin.DI.asmdef"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"hotPaths": [
|
|
59
|
+
{
|
|
60
|
+
"path": "",
|
|
61
|
+
"accessCount": 5,
|
|
62
|
+
"lastAccessed": 1773710728288,
|
|
63
|
+
"type": "directory"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"path": "README.md",
|
|
67
|
+
"accessCount": 2,
|
|
68
|
+
"lastAccessed": 1773711146365,
|
|
69
|
+
"type": "file"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"path": "package.json",
|
|
73
|
+
"accessCount": 1,
|
|
74
|
+
"lastAccessed": 1773710732915,
|
|
75
|
+
"type": "file"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"path": "Runtime\\Kylin.DI.asmdef",
|
|
79
|
+
"accessCount": 1,
|
|
80
|
+
"lastAccessed": 1773710733554,
|
|
81
|
+
"type": "file"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"path": "Runtime\\DI\\KDI.cs",
|
|
85
|
+
"accessCount": 1,
|
|
86
|
+
"lastAccessed": 1773710733804,
|
|
87
|
+
"type": "file"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"path": "Runtime\\DI\\Scope.cs",
|
|
91
|
+
"accessCount": 1,
|
|
92
|
+
"lastAccessed": 1773710734108,
|
|
93
|
+
"type": "file"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"path": "Runtime\\DI\\Registration.cs",
|
|
97
|
+
"accessCount": 1,
|
|
98
|
+
"lastAccessed": 1773710734544,
|
|
99
|
+
"type": "file"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"path": "Runtime\\Core\\LifetimeScope.cs",
|
|
103
|
+
"accessCount": 1,
|
|
104
|
+
"lastAccessed": 1773710734976,
|
|
105
|
+
"type": "file"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"path": "Runtime\\Core\\DIBehaviour.cs",
|
|
109
|
+
"accessCount": 1,
|
|
110
|
+
"lastAccessed": 1773710735204,
|
|
111
|
+
"type": "file"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"path": "Runtime\\DI\\DependencyBuilder.cs",
|
|
115
|
+
"accessCount": 1,
|
|
116
|
+
"lastAccessed": 1773710739245,
|
|
117
|
+
"type": "file"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"path": "Runtime\\DI\\DependencyInjector.cs",
|
|
121
|
+
"accessCount": 1,
|
|
122
|
+
"lastAccessed": 1773710739574,
|
|
123
|
+
"type": "file"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"path": "Runtime\\DI\\InstanceFactory.cs",
|
|
127
|
+
"accessCount": 1,
|
|
128
|
+
"lastAccessed": 1773710740017,
|
|
129
|
+
"type": "file"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"path": "Runtime\\DI\\ScopeBuilder.cs",
|
|
133
|
+
"accessCount": 1,
|
|
134
|
+
"lastAccessed": 1773710740465,
|
|
135
|
+
"type": "file"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"path": "Runtime\\DI\\ScopeExtensions.cs",
|
|
139
|
+
"accessCount": 1,
|
|
140
|
+
"lastAccessed": 1773710740905,
|
|
141
|
+
"type": "file"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"path": "Runtime\\Attributes\\InjectAttribute.cs",
|
|
145
|
+
"accessCount": 1,
|
|
146
|
+
"lastAccessed": 1773710741924,
|
|
147
|
+
"type": "file"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"path": "Runtime\\Attributes\\ViewModelAttribute.cs",
|
|
151
|
+
"accessCount": 1,
|
|
152
|
+
"lastAccessed": 1773710742234,
|
|
153
|
+
"type": "file"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"path": "Runtime\\Update\\UpdateLoopManager.cs",
|
|
157
|
+
"accessCount": 1,
|
|
158
|
+
"lastAccessed": 1773710742681,
|
|
159
|
+
"type": "file"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"path": "Runtime\\SubscribableProperty\\SubscribableProperty.cs",
|
|
163
|
+
"accessCount": 1,
|
|
164
|
+
"lastAccessed": 1773710742957,
|
|
165
|
+
"type": "file"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"path": "CLAUDE.md",
|
|
169
|
+
"accessCount": 1,
|
|
170
|
+
"lastAccessed": 1773710790027,
|
|
171
|
+
"type": "file"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"path": "Runtime\\SubscribableProperty\\SubscribableCollection.cs",
|
|
175
|
+
"accessCount": 1,
|
|
176
|
+
"lastAccessed": 1773710989419,
|
|
177
|
+
"type": "file"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"path": "Runtime\\SubscribableProperty\\SubscribablePropertyExtensions.cs",
|
|
181
|
+
"accessCount": 1,
|
|
182
|
+
"lastAccessed": 1773710989888,
|
|
183
|
+
"type": "file"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"path": "Runtime\\SubscribableProperty\\SubscribablePropertyLinq.cs",
|
|
187
|
+
"accessCount": 1,
|
|
188
|
+
"lastAccessed": 1773710990457,
|
|
189
|
+
"type": "file"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"path": "Runtime\\SubscribableProperty\\SubscribableCommand.cs",
|
|
193
|
+
"accessCount": 1,
|
|
194
|
+
"lastAccessed": 1773710990803,
|
|
195
|
+
"type": "file"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"path": "Runtime\\SubscribableProperty\\SubscribableDictionary.cs",
|
|
199
|
+
"accessCount": 1,
|
|
200
|
+
"lastAccessed": 1773710991251,
|
|
201
|
+
"type": "file"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"path": "Runtime\\SubscribableProperty\\SubscribableCollectionExtensions.cs",
|
|
205
|
+
"accessCount": 1,
|
|
206
|
+
"lastAccessed": 1773710991698,
|
|
207
|
+
"type": "file"
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"path": "Runtime\\Update\\IUpdatable.cs",
|
|
211
|
+
"accessCount": 1,
|
|
212
|
+
"lastAccessed": 1773710992139,
|
|
213
|
+
"type": "file"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"path": "Runtime\\Update\\IUpdatePriority.cs",
|
|
217
|
+
"accessCount": 1,
|
|
218
|
+
"lastAccessed": 1773710992657,
|
|
219
|
+
"type": "file"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"path": "Runtime\\Debug\\ClosureAnalyzer.cs",
|
|
223
|
+
"accessCount": 1,
|
|
224
|
+
"lastAccessed": 1773710993007,
|
|
225
|
+
"type": "file"
|
|
226
|
+
}
|
|
227
|
+
],
|
|
228
|
+
"userDirectives": []
|
|
229
|
+
}
|
package/README.md
CHANGED
package/Runtime/Kylin.DI.asmdef
CHANGED
package/package.json
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "com.kylin.di",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"displayName": "KDI (Kylin Dependency Injection)",
|
|
5
|
-
"description": "Scope-based dependency injection framework for Unity 6. Field injection only, hierarchical scopes
|
|
5
|
+
"description": "Scope-based dependency injection framework for Unity 6. Field injection only, hierarchical scopes.",
|
|
6
6
|
"unity": "6000.0",
|
|
7
7
|
"documentationUrl": "",
|
|
8
8
|
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/ToolStorage/KDI"
|
|
12
|
+
},
|
|
9
13
|
"keywords": [
|
|
10
14
|
"dependency-injection",
|
|
11
15
|
"di",
|
|
12
16
|
"ioc",
|
|
13
|
-
"scope"
|
|
14
|
-
"reactive"
|
|
17
|
+
"scope"
|
|
15
18
|
],
|
|
16
19
|
"author": {
|
|
17
20
|
"name": "Kylin"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"com.kylin.subscribable": "1.0.0"
|
|
18
24
|
}
|
|
19
25
|
}
|
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
using System;
|
|
2
|
-
using System.Collections.Generic;
|
|
3
|
-
using System.Linq;
|
|
4
|
-
using System.Reflection;
|
|
5
|
-
using UnityEngine;
|
|
6
|
-
|
|
7
|
-
namespace Kylin.SubscribableProperty
|
|
8
|
-
{
|
|
9
|
-
/// <summary>
|
|
10
|
-
/// 클로저 캡처 정보
|
|
11
|
-
/// </summary>
|
|
12
|
-
public class ClosureCaptureInfo
|
|
13
|
-
{
|
|
14
|
-
public WeakReference ClosureRef { get; }
|
|
15
|
-
public Type ClosureType { get; }
|
|
16
|
-
public string ClosureTypeName { get; }
|
|
17
|
-
public DateTime CaptureTime { get; }
|
|
18
|
-
public string CaptureLocation { get; }
|
|
19
|
-
|
|
20
|
-
// 캡처된 변수들
|
|
21
|
-
public List<CapturedVariableInfo> CapturedVariables { get; }
|
|
22
|
-
|
|
23
|
-
// 메모리 정보
|
|
24
|
-
public long EstimatedMemoryBytes { get; private set; }
|
|
25
|
-
public bool IsAlive => ClosureRef.IsAlive;
|
|
26
|
-
public object Closure => ClosureRef.Target;
|
|
27
|
-
|
|
28
|
-
// 구독 해제 정보
|
|
29
|
-
public bool IsUnsubscribed { get; set; }
|
|
30
|
-
public DateTime? UnsubscribeTime { get; set; }
|
|
31
|
-
public TimeSpan? Lifetime => UnsubscribeTime.HasValue
|
|
32
|
-
? UnsubscribeTime.Value - CaptureTime
|
|
33
|
-
: (DateTime.Now - CaptureTime);
|
|
34
|
-
|
|
35
|
-
public ClosureCaptureInfo(object closure, string captureLocation)
|
|
36
|
-
{
|
|
37
|
-
ClosureRef = new WeakReference(closure);
|
|
38
|
-
ClosureType = closure.GetType();
|
|
39
|
-
ClosureTypeName = GetReadableTypeName(ClosureType);
|
|
40
|
-
CaptureTime = DateTime.Now;
|
|
41
|
-
CaptureLocation = captureLocation;
|
|
42
|
-
CapturedVariables = new List<CapturedVariableInfo>();
|
|
43
|
-
|
|
44
|
-
AnalyzeCapturedVariables();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
private string GetReadableTypeName(Type type)
|
|
48
|
-
{
|
|
49
|
-
// <>c__DisplayClass1_0 → Closure_1_0
|
|
50
|
-
if (type.Name.Contains("DisplayClass"))
|
|
51
|
-
{
|
|
52
|
-
var parts = type.Name.Split('_');
|
|
53
|
-
if (parts.Length >= 3)
|
|
54
|
-
{
|
|
55
|
-
return $"Closure_{parts[^2]}_{parts[^1]}";
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return type.Name;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
private void AnalyzeCapturedVariables()
|
|
62
|
-
{
|
|
63
|
-
if (!IsAlive) return;
|
|
64
|
-
|
|
65
|
-
var closure = Closure;
|
|
66
|
-
var fields = ClosureType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
67
|
-
|
|
68
|
-
foreach (var field in fields)
|
|
69
|
-
{
|
|
70
|
-
try
|
|
71
|
-
{
|
|
72
|
-
var value = field.GetValue(closure);
|
|
73
|
-
var varInfo = new CapturedVariableInfo(field, value);
|
|
74
|
-
CapturedVariables.Add(varInfo);
|
|
75
|
-
EstimatedMemoryBytes += varInfo.EstimatedMemoryBytes;
|
|
76
|
-
}
|
|
77
|
-
catch (Exception ex)
|
|
78
|
-
{
|
|
79
|
-
Debug.LogWarning($"[ClosureAnalyzer] Failed to analyze field {field.Name}: {ex.Message}");
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
public void MarkUnsubscribed()
|
|
85
|
-
{
|
|
86
|
-
IsUnsubscribed = true;
|
|
87
|
-
UnsubscribeTime = DateTime.Now;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
public void RefreshMemoryInfo()
|
|
91
|
-
{
|
|
92
|
-
EstimatedMemoryBytes = 0;
|
|
93
|
-
foreach (var varInfo in CapturedVariables)
|
|
94
|
-
{
|
|
95
|
-
varInfo.RefreshMemoryInfo();
|
|
96
|
-
EstimatedMemoryBytes += varInfo.EstimatedMemoryBytes;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/// <summary>
|
|
102
|
-
/// 캡처된 개별 변수 정보
|
|
103
|
-
/// </summary>
|
|
104
|
-
public class CapturedVariableInfo
|
|
105
|
-
{
|
|
106
|
-
public string FieldName { get; }
|
|
107
|
-
public Type FieldType { get; }
|
|
108
|
-
public string FieldTypeName { get; }
|
|
109
|
-
public WeakReference ValueRef { get; }
|
|
110
|
-
public bool IsReferenceType { get; }
|
|
111
|
-
public long EstimatedMemoryBytes { get; private set; }
|
|
112
|
-
|
|
113
|
-
// 위험도 평가
|
|
114
|
-
public ClosureCaptureRisk RiskLevel { get; private set; }
|
|
115
|
-
public string RiskDescription { get; private set; }
|
|
116
|
-
|
|
117
|
-
public bool IsAlive => ValueRef?.IsAlive ?? false;
|
|
118
|
-
public object Value => ValueRef?.Target;
|
|
119
|
-
|
|
120
|
-
public CapturedVariableInfo(FieldInfo field, object value)
|
|
121
|
-
{
|
|
122
|
-
FieldName = CleanFieldName(field.Name);
|
|
123
|
-
FieldType = field.FieldType;
|
|
124
|
-
FieldTypeName = GetReadableTypeName(field.FieldType);
|
|
125
|
-
IsReferenceType = !field.FieldType.IsValueType;
|
|
126
|
-
|
|
127
|
-
if (value != null && IsReferenceType)
|
|
128
|
-
{
|
|
129
|
-
ValueRef = new WeakReference(value);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
EstimateMemory(value);
|
|
133
|
-
EvaluateRisk(field, value);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
private string CleanFieldName(string fieldName)
|
|
137
|
-
{
|
|
138
|
-
// <>4__this → this
|
|
139
|
-
if (fieldName.Contains("4__this"))
|
|
140
|
-
return "this";
|
|
141
|
-
|
|
142
|
-
// CS$<>8__locals1 → locals_1
|
|
143
|
-
if (fieldName.Contains("CS$"))
|
|
144
|
-
return "locals";
|
|
145
|
-
|
|
146
|
-
return fieldName.TrimStart('<', '>');
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private string GetReadableTypeName(Type type)
|
|
150
|
-
{
|
|
151
|
-
if (type.IsGenericType)
|
|
152
|
-
{
|
|
153
|
-
var name = type.Name.Split('`')[0];
|
|
154
|
-
var args = string.Join(", ", type.GetGenericArguments().Select(t => t.Name));
|
|
155
|
-
return $"{name}<{args}>";
|
|
156
|
-
}
|
|
157
|
-
return type.Name;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
private void EstimateMemory(object value)
|
|
161
|
-
{
|
|
162
|
-
if (value == null)
|
|
163
|
-
{
|
|
164
|
-
EstimatedMemoryBytes = 0;
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (!IsReferenceType)
|
|
169
|
-
{
|
|
170
|
-
EstimatedMemoryBytes = System.Runtime.InteropServices.Marshal.SizeOf(FieldType);
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// 참조 타입 메모리 추정
|
|
175
|
-
EstimatedMemoryBytes = 8; // 참조 자체
|
|
176
|
-
|
|
177
|
-
try
|
|
178
|
-
{
|
|
179
|
-
if (value is string str)
|
|
180
|
-
{
|
|
181
|
-
EstimatedMemoryBytes += str.Length * 2 + 24; // UTF-16 + 오버헤드
|
|
182
|
-
}
|
|
183
|
-
else if (value is Array array)
|
|
184
|
-
{
|
|
185
|
-
EstimatedMemoryBytes += array.Length * 8 + 24;
|
|
186
|
-
}
|
|
187
|
-
else if (value is System.Collections.ICollection collection)
|
|
188
|
-
{
|
|
189
|
-
EstimatedMemoryBytes += collection.Count * 8 + 40;
|
|
190
|
-
}
|
|
191
|
-
else if (value is MonoBehaviour || value is ScriptableObject)
|
|
192
|
-
{
|
|
193
|
-
EstimatedMemoryBytes += 1024; // Unity 객체 추정치
|
|
194
|
-
}
|
|
195
|
-
else
|
|
196
|
-
{
|
|
197
|
-
EstimatedMemoryBytes += 64; // 일반 객체 추정치
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
catch
|
|
201
|
-
{
|
|
202
|
-
EstimatedMemoryBytes += 64;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
private void EvaluateRisk(FieldInfo field, object value)
|
|
207
|
-
{
|
|
208
|
-
RiskLevel = ClosureCaptureRisk.Low;
|
|
209
|
-
RiskDescription = "안전";
|
|
210
|
-
|
|
211
|
-
if (value == null) return;
|
|
212
|
-
|
|
213
|
-
// this 캡처 (가장 위험)
|
|
214
|
-
if (field.Name.Contains("4__this"))
|
|
215
|
-
{
|
|
216
|
-
RiskLevel = ClosureCaptureRisk.Critical;
|
|
217
|
-
RiskDescription = "인스턴스 전체 캡처 - 모든 필드가 메모리에 유지됨";
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// 큰 컬렉션
|
|
222
|
-
if (value is System.Collections.ICollection collection && collection.Count > 100)
|
|
223
|
-
{
|
|
224
|
-
RiskLevel = ClosureCaptureRisk.High;
|
|
225
|
-
RiskDescription = $"대용량 컬렉션 캡처 ({collection.Count}개 요소)";
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Unity 객체
|
|
230
|
-
if (value is MonoBehaviour || value is ScriptableObject)
|
|
231
|
-
{
|
|
232
|
-
RiskLevel = ClosureCaptureRisk.High;
|
|
233
|
-
RiskDescription = "Unity 컴포넌트 캡처 - 게임 오브젝트와 연결된 모든 데이터 유지";
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// 큰 배열/버퍼
|
|
238
|
-
if (value is Array array && array.Length > 1000)
|
|
239
|
-
{
|
|
240
|
-
RiskLevel = ClosureCaptureRisk.High;
|
|
241
|
-
RiskDescription = $"대용량 배열 캡처 ({array.Length}개 요소, ~{EstimatedMemoryBytes / 1024}KB)";
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// 중간 위험
|
|
246
|
-
if (EstimatedMemoryBytes > 1024) // 1KB 이상
|
|
247
|
-
{
|
|
248
|
-
RiskLevel = ClosureCaptureRisk.Medium;
|
|
249
|
-
RiskDescription = $"메모리 사용량 높음 (~{EstimatedMemoryBytes / 1024}KB)";
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// 참조 타입이지만 작은 객체
|
|
254
|
-
if (IsReferenceType)
|
|
255
|
-
{
|
|
256
|
-
RiskLevel = ClosureCaptureRisk.Low;
|
|
257
|
-
RiskDescription = "작은 참조 타입 캡처";
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
public void RefreshMemoryInfo()
|
|
262
|
-
{
|
|
263
|
-
if (IsAlive)
|
|
264
|
-
{
|
|
265
|
-
EstimateMemory(Value);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
public enum ClosureCaptureRisk
|
|
271
|
-
{
|
|
272
|
-
Low, // 안전 (값 타입, 작은 참조)
|
|
273
|
-
Medium, // 주의 (중간 크기 객체)
|
|
274
|
-
High, // 위험 (큰 객체, Unity 컴포넌트)
|
|
275
|
-
Critical // 매우 위험 (this 캡처)
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/// <summary>
|
|
279
|
-
/// 클로저 캡처 프로파일러
|
|
280
|
-
/// </summary>
|
|
281
|
-
public static class ClosureProfiler
|
|
282
|
-
{
|
|
283
|
-
private static readonly List<ClosureCaptureInfo> _captures = new List<ClosureCaptureInfo>();
|
|
284
|
-
private static readonly object _lock = new object();
|
|
285
|
-
|
|
286
|
-
public static bool IsEnabled =>
|
|
287
|
-
#if UNITY_EDITOR
|
|
288
|
-
ClosureProfilerWindow.IsWindowOpen && ClosureProfilerWindow.IsProfilingEnabled;
|
|
289
|
-
#else
|
|
290
|
-
false;
|
|
291
|
-
#endif
|
|
292
|
-
|
|
293
|
-
public static void RecordCapture(object closure, string location)
|
|
294
|
-
{
|
|
295
|
-
if (!IsEnabled) return;
|
|
296
|
-
if (closure == null) return;
|
|
297
|
-
|
|
298
|
-
// 컴파일러 생성 클로저인지 확인
|
|
299
|
-
var typeName = closure.GetType().Name;
|
|
300
|
-
if (!typeName.Contains("DisplayClass") && !typeName.Contains("<>c"))
|
|
301
|
-
return;
|
|
302
|
-
|
|
303
|
-
lock (_lock)
|
|
304
|
-
{
|
|
305
|
-
var info = new ClosureCaptureInfo(closure, location);
|
|
306
|
-
_captures.Add(info);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
public static void RecordUnsubscribe(object closure)
|
|
311
|
-
{
|
|
312
|
-
if (closure == null) return;
|
|
313
|
-
|
|
314
|
-
lock (_lock)
|
|
315
|
-
{
|
|
316
|
-
var info = _captures.FirstOrDefault(c =>
|
|
317
|
-
c.IsAlive && ReferenceEquals(c.Closure, closure));
|
|
318
|
-
|
|
319
|
-
if (info != null)
|
|
320
|
-
{
|
|
321
|
-
info.MarkUnsubscribed();
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
public static IEnumerable<ClosureCaptureInfo> GetAllCaptures()
|
|
327
|
-
{
|
|
328
|
-
lock (_lock)
|
|
329
|
-
{
|
|
330
|
-
// 죽은 클로저는 제거하지 않음 (히스토리 유지)
|
|
331
|
-
return _captures.ToList();
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
public static IEnumerable<ClosureCaptureInfo> GetActiveCaptures()
|
|
336
|
-
{
|
|
337
|
-
lock (_lock)
|
|
338
|
-
{
|
|
339
|
-
return _captures.Where(c => c.IsAlive && !c.IsUnsubscribed).ToList();
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
public static void Clear()
|
|
344
|
-
{
|
|
345
|
-
lock (_lock)
|
|
346
|
-
{
|
|
347
|
-
_captures.Clear();
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
public static (int total, int active, int unsubscribed, long totalMemory) GetStatistics()
|
|
352
|
-
{
|
|
353
|
-
lock (_lock)
|
|
354
|
-
{
|
|
355
|
-
var total = _captures.Count;
|
|
356
|
-
var active = _captures.Count(c => c.IsAlive && !c.IsUnsubscribed);
|
|
357
|
-
var unsubscribed = _captures.Count(c => c.IsUnsubscribed);
|
|
358
|
-
var totalMemory = _captures
|
|
359
|
-
.Where(c => c.IsAlive && !c.IsUnsubscribed)
|
|
360
|
-
.Sum(c => c.EstimatedMemoryBytes);
|
|
361
|
-
|
|
362
|
-
return (total, active, unsubscribed, totalMemory);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
public static void RefreshMemoryInfo()
|
|
367
|
-
{
|
|
368
|
-
lock (_lock)
|
|
369
|
-
{
|
|
370
|
-
foreach (var capture in _captures.Where(c => c.IsAlive))
|
|
371
|
-
{
|
|
372
|
-
capture.RefreshMemoryInfo();
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|