-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathBinjectManager.cs
474 lines (399 loc) · 20 KB
/
BinjectManager.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
#if (UNITY_EDITOR || DEVELOPMENT_BUILD || DEBUG) && BINJECT_VERBOSE && !BINJECT_SILENT
#define B_DEBUG
#endif
#if !BINJECT_SILENT
#define WARN
#endif
using System;
using UnityEngine;
using System.Linq;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using System.Runtime.CompilerServices;
namespace Binject {
[ExecuteAlways]
public static class BinjectManager {
#if B_DEBUG
static BinjectManager() => Debug.Log( "Binject Domain Reloaded" );
#endif
/// <summary>
/// Topmost scene handle. can be used to detect the top root context
/// </summary>
[NonSerialized] static SceneHandle _topMostScene;
/// <summary>
/// Contexts grouped per scene (key is <see cref="Scene.handle"/>). only scenes with at least 1 <see cref="BContext"/> are
/// contained here; when they reach zero length, they'll be removed from the dictionary altogether.
/// </summary>
[NonSerialized] static readonly Dictionary<SceneHandle, BContextList> _sceneContexts = new( 4 );
/// <summary>
/// contexts grouped per <see cref="BContext.Group"/>. only groups with at least 1 <see cref="BContext"/> are
/// contained here; when they reach zero length, they'll be removed from the dictionary altogether.
/// </summary>
[NonSerialized] static readonly Dictionary<ushort, BContextList> _groupedContexts = new( 4 );
#region Publics
/// <summary>
/// Returns the root <see cref="BContext"/> of this scene. It will create new <see cref="BContext"/> component
/// on this gameObject instead.
/// </summary>
public static BContext GetSceneRootContext(Transform transform) {
if (_sceneContexts.TryGetValue( new( transform.gameObject.scene ), out var list ))
return list.GetRoot();
#if WARN
Debug.LogWarning( $"No context found in scene {transform.gameObject.scene}, Creating a new " +
$"{nameof(BContext)} component on the game object instead.", transform );
#endif
return transform.gameObject.AddComponent<BContext>();
}
/// <summary>
/// Finds the first context in self or it's parent. It'll go to other scenes if didn't find any. if nothing was
/// found, it'll create a new <see cref="BContext"/> component on the gameObject.
/// </summary>
public static BContext FindNearestContext(Transform transform, ushort groupNumber = 0) {
BContextList groupList = null;
if (groupNumber != 0 && !_groupedContexts.TryGetValue( groupNumber, out groupList )) {
goto CreateComponent;
}
if (_sceneContexts.TryGetValue( new( transform.gameObject.scene ), out var contextsInScene )) {
var originalTransform = transform;
// parents
while (transform is not null) {
for (int i = 0; i < contextsInScene.Count; i++) {
var context = contextsInScene[i];
if (isCorrectGroup( context, groupNumber ) && ReferenceEquals( transform, context.transform )) {
contextsInScene.AddPoint( i );
return context;
}
}
transform = transform.parent;
}
// scene root
var root = contextsInScene.GetRoot();
if (isCorrectGroup( root, groupNumber ))
return root;
transform = originalTransform;
}
// topmost root
var topmostRoot = _sceneContexts[_topMostScene].GetRoot();
if (isCorrectGroup( topmostRoot, groupNumber ))
return topmostRoot;
// root of grouped contexts
if (groupList is not null)
return groupList.GetRoot();
// create a component
CreateComponent:
#if WARN
Debug.LogWarning( $"No context found with the group {groupNumber}. Creating a new one on the game " +
"object instead", transform );
#endif
return transform.gameObject.AddComponent<BContext>();
}
/// <summary>
/// Finds the compatible context holding the specified dependency. returns null if not found any.
/// </summary>
[MethodImpl( MethodImplOptions.AggressiveInlining)]
public static BContext FindContext<T>(Transform transform, ushort groupNumber = 0) {
var sceneHandle = new SceneHandle( transform.gameObject.scene );
// search in scene
if (_sceneContexts.TryGetValue( sceneHandle, out var contextsInScene )) {
// check parents
while (transform is not null) {
// fast check
if (contextsInScene.ContainsTransform( transform )) {
// find
for (int i = 0; i < contextsInScene.Count; i++) {
var context = contextsInScene[i];
if (isCorrectGroup( context, groupNumber ) && context.transform == transform ) {
if (context.HasDependency<T>()) {
contextsInScene.AddPoint( i );
return context;
}
}
}
}
transform = transform.parent;
}
// check scene root context
var root = contextsInScene.GetRoot();
if (isCorrectGroup( root, groupNumber ) && root.HasDependency<T>())
return root;
}
// check topmost scene root context
if (_topMostScene.Equals( sceneHandle )) {
var root = _sceneContexts[_topMostScene].GetRoot();
if (isCorrectGroup( root, groupNumber ) && root.HasDependency<T>())
return root;
}
// check grouped contexts from any scene
if (_groupedContexts.TryGetValue( groupNumber, out var list ) && list.Count > 0) {
for (int i = 0; i < list.Count; i++) {
var context = list[i];
if (context.HasDependency<T>()) {
list.AddPoint( i );
return context;
}
}
}
#if WARN
Debug.LogWarning( $"No context found containing the dependency type {typeof(T).FullName}" );
#endif
return null;
}
#endregion
#region Non Publics
/// <summary>
/// Adds the context to internal lists and updates caches
/// </summary>
internal static void AddContext(BContext context, SceneHandle sceneHandle) {
#if B_DEBUG
Debug.Log( $"adding {context.name}({context.gameObject.scene.name}). all: {CreateStringListOfAllContexts()}", context );
#endif
// add to lists
if (!_groupedContexts.TryGetValue( context.Group, out var glist ))
_groupedContexts[context.Group] = glist = new( 4 );
glist.Add( context );
if (!_sceneContexts.TryGetValue( sceneHandle, out var slist ))
_sceneContexts[sceneHandle] = slist = new( 4 );
slist.Add( context );
UpdateAllRootContextsAndTopmostScene();
}
/// <summary>
/// Removes the context from internal lists and updates caches
/// </summary>
internal static void RemoveContext(BContext context, SceneHandle sceneHandle) {
#if B_DEBUG
Debug.Log( $"removing {(context ? $"{context.name}({context.gameObject.scene.name})" : "null")}. all: {CreateStringListOfAllContexts()}", context );
#endif
bool changed = false;
// remove from lists
if (_groupedContexts.TryGetValue( context.Group, out var glist )) {
changed = glist.Remove( context );
if (changed && glist.Count == 0)
_groupedContexts.Remove( context.Group );
}
if (_sceneContexts.TryGetValue( sceneHandle, out var slist )) {
changed |= slist.Remove( context );
if (changed && slist.Count == 0)
_sceneContexts.Remove( sceneHandle );
}
if (changed) UpdateAllRootContextsAndTopmostScene();
}
/// <summary>
/// Updates the internal lists based on the scene change. Will also update all the root contexts. <para/>
/// <b> It's an expensive call! </b>
/// </summary>
internal static void UpdateContextScene(BContext context, SceneHandle previousScene) {
var sceneHandle = new SceneHandle( context.gameObject.scene );
if (sceneHandle.Value == previousScene.Value) return;
#if B_DEBUG
Debug.Log( $"Context {context.name} changed scene handle from {previousScene.Value} to {sceneHandle.Value}" );
#endif
// add
if (!_sceneContexts.TryGetValue( sceneHandle, out var list ))
_sceneContexts.Add( sceneHandle, list = new( 8 ) );
list.Add( context );
// remove
list = _sceneContexts[previousScene];
list.Remove( context );
if (list.Count == 0) _sceneContexts.Remove( previousScene );
UpdateAllRootContextsAndTopmostScene();
}
#if B_DEBUG
[MethodImpl( MethodImplOptions.AggressiveInlining )]
static string CreateStringListOfAllContexts() =>
$"[{string.Join( ", ", _sceneContexts.SelectMany( s => s.Value ).Select( c => c ? $"{c.name}({c.gameObject.scene.name})" : "null" ) )}]";
#endif
/// <summary>
/// Updates <see cref="_sceneContexts"/>'s roots, <see cref="_groupedContexts"/>'s roots and
/// <see cref="_topMostScene"/>.
/// </summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
internal static void UpdateAllRootContextsAndTopmostScene() {
if (_sceneContexts.Count == 0) {
_topMostScene = default;
return;
}
// will be needed
var stack = new Stack<Transform>( 32 );
// scene roots
int rootOrder;
foreach (var contexts in _sceneContexts.Values) {
#if B_DEBUG
bool changed = false;
#endif
rootOrder = CalculateHierarchyOrder( contexts.GetRoot().transform, stack );
for (int i = 0; i < contexts.Count; i++) {
var order = CalculateHierarchyOrder( contexts[i].transform, stack );
if (order < rootOrder) {
rootOrder = order;
contexts.RootIndex = i;
#if B_DEBUG
changed = true;
#endif
}
}
#if B_DEBUG
if (changed)
Debug.Log( $"Root of scene '{contexts[0].gameObject.scene.name}' changed: {contexts.GetRoot().name}({contexts.GetRoot().gameObject.scene.name})" );
#endif
}
// group roots (and topmost scene at the same time)
Dictionary<Scene, int> sceneOrder = new( SceneManager.sceneCount );
bool foundTopmostScene = false;
for (int i = 0; i < SceneManager.sceneCount; i++) {
sceneOrder[SceneManager.GetSceneAt( i )] = i;
// resolving topmost scene right here
var scene = SceneManager.GetSceneAt( i );
var sceneHandle = new SceneHandle( scene );
if (!foundTopmostScene && _sceneContexts.ContainsKey( sceneHandle )) {
#if B_DEBUG
if (!_topMostScene.Equals( sceneHandle ))
Debug.Log( $"Topmost scene changed: {scene.name}" );
#endif
_topMostScene = sceneHandle;
foundTopmostScene = true;
}
}
foreach (var contexts in _groupedContexts.Values) {
const int SCENE_BENEFIT = 1_000_000;
rootOrder = CalculateHierarchyOrder( contexts.GetRoot().transform, stack ) * sceneOrder[contexts.GetRoot().gameObject.scene] * SCENE_BENEFIT;
#if B_DEBUG
bool changed = false;
#endif
for (int i = 0; i < contexts.Count; i++) {
var order = CalculateHierarchyOrder( contexts[i].transform, stack ) * sceneOrder[contexts[i].gameObject.scene] * SCENE_BENEFIT;
if (order < rootOrder) {
rootOrder = order;
contexts.RootIndex = i;
#if B_DEBUG
changed = true;
#endif
}
}
#if B_DEBUG
if (changed)
Debug.Log( $"Root of group '{contexts[0].Group}' changed: {contexts.GetRoot().name}({contexts.GetRoot().gameObject.scene.name})" );
#endif
}
}
/// <summary>
/// checks whether or not the <see cref="context"/> is compatible with the given <see cref="groupNumber"/>
/// </summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
static bool isCorrectGroup(BContext context, ushort groupNumber) => groupNumber == 0 || groupNumber == context.Group;
/// <summary>
/// Returns the index of which the transform will show up in hierarchy if everything is expanded. <para/>
/// the <see cref="stack"/> has to be empty but initialized.
/// </summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
static int CalculateHierarchyOrder(Transform transform, Stack<Transform> stack) {
do {
stack.Push( transform );
transform = transform.parent;
} while (transform is not null);
int order = 0;
while (stack.Count > 0)
order += stack.Pop().GetSiblingIndex() * 100;
return order;
}
#endregion
#region Public Helpers
/// <summary>
/// Returns the dependency from a compatible context. returns default if not found any.
/// </summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static T GetDependency<T>(Transform transform, ushort groupNumber = 0) {
var context = FindContext<T>( transform, groupNumber );
return context == null ? default : context.GetDependencyNoCheck<T>();
}
/// <summary>
/// Finds the dependency from a compatible context and returns `true` if found any, and `false` if didn't.
/// <see cref="result"/> will be default if didn't find any. <para/>
/// </summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static bool TryGetDependency<T>(Transform transform, out T result, ushort groupNumber = 0) {
var context = FindContext<T>( transform, groupNumber );
result = context is null ? default : context.GetDependencyNoCheck<T>();
return context is not null;
}
/// <summary>
/// Checks if the dependency exists in a compatible context.
/// </summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static bool DependencyExists<T>(Transform transform, ushort groupNumber = 0) {
return FindContext<T>( transform, groupNumber ) != null;
}
#endregion
#region Extensions
/// <inheritdoc cref="GetDependency{T}(UnityEngine.Transform,ushort)"/>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static T GetDependency<T>(this Component component, ushort groupName = 0) {
return GetDependency<T>( component.transform, groupName );
}
/// <inheritdoc cref="TryGetDependency{T}(UnityEngine.Transform,out T,ushort)"/>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static bool TryGetDependency<T>(this Component component, out T result, ushort groupNumber = 0) =>
TryGetDependency( component.transform, out result, groupNumber );
/// <inheritdoc cref="DependencyExists{T}(UnityEngine.Transform,ushort)"/>
[MethodImpl (MethodImplOptions.AggressiveInlining )]
public static bool DependencyExists<T>(this Component component, ushort groupName = 0) {
return DependencyExists<T>( component.transform, groupName );
}
/// <inheritdoc cref="FindNearestContext(UnityEngine.Transform,ushort)"/>
public static BContext FindNearestContext(this Component component, ushort groupNumber = 0) =>
FindNearestContext( component.transform, groupNumber );
/// <inheritdoc cref="FindContext{T}(UnityEngine.Transform,ushort)"/>
public static BContext FindContext<T>(this Component component, ushort groupNumber = 0) =>
FindContext<T>( component.transform, groupNumber );
/// <inheritdoc cref="GetSceneRootContext(UnityEngine.Transform)"/>
public static BContext GetSceneRootContext(this Component component) =>
GetSceneRootContext( component.transform );
#region Multis
/// <inheritdoc cref="GetDependency{T}(UnityEngine.Transform,ushort)"/>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void GetDependency<T1, T2>(this Component component, out T1 result1, out T2 result2, ushort groupName = 0) {
result1 = GetDependency<T1>( component.transform, groupName );
result2 = GetDependency<T2>( component.transform, groupName );
}
/// <inheritdoc cref="GetDependency{T}(UnityEngine.Transform,ushort)"/>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void GetDependency<T1, T2, T3>(this Component component, out T1 result1, out T2 result2, out T3 result3, ushort groupName = 0) {
result1 = GetDependency<T1>( component.transform, groupName );
result2 = GetDependency<T2>( component.transform, groupName );
result3 = GetDependency<T3>( component.transform, groupName );
}
/// <inheritdoc cref="GetDependency{T}(UnityEngine.Transform,ushort)"/>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void GetDependency<T1, T2, T3, T4>(this Component component, out T1 result1, out T2 result2, out T3 result3, out T4 result4, ushort groupName = 0) {
result1 = GetDependency<T1>( component.transform, groupName );
result2 = GetDependency<T2>( component.transform, groupName );
result3 = GetDependency<T3>( component.transform, groupName );
result4 = GetDependency<T4>( component.transform, groupName );
}
#endregion
#endregion
}
internal readonly struct SceneHandle : IEquatable<SceneHandle> {
public readonly int Value;
public SceneHandle(Scene scene) => Value = scene.handle;
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public override int GetHashCode() => Value;
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public bool Equals(SceneHandle other) => Value == other.Value;
}
struct TransformTypeTuple : IEqualityComparer<TransformTypeTuple> {
public int transformHash;
public Type type;
public TransformTypeTuple(Transform transform, Type type) {
transformHash = transform.GetHashCode();
this.type = type;
}
public bool Equals(TransformTypeTuple x, TransformTypeTuple y) => x.transformHash == y.transformHash && ReferenceEquals( x.type, y.type );
public int GetHashCode(TransformTypeTuple obj) {
var hash = new HashCode();
hash.Add( obj.transformHash.GetHashCode() );
hash.Add( obj.type.GetHashCode() );
return hash.ToHashCode();
}
public override string ToString() => $"({transformHash}, {type})";
}
}