├── AssemblyInfo.cs ├── AssemblyInfo.cs.meta ├── BContext.cs ├── BContext.cs.meta ├── BContextList.cs ├── BContextList.cs.meta ├── Binject.asmdef ├── Binject.asmdef.meta ├── BinjectManager.cs ├── BinjectManager.cs.meta ├── Editor.meta ├── Editor ├── BContextEditor.cs ├── BContextEditor.cs.meta ├── BinjectEditor.asmdef ├── BinjectEditor.asmdef.meta ├── TypeSelectDropDown.cs ├── TypeSelectDropDown.cs.meta ├── UnmanagedUtility.cs └── UnmanagedUtility.cs.meta ├── Interfaces.cs ├── Interfaces.cs.meta ├── LICENSE ├── LICENSE.meta ├── README.md ├── README.md.meta ├── RealValueHolder.cs ├── RealValueHolder.cs.meta ├── Res.meta ├── Res ├── BContext-icon.png ├── BContext-icon.png.meta ├── img.png ├── img.png.meta ├── img_1.png ├── img_1.png.meta ├── img_2.png ├── img_2.png.meta ├── img_3.png ├── img_3.png.meta ├── img_4.png ├── img_4.png.meta ├── img_5.png └── img_5.png.meta ├── package.json └── package.json.meta /AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | // for editor scripts 4 | [assembly: InternalsVisibleTo( "BinjectEditor" )] 5 | -------------------------------------------------------------------------------- /AssemblyInfo.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9c1054bcde4f4b9c8cfc21281fc277e0 3 | timeCreated: 1689517048 -------------------------------------------------------------------------------- /BContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.Text; 5 | using UnityEngine; 6 | using UnityEngine.Serialization; 7 | 8 | namespace Binject { 9 | /// 10 | /// A container for dependencies. You can use contexts to group dependencies. 11 | /// 12 | [ExecuteAlways] 13 | [DisallowMultipleComponent] 14 | [DefaultExecutionOrder( -10 )] 15 | [AddComponentMenu( "Binject/Binject Context" )] 16 | public sealed class BContext : MonoBehaviour { 17 | 18 | [Tooltip( "used for when you want to use a specific context but don't have access to it directly; you can find " + 19 | "it using it's Group number." )] 20 | [SerializeField] internal ushort Group; 21 | 22 | [FormerlySerializedAs( "ObjectDependencies" )] 23 | [Tooltip( "List of injectable Unity Objects as dependency." )] 24 | [SerializeField] internal List UnityObjectDependencies = new( 8 ); 25 | 26 | [Tooltip( "List of injectable non Unity Object classes as dependency." )] 27 | [SerializeReference] internal List ClassDependencies = new( 8 ); 28 | 29 | [Tooltip( "List of injectable value types (struct) as dependency.\n" + 30 | "Elements added from inspector will be boxed." )] 31 | [SerializeReference] internal List StructDependencies_Serialized = new( 8 ); 32 | 33 | 34 | [NonSerialized] readonly List _structDependencies = new( 8 ); 35 | [NonSerialized] readonly HashSet _dependencyTypes = new( 16, new TypeComparer() ); 36 | [NonSerialized] bool _inited; 37 | [NonSerialized] bool _addedToManager; 38 | 39 | SceneHandle _lastSceneHandle; 40 | Transform _lastParent; 41 | int _lastSiblingIndex; 42 | 43 | #if UNITY_EDITOR 44 | /// 45 | /// When true, will be updated. 46 | /// 47 | [NonSerialized] internal bool IsEditorInspecting_EditorOnly; 48 | #endif 49 | 50 | #if UNITY_EDITOR 51 | void OnValidate() { 52 | if (Application.isPlaying) return; 53 | 54 | // fix broken lists 55 | StringBuilder sb = new( 128 ); 56 | for (int i = 0; i < ClassDependencies.Count; i++) 57 | if (ClassDependencies[i] == null) { 58 | sb.AppendLine( $" - class at {i}: was null" ); 59 | ClassDependencies.RemoveAt( i-- ); 60 | } 61 | 62 | for (int i = 0; i < StructDependencies_Serialized.Count; i++) { 63 | if (StructDependencies_Serialized[i] == null || StructDependencies_Serialized[i].BoxAndGetValue() == null) { 64 | StructDependencies_Serialized.RemoveAt( i-- ); 65 | } 66 | } 67 | 68 | // delete duplicates 69 | for (int i = 0; i < UnityObjectDependencies.Count - 1; i++) 70 | for (int j = i + 1; j < UnityObjectDependencies.Count; j++) 71 | if ((UnityObjectDependencies[i] == null && UnityObjectDependencies[j] == null) || 72 | (UnityObjectDependencies[i].GetType() == UnityObjectDependencies[j].GetType())) 73 | { 74 | sb.AppendLine( $" - Unity Object at [{j}]: duplicate of [{i}]" ); 75 | UnityObjectDependencies.RemoveAt( j-- ); 76 | } 77 | 78 | for (int i = 0; i < ClassDependencies.Count - 1; i++) 79 | for (int j = i + 1; j < ClassDependencies.Count; j++) 80 | if (ClassDependencies[i].GetType() == ClassDependencies[j].GetType()) { 81 | sb.AppendLine( $" - class at [{j}]: duplicate of [{i}]" ); 82 | ClassDependencies.RemoveAt( j-- ); 83 | } 84 | for (int i = 0; i < StructDependencies_Serialized.Count - 1; i++) 85 | for (int j = i + 1; j < StructDependencies_Serialized.Count; j++) 86 | if (StructDependencies_Serialized[i].GetValueType() == StructDependencies_Serialized[j].GetValueType()) { 87 | sb.AppendLine( $" - struct at [{j}]: duplicate of [{i}]" ); 88 | StructDependencies_Serialized.RemoveAt( j-- ); 89 | } 90 | 91 | if (sb.Length > 0) 92 | Debug.LogWarning( $"Binject Context of {name} removed some dependencies:\n{sb}" ); 93 | 94 | // support in-editor injection 95 | InitializeIfHaveNot(); 96 | AddToManagerIfNotAddedTo(); 97 | ReportSceneChangeOrRemoveFromManagerIfPossible(); 98 | ReportTransformHierarchyChangeIfPossible(); 99 | } 100 | 101 | #endif 102 | 103 | void Awake() { 104 | _structDependencies.Clear(); 105 | ApplyAllSerializedStructs(); 106 | InitializeIfHaveNot(); 107 | SaveLastScene(); 108 | SaveLastHierarchyState(); 109 | AddToManagerIfNotAddedTo(); 110 | } 111 | 112 | void OnEnable() => AddToManagerIfNotAddedTo(); 113 | void OnDisable() => RemoveFromManagerIfAddedTo(); 114 | void OnDestroy() => RemoveFromManagerIfAddedTo(); 115 | 116 | void LateUpdate() { 117 | // TODO: come up with a better way of handling scene-change 118 | ReportSceneChangeOrRemoveFromManagerIfPossible(); 119 | ReportTransformHierarchyChangeIfPossible(); 120 | } 121 | 122 | 123 | /// 124 | /// Binds a dependency to this context. If one with the same type already exists, the new one will override 125 | /// the old one. 126 | /// 127 | public void Bind(T dependency) { 128 | if (_dependencyTypes.Add( typeof(T) )) { 129 | // new type 130 | if (typeof(T).IsValueType) { 131 | _structDependencies.Add( new RealValueHolder( dependency ) ); 132 | #if UNITY_EDITOR 133 | if (IsEditorInspecting_EditorOnly) 134 | StructDependencies_Serialized.Add( new BoxedValueHolder( dependency ) ); 135 | #endif 136 | } 137 | else if (IsUnityObjectType( dependency.GetType() )) 138 | UnityObjectDependencies.Add( dependency as UnityEngine.Object ); 139 | else 140 | ClassDependencies.Add( dependency ); 141 | } else { 142 | // override previous of same type 143 | if (typeof(T).IsValueType) { 144 | for (int i = 0; i < _structDependencies.Count; i++) { 145 | if (_structDependencies[i] is RealValueHolder sd) { 146 | sd.Value = dependency; 147 | #if UNITY_EDITOR 148 | if (IsEditorInspecting_EditorOnly) 149 | StructDependencies_Serialized[i].BoxAndSetValue( dependency ); 150 | #endif 151 | break; 152 | } 153 | if (_structDependencies[i].GetValueType() == typeof(T)) { 154 | _structDependencies[i].BoxAndSetValue( dependency ); 155 | #if UNITY_EDITOR 156 | if (IsEditorInspecting_EditorOnly) 157 | StructDependencies_Serialized[i].BoxAndSetValue( dependency ); 158 | #endif 159 | break; 160 | } 161 | } 162 | } 163 | else if (IsUnityObjectType( dependency.GetType() )) { 164 | for (int i = 0; i < UnityObjectDependencies.Count; i++) { 165 | if (UnityObjectDependencies[i].GetType() == dependency.GetType()) { 166 | UnityObjectDependencies[i] = dependency as UnityEngine.Object; 167 | break; 168 | } 169 | } 170 | } else { 171 | for (int i = 0; i < ClassDependencies.Count; i++) { 172 | if (ClassDependencies[i].GetType() == dependency.GetType()) { 173 | ClassDependencies[i] = dependency; 174 | break; 175 | } 176 | } 177 | } 178 | } 179 | } 180 | 181 | /// 182 | /// Unbinds a dependency from this context. 183 | /// 184 | public void Unbind() { 185 | if (_dependencyTypes.Remove( typeof(T) )) { 186 | if (typeof(T).IsValueType) { 187 | for (int i = 0; i < _structDependencies.Count; i++) { 188 | if (_structDependencies[i].GetValueType() == typeof(T)) { 189 | _structDependencies.RemoveAt( i ); 190 | #if UNITY_EDITOR 191 | if (IsEditorInspecting_EditorOnly) 192 | StructDependencies_Serialized.RemoveAt( i ); 193 | #endif 194 | return; 195 | } 196 | } 197 | } 198 | if (IsUnityObjectType( typeof(T) )) { 199 | for (int i = 0; i < UnityObjectDependencies.Count; i++) { 200 | if (UnityObjectDependencies[i].GetType() == typeof(T)) { 201 | UnityObjectDependencies.RemoveAt( i ); 202 | return; 203 | } 204 | } 205 | } else { 206 | for (int i = 0; i < ClassDependencies.Count; i++) { 207 | if (ClassDependencies[i].GetType() == typeof(T)) { 208 | ClassDependencies.RemoveAt( i ); 209 | return; 210 | } 211 | } 212 | } 213 | } 214 | } 215 | 216 | /// 217 | /// Checks if this context has a dependency of type 218 | /// 219 | public bool HasDependency() => _dependencyTypes.Contains( typeof(T) ); 220 | 221 | /// 222 | /// Returns the dependency of type if it exists, otherwise returns default. 223 | /// 224 | public T GetDependency() { 225 | if (HasDependency()) 226 | if (TryFindDependency( out T result )) 227 | return result; 228 | Debug.LogWarning( $"No dependency of type {typeof(T).FullName} found. returning default/null." ); 229 | return default; 230 | } 231 | 232 | /// 233 | /// Without checking if it exists, returns the dependency of type . If not found, returns default. 234 | /// Slightly faster than if you already know that the dependency exists, but 235 | /// using and this method together is slightly slower than a single 236 | /// call. 237 | /// 238 | public T GetDependencyNoCheck() { 239 | if (TryFindDependency( out var result )) 240 | return result; 241 | Debug.LogWarning( $"No dependency of type {typeof(T).FullName} found. returning default/null." ); 242 | return default; 243 | } 244 | 245 | 246 | internal void PopulateSerializedStructs() { 247 | StructDependencies_Serialized.Clear(); 248 | for (int i = 0; i < _structDependencies.Count; i++) 249 | StructDependencies_Serialized.Add( new BoxedValueHolder( _structDependencies[i].BoxAndGetValue() ) ); 250 | } 251 | 252 | 253 | /// 254 | /// updates manager to adapt for the new transform hierarchy change 255 | /// 256 | void ReportTransformHierarchyChangeIfPossible() { 257 | if (transform.GetSiblingIndex() != _lastSiblingIndex || !ReferenceEquals( _lastParent, transform.parent )) { 258 | BinjectManager.UpdateAllRootContextsAndTopmostScene(); 259 | SaveLastHierarchyState(); 260 | } 261 | } 262 | 263 | void SaveLastHierarchyState() { 264 | _lastParent = transform.parent; 265 | _lastSiblingIndex = transform.GetSiblingIndex(); 266 | } 267 | 268 | /// 269 | /// Reports scene change to if it happened, or removes this from it if it has 270 | /// moved to an invalid scene. 271 | /// 272 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 273 | void ReportSceneChangeOrRemoveFromManagerIfPossible() { 274 | var sceneHandle = new SceneHandle( gameObject.scene ); 275 | if (_lastSceneHandle.Value != sceneHandle.Value) { // scene changed 276 | if (sceneHandle.Value == 0 || !gameObject.scene.isLoaded) { // invalid scene 277 | BinjectManager.RemoveContext( this, sceneHandle ); 278 | _addedToManager = false; 279 | } else { 280 | BinjectManager.UpdateContextScene( this, _lastSceneHandle ); 281 | } 282 | SaveLastScene(); 283 | SaveLastHierarchyState(); 284 | } 285 | } 286 | 287 | /// 288 | /// Adds this to if it wasn't added to it already and if it has a valid scene. 289 | /// 290 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 291 | void AddToManagerIfNotAddedTo() { 292 | if (_lastSceneHandle.Value != 0 && gameObject.scene.isLoaded && !_addedToManager) { 293 | BinjectManager.AddContext( this, _lastSceneHandle ); 294 | _addedToManager = true; 295 | } 296 | } 297 | 298 | /// 299 | /// Removes this from if it was added to it. 300 | /// 301 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 302 | void RemoveFromManagerIfAddedTo() { 303 | if (_addedToManager) { 304 | BinjectManager.RemoveContext( this, _lastSceneHandle ); 305 | _addedToManager = false; 306 | } 307 | } 308 | 309 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 310 | void InitializeIfHaveNot() { 311 | if (!_inited) { 312 | SyncAllDependencyTypes( true ); 313 | SaveLastHierarchyState(); 314 | SaveLastScene(); 315 | _inited = true; 316 | } 317 | } 318 | 319 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 320 | void SaveLastScene() => _lastSceneHandle = new( gameObject.scene ); 321 | 322 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 323 | void ApplyAllSerializedStructs() { 324 | for (int i = 0; i < StructDependencies_Serialized.Count; i++) 325 | _structDependencies.Add( StructDependencies_Serialized[i] ); 326 | } 327 | 328 | /// 329 | /// Stores types of all dependencies to . 330 | /// 331 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 332 | void SyncAllDependencyTypes(bool clear) { 333 | if (clear) _dependencyTypes.Clear(); 334 | for (int i = 0; i < ClassDependencies.Count; i++) 335 | _dependencyTypes.Add( ClassDependencies[i].GetType() ); 336 | for (int i = 0; i < UnityObjectDependencies.Count; i++) 337 | _dependencyTypes.Add( UnityObjectDependencies[i].GetType() ); 338 | for (int i = 0; i < _structDependencies.Count; i++) 339 | _dependencyTypes.Add( _structDependencies[i].GetValueType() ); 340 | } 341 | 342 | /// 343 | /// Tries to find the dependency from the given type, from fields , 344 | /// and respectively. 345 | /// (It won't allocate garbage if is a value type.) 346 | /// 347 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 348 | bool TryFindDependency(out T result) { 349 | if (typeof(T).IsValueType) { 350 | for (int i = 0; i < _structDependencies.Count; i++) 351 | if (_structDependencies[i] is RealValueHolder sd) { 352 | result = sd.Value; 353 | return true; 354 | } else if (_structDependencies[i].GetValueType() == typeof(T)) { 355 | result = (T)_structDependencies[i].BoxAndGetValue(); 356 | return true; 357 | } 358 | } 359 | if (IsUnityObjectType( typeof(T) )) { 360 | for (int i = 0; i < UnityObjectDependencies.Count; i++) 361 | if (UnityObjectDependencies[i] is T obj) { 362 | result = obj; 363 | return true; 364 | } 365 | } else { 366 | for (int i = 0; i < ClassDependencies.Count; i++) 367 | if (ClassDependencies[i] is T dat) { 368 | result = dat; 369 | return true; 370 | } 371 | } 372 | 373 | result = default; 374 | return false; 375 | } 376 | 377 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 378 | static bool IsUnityObjectType(Type type) => type.IsSubclassOf( typeof(UnityEngine.Object) ); 379 | } 380 | 381 | 382 | class TypeComparer : IEqualityComparer { 383 | public bool Equals(Type x, Type y) => ReferenceEquals( x, y ); 384 | public int GetHashCode(Type obj) => obj.GetHashCode(); 385 | } 386 | } -------------------------------------------------------------------------------- /BContext.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3474895dbac148c497315f334100a335 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {fileID: 2800000, guid: ad74d0161b00d184194866836650de1c, type: 3} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /BContextList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.CompilerServices; 3 | using UnityEngine; 4 | 5 | namespace Binject { 6 | /// 7 | /// A list specifically designed for . it holds a and sorts 8 | /// contexts by their use count. 9 | /// 10 | public class BContextList : List { 11 | public int RootIndex; 12 | List _points; 13 | HashSet _transforms; 14 | 15 | public BContextList(int capacity) : base( capacity ) => Init(); 16 | 17 | void Init() { 18 | _points = new( Capacity ); 19 | _transforms = new( Capacity, new ObjectReferenceEquality() ); 20 | } 21 | 22 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 23 | public BContext GetRoot() => RootIndex >= Count ? this[0] : this[RootIndex]; 24 | 25 | public new void Add(BContext context) { 26 | base.Add( context ); 27 | _points.Add( 0 ); 28 | _transforms.Add( context.transform ); 29 | } 30 | 31 | public new bool Remove(BContext context) { 32 | _points.RemoveAt( IndexOf( context ) ); 33 | _transforms.Remove( context.transform ); 34 | return base.Remove( context ); 35 | } 36 | 37 | public bool ContainsTransform(Transform transform) => _transforms.Contains( transform ); 38 | 39 | public void AddPoint(int index) { 40 | if (index == 0) return; 41 | _points[index]++; 42 | while (index > 0 && _points[index] > _points[index - 1]) { 43 | (this[index], this[index - 1]) = (this[index - 1], this[index]); 44 | (_points[index], _points[index - 1]) = (_points[index - 1], _points[index]); 45 | index--; 46 | } 47 | } 48 | } 49 | 50 | 51 | class ObjectReferenceEquality : IEqualityComparer { 52 | public bool Equals(T x, T y) => ReferenceEquals( x, y ); 53 | public int GetHashCode(T obj) => obj.GetHashCode(); 54 | } 55 | } -------------------------------------------------------------------------------- /BContextList.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9f9ff691f2944ca8a2072b12da9440d2 3 | timeCreated: 1690564032 -------------------------------------------------------------------------------- /Binject.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Binject", 3 | "rootNamespace": "", 4 | "references": [], 5 | "includePlatforms": [], 6 | "excludePlatforms": [], 7 | "allowUnsafeCode": false, 8 | "overrideReferences": false, 9 | "precompiledReferences": [], 10 | "autoReferenced": true, 11 | "defineConstraints": [], 12 | "versionDefines": [], 13 | "noEngineReferences": false 14 | } -------------------------------------------------------------------------------- /Binject.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 76a13bcc2bd27f5479647970fea773d1 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /BinjectManager.cs: -------------------------------------------------------------------------------- 1 | #if (UNITY_EDITOR || DEVELOPMENT_BUILD || DEBUG) && BINJECT_VERBOSE && !BINJECT_SILENT 2 | #define B_DEBUG 3 | #endif 4 | 5 | #if !BINJECT_SILENT 6 | #define WARN 7 | #endif 8 | 9 | using System; 10 | using UnityEngine; 11 | using System.Linq; 12 | using System.Collections.Generic; 13 | using UnityEngine.SceneManagement; 14 | using System.Runtime.CompilerServices; 15 | 16 | namespace Binject { 17 | [ExecuteAlways] 18 | public static class BinjectManager { 19 | 20 | #if B_DEBUG 21 | static BinjectManager() => Debug.Log( "Binject Domain Reloaded" ); 22 | #endif 23 | 24 | /// 25 | /// Topmost scene handle. can be used to detect the top root context 26 | /// 27 | [NonSerialized] static SceneHandle _topMostScene; 28 | 29 | /// 30 | /// Contexts grouped per scene (key is ). only scenes with at least 1 are 31 | /// contained here; when they reach zero length, they'll be removed from the dictionary altogether. 32 | /// 33 | [NonSerialized] static readonly Dictionary _sceneContexts = new( 4 ); 34 | 35 | /// 36 | /// contexts grouped per . only groups with at least 1 are 37 | /// contained here; when they reach zero length, they'll be removed from the dictionary altogether. 38 | /// 39 | [NonSerialized] static readonly Dictionary _groupedContexts = new( 4 ); 40 | 41 | #region Publics 42 | 43 | /// 44 | /// Returns the root of this scene. It will create new component 45 | /// on this gameObject instead. 46 | /// 47 | public static BContext GetSceneRootContext(Transform transform) { 48 | if (_sceneContexts.TryGetValue( new( transform.gameObject.scene ), out var list )) 49 | return list.GetRoot(); 50 | #if WARN 51 | Debug.LogWarning( $"No context found in scene {transform.gameObject.scene}, Creating a new " + 52 | $"{nameof(BContext)} component on the game object instead.", transform ); 53 | #endif 54 | return transform.gameObject.AddComponent(); 55 | } 56 | 57 | /// 58 | /// Finds the first context in self or it's parent. It'll go to other scenes if didn't find any. if nothing was 59 | /// found, it'll create a new component on the gameObject. 60 | /// 61 | public static BContext FindNearestContext(Transform transform, ushort groupNumber = 0) { 62 | BContextList groupList = null; 63 | if (groupNumber != 0 && !_groupedContexts.TryGetValue( groupNumber, out groupList )) { 64 | goto CreateComponent; 65 | } 66 | 67 | if (_sceneContexts.TryGetValue( new( transform.gameObject.scene ), out var contextsInScene )) { 68 | var originalTransform = transform; 69 | // parents 70 | while (transform is not null) { 71 | for (int i = 0; i < contextsInScene.Count; i++) { 72 | var context = contextsInScene[i]; 73 | if (isCorrectGroup( context, groupNumber ) && ReferenceEquals( transform, context.transform )) { 74 | contextsInScene.AddPoint( i ); 75 | return context; 76 | } 77 | } 78 | 79 | transform = transform.parent; 80 | } 81 | 82 | // scene root 83 | var root = contextsInScene.GetRoot(); 84 | if (isCorrectGroup( root, groupNumber )) 85 | return root; 86 | transform = originalTransform; 87 | } 88 | 89 | // topmost root 90 | var topmostRoot = _sceneContexts[_topMostScene].GetRoot(); 91 | if (isCorrectGroup( topmostRoot, groupNumber )) 92 | return topmostRoot; 93 | 94 | // root of grouped contexts 95 | if (groupList is not null) 96 | return groupList.GetRoot(); 97 | 98 | // create a component 99 | CreateComponent: 100 | #if WARN 101 | Debug.LogWarning( $"No context found with the group {groupNumber}. Creating a new one on the game " + 102 | "object instead", transform ); 103 | #endif 104 | return transform.gameObject.AddComponent(); 105 | } 106 | 107 | /// 108 | /// Finds the compatible context holding the specified dependency. returns null if not found any. 109 | /// 110 | [MethodImpl( MethodImplOptions.AggressiveInlining)] 111 | public static BContext FindContext(Transform transform, ushort groupNumber = 0) { 112 | 113 | var sceneHandle = new SceneHandle( transform.gameObject.scene ); 114 | 115 | // search in scene 116 | if (_sceneContexts.TryGetValue( sceneHandle, out var contextsInScene )) { 117 | 118 | // check parents 119 | while (transform is not null) { 120 | // fast check 121 | if (contextsInScene.ContainsTransform( transform )) { 122 | // find 123 | for (int i = 0; i < contextsInScene.Count; i++) { 124 | var context = contextsInScene[i]; 125 | if (isCorrectGroup( context, groupNumber ) && context.transform == transform ) { 126 | if (context.HasDependency()) { 127 | contextsInScene.AddPoint( i ); 128 | return context; 129 | } 130 | } 131 | } 132 | } 133 | transform = transform.parent; 134 | } 135 | 136 | // check scene root context 137 | var root = contextsInScene.GetRoot(); 138 | if (isCorrectGroup( root, groupNumber ) && root.HasDependency()) 139 | return root; 140 | } 141 | 142 | // check topmost scene root context 143 | if (_topMostScene.Equals( sceneHandle )) { 144 | var root = _sceneContexts[_topMostScene].GetRoot(); 145 | if (isCorrectGroup( root, groupNumber ) && root.HasDependency()) 146 | return root; 147 | } 148 | 149 | // check grouped contexts from any scene 150 | if (_groupedContexts.TryGetValue( groupNumber, out var list ) && list.Count > 0) { 151 | for (int i = 0; i < list.Count; i++) { 152 | var context = list[i]; 153 | if (context.HasDependency()) { 154 | list.AddPoint( i ); 155 | return context; 156 | } 157 | } 158 | } 159 | 160 | #if WARN 161 | Debug.LogWarning( $"No context found containing the dependency type {typeof(T).FullName}" ); 162 | #endif 163 | return null; 164 | } 165 | 166 | #endregion 167 | 168 | #region Non Publics 169 | 170 | /// 171 | /// Adds the context to internal lists and updates caches 172 | /// 173 | internal static void AddContext(BContext context, SceneHandle sceneHandle) { 174 | #if B_DEBUG 175 | Debug.Log( $"adding {context.name}({context.gameObject.scene.name}). all: {CreateStringListOfAllContexts()}", context ); 176 | #endif 177 | // add to lists 178 | if (!_groupedContexts.TryGetValue( context.Group, out var glist )) 179 | _groupedContexts[context.Group] = glist = new( 4 ); 180 | glist.Add( context ); 181 | if (!_sceneContexts.TryGetValue( sceneHandle, out var slist )) 182 | _sceneContexts[sceneHandle] = slist = new( 4 ); 183 | slist.Add( context ); 184 | 185 | UpdateAllRootContextsAndTopmostScene(); 186 | } 187 | 188 | /// 189 | /// Removes the context from internal lists and updates caches 190 | /// 191 | internal static void RemoveContext(BContext context, SceneHandle sceneHandle) { 192 | #if B_DEBUG 193 | Debug.Log( $"removing {(context ? $"{context.name}({context.gameObject.scene.name})" : "null")}. all: {CreateStringListOfAllContexts()}", context ); 194 | #endif 195 | bool changed = false; 196 | 197 | // remove from lists 198 | if (_groupedContexts.TryGetValue( context.Group, out var glist )) { 199 | changed = glist.Remove( context ); 200 | if (changed && glist.Count == 0) 201 | _groupedContexts.Remove( context.Group ); 202 | } 203 | if (_sceneContexts.TryGetValue( sceneHandle, out var slist )) { 204 | changed |= slist.Remove( context ); 205 | if (changed && slist.Count == 0) 206 | _sceneContexts.Remove( sceneHandle ); 207 | } 208 | 209 | if (changed) UpdateAllRootContextsAndTopmostScene(); 210 | } 211 | 212 | /// 213 | /// Updates the internal lists based on the scene change. Will also update all the root contexts. 214 | /// It's an expensive call! 215 | /// 216 | internal static void UpdateContextScene(BContext context, SceneHandle previousScene) { 217 | var sceneHandle = new SceneHandle( context.gameObject.scene ); 218 | if (sceneHandle.Value == previousScene.Value) return; 219 | 220 | #if B_DEBUG 221 | Debug.Log( $"Context {context.name} changed scene handle from {previousScene.Value} to {sceneHandle.Value}" ); 222 | #endif 223 | 224 | // add 225 | if (!_sceneContexts.TryGetValue( sceneHandle, out var list )) 226 | _sceneContexts.Add( sceneHandle, list = new( 8 ) ); 227 | list.Add( context ); 228 | // remove 229 | list = _sceneContexts[previousScene]; 230 | list.Remove( context ); 231 | if (list.Count == 0) _sceneContexts.Remove( previousScene ); 232 | 233 | UpdateAllRootContextsAndTopmostScene(); 234 | } 235 | 236 | #if B_DEBUG 237 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 238 | static string CreateStringListOfAllContexts() => 239 | $"[{string.Join( ", ", _sceneContexts.SelectMany( s => s.Value ).Select( c => c ? $"{c.name}({c.gameObject.scene.name})" : "null" ) )}]"; 240 | #endif 241 | 242 | /// 243 | /// Updates 's roots, 's roots and 244 | /// . 245 | /// 246 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 247 | internal static void UpdateAllRootContextsAndTopmostScene() { 248 | if (_sceneContexts.Count == 0) { 249 | _topMostScene = default; 250 | return; 251 | } 252 | // will be needed 253 | var stack = new Stack( 32 ); 254 | 255 | // scene roots 256 | int rootOrder; 257 | foreach (var contexts in _sceneContexts.Values) { 258 | #if B_DEBUG 259 | bool changed = false; 260 | #endif 261 | rootOrder = CalculateHierarchyOrder( contexts.GetRoot().transform, stack ); 262 | for (int i = 0; i < contexts.Count; i++) { 263 | var order = CalculateHierarchyOrder( contexts[i].transform, stack ); 264 | if (order < rootOrder) { 265 | rootOrder = order; 266 | contexts.RootIndex = i; 267 | #if B_DEBUG 268 | changed = true; 269 | #endif 270 | } 271 | } 272 | 273 | #if B_DEBUG 274 | if (changed) 275 | Debug.Log( $"Root of scene '{contexts[0].gameObject.scene.name}' changed: {contexts.GetRoot().name}({contexts.GetRoot().gameObject.scene.name})" ); 276 | #endif 277 | } 278 | 279 | // group roots (and topmost scene at the same time) 280 | Dictionary sceneOrder = new( SceneManager.sceneCount ); 281 | bool foundTopmostScene = false; 282 | for (int i = 0; i < SceneManager.sceneCount; i++) { 283 | sceneOrder[SceneManager.GetSceneAt( i )] = i; 284 | 285 | // resolving topmost scene right here 286 | var scene = SceneManager.GetSceneAt( i ); 287 | var sceneHandle = new SceneHandle( scene ); 288 | if (!foundTopmostScene && _sceneContexts.ContainsKey( sceneHandle )) { 289 | #if B_DEBUG 290 | if (!_topMostScene.Equals( sceneHandle )) 291 | Debug.Log( $"Topmost scene changed: {scene.name}" ); 292 | #endif 293 | _topMostScene = sceneHandle; 294 | foundTopmostScene = true; 295 | } 296 | } 297 | 298 | foreach (var contexts in _groupedContexts.Values) { 299 | const int SCENE_BENEFIT = 1_000_000; 300 | rootOrder = CalculateHierarchyOrder( contexts.GetRoot().transform, stack ) * sceneOrder[contexts.GetRoot().gameObject.scene] * SCENE_BENEFIT; 301 | #if B_DEBUG 302 | bool changed = false; 303 | #endif 304 | for (int i = 0; i < contexts.Count; i++) { 305 | var order = CalculateHierarchyOrder( contexts[i].transform, stack ) * sceneOrder[contexts[i].gameObject.scene] * SCENE_BENEFIT; 306 | if (order < rootOrder) { 307 | rootOrder = order; 308 | contexts.RootIndex = i; 309 | #if B_DEBUG 310 | changed = true; 311 | #endif 312 | } 313 | } 314 | #if B_DEBUG 315 | if (changed) 316 | Debug.Log( $"Root of group '{contexts[0].Group}' changed: {contexts.GetRoot().name}({contexts.GetRoot().gameObject.scene.name})" ); 317 | #endif 318 | } 319 | } 320 | 321 | /// 322 | /// checks whether or not the is compatible with the given 323 | /// 324 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 325 | static bool isCorrectGroup(BContext context, ushort groupNumber) => groupNumber == 0 || groupNumber == context.Group; 326 | 327 | /// 328 | /// Returns the index of which the transform will show up in hierarchy if everything is expanded. 329 | /// the has to be empty but initialized. 330 | /// 331 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 332 | static int CalculateHierarchyOrder(Transform transform, Stack stack) { 333 | do { 334 | stack.Push( transform ); 335 | transform = transform.parent; 336 | } while (transform is not null); 337 | 338 | int order = 0; 339 | while (stack.Count > 0) 340 | order += stack.Pop().GetSiblingIndex() * 100; 341 | 342 | return order; 343 | } 344 | 345 | #endregion 346 | 347 | #region Public Helpers 348 | 349 | /// 350 | /// Returns the dependency from a compatible context. returns default if not found any. 351 | /// 352 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 353 | public static T GetDependency(Transform transform, ushort groupNumber = 0) { 354 | var context = FindContext( transform, groupNumber ); 355 | return context == null ? default : context.GetDependencyNoCheck(); 356 | } 357 | 358 | /// 359 | /// Finds the dependency from a compatible context and returns `true` if found any, and `false` if didn't. 360 | /// will be default if didn't find any. 361 | /// 362 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 363 | public static bool TryGetDependency(Transform transform, out T result, ushort groupNumber = 0) { 364 | var context = FindContext( transform, groupNumber ); 365 | result = context is null ? default : context.GetDependencyNoCheck(); 366 | return context is not null; 367 | } 368 | 369 | /// 370 | /// Checks if the dependency exists in a compatible context. 371 | /// 372 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 373 | public static bool DependencyExists(Transform transform, ushort groupNumber = 0) { 374 | return FindContext( transform, groupNumber ) != null; 375 | } 376 | 377 | #endregion 378 | 379 | #region Extensions 380 | 381 | /// 382 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 383 | public static T GetDependency(this Component component, ushort groupName = 0) { 384 | return GetDependency( component.transform, groupName ); 385 | } 386 | 387 | /// 388 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 389 | public static bool TryGetDependency(this Component component, out T result, ushort groupNumber = 0) => 390 | TryGetDependency( component.transform, out result, groupNumber ); 391 | 392 | /// 393 | [MethodImpl (MethodImplOptions.AggressiveInlining )] 394 | public static bool DependencyExists(this Component component, ushort groupName = 0) { 395 | return DependencyExists( component.transform, groupName ); 396 | } 397 | 398 | /// 399 | public static BContext FindNearestContext(this Component component, ushort groupNumber = 0) => 400 | FindNearestContext( component.transform, groupNumber ); 401 | 402 | /// 403 | public static BContext FindContext(this Component component, ushort groupNumber = 0) => 404 | FindContext( component.transform, groupNumber ); 405 | 406 | /// 407 | public static BContext GetSceneRootContext(this Component component) => 408 | GetSceneRootContext( component.transform ); 409 | 410 | 411 | #region Multis 412 | 413 | /// 414 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 415 | public static void GetDependency(this Component component, out T1 result1, out T2 result2, ushort groupName = 0) { 416 | result1 = GetDependency( component.transform, groupName ); 417 | result2 = GetDependency( component.transform, groupName ); 418 | } 419 | 420 | /// 421 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 422 | public static void GetDependency(this Component component, out T1 result1, out T2 result2, out T3 result3, ushort groupName = 0) { 423 | result1 = GetDependency( component.transform, groupName ); 424 | result2 = GetDependency( component.transform, groupName ); 425 | result3 = GetDependency( component.transform, groupName ); 426 | } 427 | 428 | /// 429 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 430 | public static void GetDependency(this Component component, out T1 result1, out T2 result2, out T3 result3, out T4 result4, ushort groupName = 0) { 431 | result1 = GetDependency( component.transform, groupName ); 432 | result2 = GetDependency( component.transform, groupName ); 433 | result3 = GetDependency( component.transform, groupName ); 434 | result4 = GetDependency( component.transform, groupName ); 435 | } 436 | 437 | #endregion 438 | 439 | #endregion 440 | } 441 | 442 | 443 | internal readonly struct SceneHandle : IEquatable { 444 | public readonly int Value; 445 | public SceneHandle(Scene scene) => Value = scene.handle; 446 | 447 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 448 | public override int GetHashCode() => Value; 449 | 450 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 451 | public bool Equals(SceneHandle other) => Value == other.Value; 452 | } 453 | 454 | 455 | struct TransformTypeTuple : IEqualityComparer { 456 | public int transformHash; 457 | public Type type; 458 | 459 | public TransformTypeTuple(Transform transform, Type type) { 460 | transformHash = transform.GetHashCode(); 461 | this.type = type; 462 | } 463 | 464 | public bool Equals(TransformTypeTuple x, TransformTypeTuple y) => x.transformHash == y.transformHash && ReferenceEquals( x.type, y.type ); 465 | public int GetHashCode(TransformTypeTuple obj) { 466 | var hash = new HashCode(); 467 | hash.Add( obj.transformHash.GetHashCode() ); 468 | hash.Add( obj.type.GetHashCode() ); 469 | return hash.ToHashCode(); 470 | } 471 | 472 | public override string ToString() => $"({transformHash}, {type})"; 473 | } 474 | } -------------------------------------------------------------------------------- /BinjectManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 18418c1dd7cb49ad94b5206d62ea8fea 3 | timeCreated: 1689507117 -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 83dbcb8754bf4fe3abafc122d65fed99 3 | timeCreated: 1689516541 -------------------------------------------------------------------------------- /Editor/BContextEditor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Runtime.CompilerServices; 5 | using Binject; 6 | using UnityEditor; 7 | using UnityEditor.IMGUI.Controls; 8 | using UnityEditorInternal; 9 | using UnityEngine; 10 | 11 | namespace BinjectEditor { 12 | [CustomEditor( typeof(BContext) )] 13 | internal class BContextEditor : Editor { 14 | SerializedProperty classDependenciesProp; 15 | SerializedProperty structDependenciesProp; 16 | SerializedProperty objectDependenciesProp; 17 | SerializedProperty groupProp; 18 | ReorderableList _classList; 19 | ReorderableList _structList; 20 | ReorderableList _objectList; 21 | bool _advancedFoldout; 22 | BContext _context; 23 | 24 | static GUIStyle _headerStyle; 25 | static GUIContent _objectListHeaderGuiContent; 26 | static GUIContent _classListHeaderGuiContent; 27 | static GUIContent _structListHeaderGuiContent; 28 | 29 | 30 | static TypeSelectDropDown _classTypeDropDown = new( new AdvancedDropdownState(), 31 | filter: type => 32 | true 33 | && !type.IsValueType // avoid boxing/unboxing 34 | && !type.IsSubclassOf( typeof(UnityEngine.Object) ) // Unity Objects won't SerializeReference 35 | && !type.IsAbstract && !type.ContainsGenericParameters && // exclude useless serializing classes 36 | !type.IsSubclassOf( typeof(Attribute) ) 37 | && type.IsSerializable // only serializable 38 | && type.Assembly.GetName().Name != "mscorlib" // mscorlib is too low-level to inject 39 | && type.GetCustomAttribute( typeof(CompilerGeneratedAttribute) ) == null // no compiler-generated classes 40 | && !type.IsSubclassOf( typeof(Exception) ) // no need to inject exceptions 41 | && !type.GetInterfaces().Any( inter => inter.IsSubclassOf( typeof(IDisposable) ) ) // no need to inject disposables 42 | && type.GetConstructors().Any( c => c.GetParameters().Length == 0 ) // construct easily 43 | ); 44 | 45 | static TypeSelectDropDown _structTypeDropDown = new( new AdvancedDropdownState(), 46 | filter: type => 47 | true 48 | && type.IsValueType && !type.IsEnum // only structs 49 | && !type.ContainsGenericParameters // exclude useless serializing types 50 | && type.IsSerializable // only serializable 51 | && type.Assembly.GetName().Name != "mscorlib" // mscorlib is too low-level to inject 52 | && type.GetCustomAttribute( typeof(CompilerGeneratedAttribute) ) == null // no compiler-generated types 53 | && !type.GetInterfaces().Any( inter => inter.IsSubclassOf( typeof(IDisposable) ) ) // no need to inject disposables 54 | ); 55 | 56 | 57 | void SetupObjectList() { 58 | _objectList = new ReorderableList( serializedObject, objectDependenciesProp, 59 | draggable: true, 60 | displayHeader: true, 61 | displayAddButton: !Application.isPlaying, 62 | displayRemoveButton: !Application.isPlaying ); 63 | 64 | _objectList.headerHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; 65 | _objectList.drawHeaderCallback += rect => { 66 | _objectListHeaderGuiContent ??= new GUIContent(objectDependenciesProp.displayName, objectDependenciesProp.tooltip); 67 | GUI.Label( rect, _objectListHeaderGuiContent, _headerStyle ); 68 | }; 69 | 70 | _objectList.elementHeightCallback += index => EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; 71 | _objectList.drawElementCallback += (rect, index, _, _) => { 72 | rect.height = EditorGUIUtility.singleLineHeight; 73 | EditorGUI.PropertyField( rect, objectDependenciesProp.GetArrayElementAtIndex( index ), GUIContent.none, true ); 74 | }; 75 | 76 | _objectList.onAddCallback += _ => { 77 | objectDependenciesProp.arraySize++; 78 | objectDependenciesProp.GetArrayElementAtIndex( objectDependenciesProp.arraySize - 1 ) 79 | .objectReferenceValue = null; 80 | }; 81 | } 82 | 83 | void SetupClassList() { 84 | _classList = new ReorderableList( serializedObject, classDependenciesProp, 85 | draggable: true, 86 | displayHeader: true, 87 | displayAddButton: !Application.isPlaying, 88 | displayRemoveButton: !Application.isPlaying ); 89 | 90 | _classList.headerHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; 91 | _classList.drawHeaderCallback += rect => { 92 | _classListHeaderGuiContent ??= new GUIContent(classDependenciesProp.displayName, classDependenciesProp.tooltip); 93 | GUI.Label( rect, _classListHeaderGuiContent, _headerStyle ); 94 | }; 95 | 96 | _classList.elementHeightCallback += index => { 97 | var prop = classDependenciesProp.GetArrayElementAtIndex( index ); 98 | var headerHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; 99 | if (!prop.isExpanded) return headerHeight; 100 | 101 | var hasCustomEditor = HasCustomEditor( prop ); 102 | if (hasCustomEditor) 103 | return headerHeight + EditorGUI.GetPropertyHeight( prop, true ); 104 | 105 | // else 106 | float bodyHeight = 0; 107 | int d = prop.depth; 108 | prop.Next( true ); 109 | do { 110 | if (prop.depth <= d) break; 111 | bodyHeight += EditorGUI.GetPropertyHeight( prop, true ) + EditorGUIUtility.standardVerticalSpacing; 112 | } while (prop.Next( false )); 113 | 114 | return headerHeight + bodyHeight; 115 | }; 116 | _classList.drawElementCallback += (rect, index, active, focused) => { 117 | var prop = classDependenciesProp.GetArrayElementAtIndex( index ); 118 | // check if has custom editor 119 | rect.height = EditorGUIUtility.singleLineHeight; 120 | 121 | prop.isExpanded = EditorGUI.BeginFoldoutHeaderGroup( 122 | position: rect, 123 | foldout: prop.isExpanded, 124 | content: prop.managedReferenceValue.GetType().ToString(), 125 | menuAction: rect => { 126 | var menu = new GenericMenu(); 127 | menu.AddItem( new GUIContent( "Remove" ), false, () => { 128 | classDependenciesProp.DeleteArrayElementAtIndex( index ); 129 | serializedObject.ApplyModifiedProperties(); 130 | } ); 131 | menu.DropDown( rect ); 132 | } 133 | ); 134 | EditorGUI.EndFoldoutHeaderGroup(); 135 | 136 | EditorGUI.indentLevel++; 137 | 138 | rect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; 139 | if (prop.isExpanded) { 140 | bool hasCustomEditor = HasCustomEditor( prop ); 141 | if (hasCustomEditor) { 142 | EditorGUI.PropertyField( rect, prop, true ); 143 | } else { 144 | int d = prop.depth; 145 | prop.Next( true ); 146 | do { 147 | if (prop.depth <= d) break; 148 | EditorGUI.PropertyField( rect, prop, true ); 149 | rect.y += EditorGUI.GetPropertyHeight( prop, true ) + 150 | EditorGUIUtility.standardVerticalSpacing; 151 | } while (prop.Next( false )); 152 | } 153 | } 154 | EditorGUI.indentLevel--; 155 | }; 156 | 157 | _classList.onAddDropdownCallback += (_, _) => { 158 | _classTypeDropDown.Show( new Rect( Event.current.mousePosition, Vector2.zero ), 159 | onSelect: type => { 160 | if (type == null) return; 161 | var dependency = Activator.CreateInstance( type ); 162 | if (dependency == null) return; 163 | classDependenciesProp.arraySize++; 164 | classDependenciesProp.GetArrayElementAtIndex( classDependenciesProp.arraySize - 1 ) 165 | .managedReferenceValue = dependency; 166 | serializedObject.ApplyModifiedProperties(); 167 | } ); 168 | }; 169 | 170 | } 171 | 172 | void SetupStructList() { 173 | _structList = new ReorderableList( serializedObject, structDependenciesProp, 174 | draggable: true, 175 | displayHeader: true, 176 | displayAddButton: !Application.isPlaying, 177 | displayRemoveButton: !Application.isPlaying ); 178 | 179 | _structList.headerHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; 180 | _structList.drawHeaderCallback += rect => { 181 | _structListHeaderGuiContent ??= new GUIContent( "Struct Dependencies", structDependenciesProp.tooltip); 182 | GUI.Label( rect, _structListHeaderGuiContent, _headerStyle ); 183 | }; 184 | 185 | _structList.elementHeightCallback += index => { 186 | var prop = structDependenciesProp.GetArrayElementAtIndex( index ); 187 | var headerHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; 188 | if (!prop.isExpanded) return headerHeight; 189 | 190 | var hasCustomEditor = HasCustomEditor( prop ); 191 | if (hasCustomEditor) 192 | return headerHeight + EditorGUI.GetPropertyHeight( prop, true ); 193 | 194 | // else 195 | float bodyHeight = 0; 196 | int d = prop.depth; 197 | prop.Next( true ); 198 | if (prop.Next( true )) { 199 | do { 200 | if (prop.depth <= d) break; 201 | bodyHeight += EditorGUI.GetPropertyHeight( prop, true ) + EditorGUIUtility.standardVerticalSpacing; 202 | } while (prop.Next( false )); 203 | } 204 | 205 | return headerHeight + bodyHeight; 206 | }; 207 | 208 | _structList.drawElementCallback += (rect, index, active, focused) => { 209 | var prop = structDependenciesProp.GetArrayElementAtIndex( index ); 210 | // check if has custom editor 211 | rect.height = EditorGUIUtility.singleLineHeight; 212 | 213 | prop.isExpanded = EditorGUI.BeginFoldoutHeaderGroup( 214 | position: rect, 215 | foldout: prop.isExpanded, 216 | content: ((IValueHolder)prop.managedReferenceValue).GetValueType().ToString(), 217 | menuAction: rect => { 218 | var menu = new GenericMenu(); 219 | menu.AddItem( new GUIContent( "Remove" ), false, () => { 220 | structDependenciesProp.DeleteArrayElementAtIndex( index ); 221 | serializedObject.ApplyModifiedProperties(); 222 | } ); 223 | menu.DropDown( rect ); 224 | } 225 | ); 226 | EditorGUI.EndFoldoutHeaderGroup(); 227 | 228 | using (new EditorGUI.DisabledScope( Application.isPlaying )) { 229 | EditorGUI.indentLevel++; 230 | rect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; 231 | if (prop.isExpanded) { 232 | bool hasCustomEditor = HasCustomEditor( prop ); 233 | if (hasCustomEditor) { 234 | EditorGUI.PropertyField( rect, prop, true ); 235 | } else { 236 | int d = prop.depth; 237 | prop.Next( true ); 238 | if (prop.Next( true )) { 239 | do { 240 | if (prop.depth <= d) break; 241 | EditorGUI.PropertyField( rect, prop, true ); 242 | rect.y += EditorGUI.GetPropertyHeight( prop, true ) + 243 | EditorGUIUtility.standardVerticalSpacing; 244 | } while (prop.Next( false )); 245 | } 246 | } 247 | } 248 | EditorGUI.indentLevel--; 249 | } 250 | }; 251 | 252 | _structList.onAddDropdownCallback += (_, _) => { 253 | _structTypeDropDown.Show( new Rect( Event.current.mousePosition, Vector2.zero ), 254 | onSelect: type => { 255 | if (type == null) return; 256 | var dependency = Activator.CreateInstance( type ); 257 | structDependenciesProp.arraySize++; 258 | structDependenciesProp.GetArrayElementAtIndex( structDependenciesProp.arraySize - 1 ) 259 | .managedReferenceValue = new BoxedValueHolder( dependency ); 260 | serializedObject.ApplyModifiedProperties(); 261 | } ); 262 | }; 263 | 264 | } 265 | 266 | static bool HasCustomEditor(SerializedProperty prop) => prop.managedReferenceValue is IBHasCustomDrawer; 267 | 268 | void OnEnable() { 269 | _context = (BContext)target; 270 | _context.PopulateSerializedStructs(); 271 | _context.IsEditorInspecting_EditorOnly = true; 272 | } 273 | 274 | void OnDisable() { 275 | _context.IsEditorInspecting_EditorOnly = false; 276 | } 277 | 278 | public override void OnInspectorGUI() { 279 | if ( classDependenciesProp == null ) { 280 | objectDependenciesProp = serializedObject.FindProperty( nameof(BContext.UnityObjectDependencies) ); 281 | classDependenciesProp = serializedObject.FindProperty( nameof(BContext.ClassDependencies) ); 282 | structDependenciesProp = serializedObject.FindProperty( nameof(BContext.StructDependencies_Serialized) ); 283 | groupProp = serializedObject.FindProperty( nameof(BContext.Group) ); 284 | SetupObjectList(); 285 | SetupClassList(); 286 | SetupStructList(); 287 | } 288 | 289 | InitStylesIfNotAlready(); 290 | serializedObject.Update(); 291 | 292 | _objectList.DoLayoutList(); 293 | _classList.DoLayoutList(); 294 | DrawAdvanced(); 295 | 296 | serializedObject.ApplyModifiedProperties(); 297 | } 298 | 299 | void DrawAdvanced() { 300 | _advancedFoldout = EditorGUILayout.BeginFoldoutHeaderGroup( _advancedFoldout, "Advanced" ); 301 | EditorGUILayout.EndFoldoutHeaderGroup(); 302 | 303 | if (_advancedFoldout) { 304 | // group prop 305 | using (new EditorGUILayout.HorizontalScope()) { 306 | var isGrouped = groupProp.intValue != 0; 307 | using (var check = new EditorGUI.ChangeCheckScope()) { 308 | using (new LabelWidth( 50 )) 309 | isGrouped = EditorGUILayout.Toggle( new GUIContent( "Group", groupProp.tooltip ), isGrouped, 310 | GUILayout.Width( 80 ) ); 311 | if (check.changed) groupProp.intValue = isGrouped ? 1 : 0; 312 | } 313 | 314 | if (isGrouped) { 315 | using (new LabelWidth( 100 )) 316 | EditorGUILayout.PropertyField( groupProp ); 317 | } 318 | } 319 | _structList.DoLayoutList(); 320 | } 321 | 322 | } 323 | 324 | static void InitStylesIfNotAlready() { 325 | if (_headerStyle == null) { 326 | _headerStyle = new GUIStyle( EditorStyles.label ); 327 | _headerStyle.alignment = TextAnchor.MiddleCenter; 328 | } 329 | } 330 | 331 | class LabelWidth : IDisposable { 332 | readonly float w; 333 | public LabelWidth(float width) { 334 | w = EditorGUIUtility.labelWidth; 335 | EditorGUIUtility.labelWidth = width; 336 | } 337 | public void Dispose() => EditorGUIUtility.labelWidth = w; 338 | } 339 | 340 | 341 | class ForceGuiEnable : IDisposable { 342 | readonly bool enabled; 343 | public ForceGuiEnable() { 344 | enabled = GUI.enabled; 345 | GUI.enabled = true; 346 | } 347 | public void Dispose() => GUI.enabled = enabled; 348 | } 349 | } 350 | } -------------------------------------------------------------------------------- /Editor/BContextEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ddcdb13b947c443b9762823e0d2a6854 3 | timeCreated: 1689516560 -------------------------------------------------------------------------------- /Editor/BinjectEditor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BinjectEditor", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:76a13bcc2bd27f5479647970fea773d1" 6 | ], 7 | "includePlatforms": [ 8 | "Editor" 9 | ], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [], 16 | "versionDefines": [], 17 | "noEngineReferences": false 18 | } -------------------------------------------------------------------------------- /Editor/BinjectEditor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b54b3df686382c3488da5c530caf730f 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/TypeSelectDropDown.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using UnityEditor; 5 | using UnityEditor.IMGUI.Controls; 6 | using UnityEngine; 7 | 8 | 9 | internal sealed class TypeSelectDropDown : AdvancedDropdown { 10 | 11 | Action _onSelect; 12 | Func _filter; 13 | AddingItem root; 14 | string longestName; 15 | 16 | class AddingItem { 17 | public string name; 18 | public List children; 19 | public AddingItem(string name) => this.name = name; 20 | } 21 | 22 | public TypeSelectDropDown(AdvancedDropdownState state, Func filter) : base( state ) { 23 | _filter = filter; 24 | 25 | root = new AddingItem( "Assemblies" ) { children = new() }; 26 | longestName = string.Empty; 27 | foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { 28 | AddingItem assItem = null; 29 | foreach (var type in assembly.GetTypes().Where( _filter )) { 30 | AddingItem item = new( type.FullName ); 31 | assItem ??= new( assembly.GetName().Name ) { children = new() }; 32 | assItem.children.Add( item ); 33 | if (type.FullName.Length > longestName.Length) 34 | longestName = type.FullName; 35 | } 36 | 37 | if (assItem != null) root.children.Add( assItem ); 38 | } 39 | } 40 | 41 | public void Show(Rect position, Action onSelect) { 42 | minimumSize = new Vector2( EditorStyles.label.CalcSize( new GUIContent( longestName ) ).x, minimumSize.y ); 43 | base.Show( position ); 44 | _onSelect = onSelect; 45 | } 46 | 47 | protected override AdvancedDropdownItem BuildRoot() { 48 | var rootItem = new AdvancedDropdownItem( root.name ); 49 | AddChildrenRecursively( rootItem, root ); 50 | return rootItem; 51 | } 52 | 53 | void AddChildrenRecursively(AdvancedDropdownItem parentItem, AddingItem parent) { 54 | for (int i = 0; i < parent.children?.Count; i++) { 55 | var childItem = new AdvancedDropdownItem( parent.children[i].name ); 56 | parentItem.AddChild( childItem ); 57 | if (childItem.children != null) 58 | AddChildrenRecursively( childItem, parent.children[i] ); 59 | } 60 | } 61 | 62 | protected override void ItemSelected(AdvancedDropdownItem item) { 63 | base.ItemSelected( item ); 64 | var type = AppDomain.CurrentDomain.GetAssemblies() 65 | .SelectMany( a => a.GetTypes() ) 66 | .FirstOrDefault( t => t.FullName == item.name ); 67 | _onSelect?.Invoke( type ); 68 | } 69 | } -------------------------------------------------------------------------------- /Editor/TypeSelectDropDown.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 36fb4b04768c4662a775acf703128095 3 | timeCreated: 1689691228 -------------------------------------------------------------------------------- /Editor/UnmanagedUtility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace BinjectEditor { 7 | 8 | public static class UnmanagedUtility { 9 | 10 | static Dictionary cachedTypes = new( 64 ); 11 | 12 | /// 13 | /// Detects whether or not a type is unmanaged 14 | /// 15 | public static bool IsUnManaged(this Type type) { 16 | bool result; 17 | if (cachedTypes.TryGetValue( type, out bool value )) return value; 18 | if (type.IsPrimitive || type.IsPointer || type.IsEnum) 19 | result = true; 20 | else if (type.IsGenericType || !type.IsValueType) 21 | result = false; 22 | else 23 | result = type 24 | .GetFields( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ) 25 | .All( x => x.FieldType.IsUnManaged() ); 26 | cachedTypes.Add( type, result ); 27 | return result; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Editor/UnmanagedUtility.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e1295e9a493845cd98b5671c88a304a0 3 | timeCreated: 1689770963 -------------------------------------------------------------------------------- /Interfaces.cs: -------------------------------------------------------------------------------- 1 | namespace Binject { 2 | 3 | /// 4 | /// Marks the implementing class as one that has a custom editor to be drawn with. It'll only work with types that 5 | /// are not inherited from . 6 | /// 7 | public interface IBHasCustomDrawer { } 8 | 9 | } -------------------------------------------------------------------------------- /Interfaces.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c3290e07ad1d4293a87ecde4de0738b4 3 | timeCreated: 1689506340 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Saeed Barari 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c940ac60f42197e4a9e46f6b32feb966 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Binject 2 | ### Better Dependency Injection System for Unity. 3 | 4 | ## ✨ What is Binject and Why use it? 5 | Binject is a dependency injection system for Unity. It's a simple and easy to use DI system that allows you to easily inject any type of dependencies into MonoBehaviours. It's advantages to other DI systems are: 6 | - It's intuitive and little learning curve. 7 | - It's more performant and lightweight. 8 | - Supports all types as dependencies. 9 | 10 | Your work as a developer will essentially be much easier once you use this in your games. Imagine not having to drag and drop a component for each consumer every time. Imagine not having to initialize fields of Instantiated objects one by one. 11 | 12 | ## ⚒️ Installation 13 | ### UPM 14 | You can install this as a unity package by following the instructions at [the official manual](https://docs.unity3d.com/Manual/upm-ui-giturl.html). 15 | > The package url is `https://github.com/somedeveloper00/Binject.git` 16 | ### Git Submodule 17 | clone the repository into your project's Assets/Plugins folder as a submodule: 18 | ```bash 19 | git submodule add --force https://github.com/somedeveloper00/Binject/Assets/Plugins/Binject 20 | git submodule update Assets/Plugins/Binject 21 | ``` 22 | or if you don't have git, simply download the zip and extract it into your project's Assets/Plugins folder: 23 | > Linux / MacOS 24 | > ``` 25 | > wget https://github.com/somedeveloper00/Binject/archive/refs/heads/main.zip -O Binject.zip 26 | > unzip Binject.zip -d Assets/Plugins 27 | > rm UnityTodo.zip 28 | 29 | > Windows 30 | > ``` 31 | > mkdir Assets\Plugins 32 | > curl -L -o UnityTodo.zip https://github.com/somedeveloper00/Binject/archive/main.zip 33 | > tar -xf Binject.zip -C Assets/Plugins 34 | > del UnityTodo.zip 35 | > ``` 36 | 37 | ## ✒️ Example Code: 38 | ### Injection 39 | ```csharp 40 | public class Movement : MonoBehaviour { 41 | Rigidbody _rigidbody; 42 | GroundedCheck _groundedCheck; 43 | Living _living; 44 | public void Awake() => this.GetDependency(out _living, out _groundedCheck, out _rigidbody); 45 | } 46 | ``` 47 | ### Binding 48 | ```csharp 49 | var context = this.FindNearestContext(); 50 | context.Bind( _rigidBody ); 51 | ``` 52 | 53 | ## 📋Technical Notes 54 | * You can use Binject just fine in both **editor** and **runtime**. 55 | * Binject works better in build than in inspector, because it sometimes has to do extra work in inspector to keep the inspector up to date, if you have a `Binject Context` component open in inspector. 56 | * There have been hours and days of research dedicated to **minimize garbage allocation**, and as the result, Binject allocates no garbage for dealing with value types, and minimum garbage when dealing with reference types. The performance is enough to do **thousands** of injections in a single frame without any performance drop. 57 | 58 | ## 💡Usage 59 | ### Contexts (Binject Context or BContext) 60 | There are `Binject Context` components that you can add to your scenes, these are the entry points for dependencies to inject from. 61 | ![img.png](Res/img.png) 62 | 63 | The list **Unity Object Dependencies** is used for Unity Objects (derived from `UnityEngine.Object`) and the list **Class Dependencies** is used for any other serializable class. That the dependencies can be from any assembly, so long as they are publicly available and serializable. 64 | 65 | > _**Advanced:**_ 66 | > Under the advanced menus in a `Binject Context` component, you can use value types (structs) as dependencies. You can add value type dependencies right from inspector. But be careful, any value type added from inspector will be boxed and unboxed every time it's used. So it's recommended to add value type dependencies from code. 67 | > ![img_4.png](Res/img_4.png) 68 | > This list is also useful for debugging the value typed dependencies at runtime; you can inspect and browse the values of the dependencies that are added from either inspector or code, but you won't be able to edit them. 69 | > ![img_5.png](Res/img_5.png) 70 | > The advantage of value typed dependencies is it's performance and that it **allocates no garbage** whatsoever. Which, as explained above, is not the case for value typed dependencies added from inspector. The performance for value typed dependencies added from code is well above 5 times better than reference typed dependencies, and you can easily inject up to above 2000 of them in every frame under 1.5 milliseconds. 71 | 72 | 73 | ### Injection 74 | 75 | There are various ways you can inject a dependency, and all of them are done solely by the consumer (you); nothing is automated because that would be less performant. Here's the thing with this library: **the priority number one is always performance**. While the consumer has to do one or two calls, the performance is much better than other DI systems. You can easily ask for an injection in any `MonoBehaviour` code by the following syntax: 76 | ```csharp 77 | value = this.GetDependency(); 78 | ``` 79 | If you have multiple dependencies, you can use another syntax that retrieves multiple dependencies in a single call: 80 | ```csharp 81 | this.GetDependency(out value1, out value2, out value3); 82 | ``` 83 | Another way to inject dependencies is through using a `BContext` reference; if you have a `BContext` object called `context`, you can inject dependencies from it like this: 84 | ```csharp 85 | if (context.HasDependency()) { 86 | value = context.GetDependencyNoCheck(); 87 | } 88 | ``` 89 | Or in a single call: 90 | ```csharp 91 | value = context.GetDependency(); 92 | ``` 93 | You can skip `HasDependency` check if you're sure that the dependency exists in the context. That will be faster. 94 | You can't use the multi-injection syntax by using `BContext` directly. It's worth noting that using a `BContext` directly is generally more performant, but it'll take away the ability to let the Binject system decide what context to give to you; Which you can always do so by: 95 | ```csharp 96 | context = this.FindContext(); 97 | ``` 98 | which will give you the context that contains that type, using performant internal search methods. Or if you just want to get the context that is nearest to your component, you can do 99 | ```csharp 100 | context = this.FindNearestContext(); 101 | ``` 102 | > _**Advanced:**_ 103 | > Contexts support grouping by number; you can assign group numbers to contexts to then retrieve them by number. Useful when transform hierarchy is not enough to determine the dependency. (i.e., finding player for UI) 104 | > ![img_3.png](Res/img_3.png) 105 | > To use a group number for injection, you can use the following syntax: 106 | > ```csharp 107 | > value = this.GetDependency( groupNumber ); 108 | > ``` 109 | > Same for any other injection functions. (i.e., `FindContext`, `FindNearestContext`, etc.) 110 | 111 | ### Binding 112 | You can bind dependencies to a context either from inspector or by code using the following syntax: 113 | ```csharp 114 | context.Bind( dependency ); 115 | ``` 116 | And you can unbind the dependency using: 117 | ```csharp 118 | context.Unbind( dependency ); 119 | ``` 120 | 121 | ### More About Context Search 122 | Internally, contexts are searched by different factors and priorities; it's good to know them. The search is done in the following pseudocode: 123 | 1. ✅ If self or a parent has context with given type and given group number, return it. 124 | 2. ✅ If scene root Context has given type and given group number, return it. 125 | 3. ✅ If topmost scene root Context has given type and given group number, return it. 126 | 4. ✅ If any context has given type and given group number, return it. 127 | 5. ❌ Give up. 128 | 129 | > _**Advanced:**_ 130 | > A **scene root Context** is a context that sits on the top-most level of the hierarchy, compared to other contexts within the scene. 131 | > A **topmost scene root Context** is a scene root Context that belongs to the topmost scene in the hierarchy. 132 | 133 | ## 🤝 Contribute 134 | Feel free to open issues and pull requests. There are no strict rules for contributing, but please try to follow the coding style of the project. If you want to contact me, you can find my email in my profile. 135 | 136 | ## 🙌 Support 137 | If you like my work, you can buy me a coffee to keep me motivated to work on this project and other projects like this. 138 | My wallet addresses are: 139 | * BTC: `bc1q808ykgvhn2ewtx09n3kdhnlmcnc6xqwxa0hnys` 140 | * ETH: `0xCe7028266Cf3eF0E63437d0604511e30f8e4B4Af` 141 | * LTC: `ltc1qlu2jahcdr5kqf5dp9xt3zr3cv66gm2p8hmnz9j` 142 | 143 | ## 📝 License 144 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 145 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0ca2af67d61cdc34f9c851429603c368 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /RealValueHolder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using UnityEngine; 4 | 5 | namespace Binject { 6 | 7 | /// 8 | /// Types implementing this will be responsible for saving a data inside and provide 9 | /// external access to it. This interface makes sure a can use any 10 | /// structs. 11 | /// 12 | internal interface IValueHolder { 13 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 14 | public Type GetValueType(); 15 | 16 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 17 | public object BoxAndGetValue(); 18 | 19 | [MethodImpl( MethodImplOptions.AggressiveInlining )] 20 | public void BoxAndSetValue(object value); 21 | } 22 | 23 | /// 24 | /// Any data stored in this will be boxed, but it's useful for showing the data inside in Unity Editor. 25 | /// 26 | [Serializable] 27 | internal class BoxedValueHolder : IValueHolder { 28 | [SerializeReference] public object Value; 29 | 30 | public BoxedValueHolder(object value) => Value = value; 31 | public Type GetValueType() => Value.GetType(); 32 | public object BoxAndGetValue() => Value; 33 | public void BoxAndSetValue(object value) => Value = value; 34 | } 35 | 36 | /// 37 | /// This will store real data and provides direct access to it without boxing. 38 | /// 39 | [Serializable] 40 | internal class RealValueHolder : IValueHolder { 41 | public T Value; 42 | public RealValueHolder(T value) => Value = value; 43 | public Type GetValueType() => typeof(T); 44 | public object BoxAndGetValue() => Value; 45 | public void BoxAndSetValue(object value) => Value = (T)value; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /RealValueHolder.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7465fc893d951694ebfe4cd5fcd29bc8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Res.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b9ee02713f69fd34db00548cd21b36e0 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Res/BContext-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/somedeveloper00/Binject/47541b7fbb010d9ac3cb16b1182b6672112cf700/Res/BContext-icon.png -------------------------------------------------------------------------------- /Res/BContext-icon.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ad74d0161b00d184194866836650de1c 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 12 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | flipGreenChannel: 0 24 | isReadable: 0 25 | streamingMipmaps: 0 26 | streamingMipmapsPriority: 0 27 | vTOnly: 0 28 | ignoreMipmapLimit: 0 29 | grayScaleToAlpha: 0 30 | generateCubemap: 6 31 | cubemapConvolution: 0 32 | seamlessCubemap: 0 33 | textureFormat: 1 34 | maxTextureSize: 2048 35 | textureSettings: 36 | serializedVersion: 2 37 | filterMode: 1 38 | aniso: 1 39 | mipBias: 0 40 | wrapU: 1 41 | wrapV: 1 42 | wrapW: 0 43 | nPOTScale: 0 44 | lightmap: 0 45 | compressionQuality: 50 46 | spriteMode: 0 47 | spriteExtrude: 1 48 | spriteMeshType: 1 49 | alignment: 0 50 | spritePivot: {x: 0.5, y: 0.5} 51 | spritePixelsToUnits: 100 52 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 53 | spriteGenerateFallbackPhysicsShape: 1 54 | alphaUsage: 1 55 | alphaIsTransparency: 1 56 | spriteTessellationDetail: -1 57 | textureType: 2 58 | textureShape: 1 59 | singleChannelComponent: 0 60 | flipbookRows: 1 61 | flipbookColumns: 1 62 | maxTextureSizeSet: 0 63 | compressionQualitySet: 0 64 | textureFormatSet: 0 65 | ignorePngGamma: 0 66 | applyGammaDecoding: 0 67 | swizzle: 50462976 68 | cookieLightType: 0 69 | platformSettings: 70 | - serializedVersion: 3 71 | buildTarget: DefaultTexturePlatform 72 | maxTextureSize: 2048 73 | resizeAlgorithm: 0 74 | textureFormat: -1 75 | textureCompression: 1 76 | compressionQuality: 50 77 | crunchedCompression: 0 78 | allowsAlphaSplitting: 0 79 | overridden: 0 80 | ignorePlatformSupport: 0 81 | androidETC2FallbackOverride: 0 82 | forceMaximumCompressionQuality_BC6H_BC7: 0 83 | - serializedVersion: 3 84 | buildTarget: Standalone 85 | maxTextureSize: 2048 86 | resizeAlgorithm: 0 87 | textureFormat: -1 88 | textureCompression: 1 89 | compressionQuality: 50 90 | crunchedCompression: 0 91 | allowsAlphaSplitting: 0 92 | overridden: 0 93 | ignorePlatformSupport: 0 94 | androidETC2FallbackOverride: 0 95 | forceMaximumCompressionQuality_BC6H_BC7: 0 96 | - serializedVersion: 3 97 | buildTarget: Server 98 | maxTextureSize: 2048 99 | resizeAlgorithm: 0 100 | textureFormat: -1 101 | textureCompression: 1 102 | compressionQuality: 50 103 | crunchedCompression: 0 104 | allowsAlphaSplitting: 0 105 | overridden: 0 106 | ignorePlatformSupport: 0 107 | androidETC2FallbackOverride: 0 108 | forceMaximumCompressionQuality_BC6H_BC7: 0 109 | - serializedVersion: 3 110 | buildTarget: Android 111 | maxTextureSize: 2048 112 | resizeAlgorithm: 0 113 | textureFormat: -1 114 | textureCompression: 1 115 | compressionQuality: 50 116 | crunchedCompression: 0 117 | allowsAlphaSplitting: 0 118 | overridden: 0 119 | ignorePlatformSupport: 0 120 | androidETC2FallbackOverride: 0 121 | forceMaximumCompressionQuality_BC6H_BC7: 0 122 | spriteSheet: 123 | serializedVersion: 2 124 | sprites: [] 125 | outline: [] 126 | physicsShape: [] 127 | bones: [] 128 | spriteID: 129 | internalID: 0 130 | vertices: [] 131 | indices: 132 | edges: [] 133 | weights: [] 134 | secondaryTextures: [] 135 | nameFileIdTable: {} 136 | mipmapLimitGroupName: 137 | pSDRemoveMatte: 0 138 | userData: 139 | assetBundleName: 140 | assetBundleVariant: 141 | -------------------------------------------------------------------------------- /Res/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/somedeveloper00/Binject/47541b7fbb010d9ac3cb16b1182b6672112cf700/Res/img.png -------------------------------------------------------------------------------- /Res/img.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 41c60859d41b4ec2abb493e79bdc11ca 3 | timeCreated: 1690107951 -------------------------------------------------------------------------------- /Res/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/somedeveloper00/Binject/47541b7fbb010d9ac3cb16b1182b6672112cf700/Res/img_1.png -------------------------------------------------------------------------------- /Res/img_1.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e64144cde16b4fa19c7bb546e7284b88 3 | timeCreated: 1690108303 -------------------------------------------------------------------------------- /Res/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/somedeveloper00/Binject/47541b7fbb010d9ac3cb16b1182b6672112cf700/Res/img_2.png -------------------------------------------------------------------------------- /Res/img_2.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: da5e8e6a05654274b1f65aae7d9120a6 3 | timeCreated: 1690109003 -------------------------------------------------------------------------------- /Res/img_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/somedeveloper00/Binject/47541b7fbb010d9ac3cb16b1182b6672112cf700/Res/img_3.png -------------------------------------------------------------------------------- /Res/img_3.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fbf530f0d88f409a8f24c991ab807a41 3 | timeCreated: 1690109063 -------------------------------------------------------------------------------- /Res/img_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/somedeveloper00/Binject/47541b7fbb010d9ac3cb16b1182b6672112cf700/Res/img_4.png -------------------------------------------------------------------------------- /Res/img_4.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e4f49ad8a4c64ef387893e94ce85b4a4 3 | timeCreated: 1690109136 -------------------------------------------------------------------------------- /Res/img_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/somedeveloper00/Binject/47541b7fbb010d9ac3cb16b1182b6672112cf700/Res/img_5.png -------------------------------------------------------------------------------- /Res/img_5.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d1101f911c44461aa4261f18b1836882 3 | timeCreated: 1690109328 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.somedeveloper.binject", 3 | "version": "0.1.2", 4 | "displayName": "Binject", 5 | "description": "Better injection system in Unity", 6 | "unity": "2022.3", 7 | "unityRelease": "22f3", 8 | "documentationUrl": "https://github.com/somedeveloper00/Binject", 9 | "licensesUrl": "https://github.com/somedeveloper00/Binject/LICENSE.md", 10 | "keywords": [ 11 | "binject", 12 | "inject", 13 | "injection", 14 | "dependency", 15 | "di", 16 | "zenject" 17 | ], 18 | "author": { 19 | "name": "SomeDeveloper", 20 | "email": "barari.saeed.am@gmail.com", 21 | "url": "https://github.com/somedeveloper00" 22 | }, 23 | "hideInEditor": false 24 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9b42cc63b02982c47ab58f6d24556bb4 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------