Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
AssetManager.ReferenceCounting.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 using System.Text;
7 using System.Threading.Tasks;
8 
9 namespace SiliconStudio.Core.Serialization.Assets
10 {
11  partial class AssetManager
12  {
13  // Used internally for Garbage Collection
14  // Allocate once and reuse collection for every GC
15  private Stack<AssetReference> stack = new Stack<AssetReference>();
16  private uint nextCollectIndex;
17 
18  /// <summary>
19  /// Increments reference count of an <see cref="AssetReference"/>.
20  /// </summary>
21  /// <param name="assetReference"></param>
22  /// <param name="publicReference"></param>
23  internal void IncrementReference(AssetReference assetReference, bool publicReference)
24  {
25  if (publicReference)
26  {
27  assetReference.PublicReferenceCount++;
28  }
29  else
30  {
31  assetReference.PrivateReferenceCount++;
32  }
33  }
34 
35  /// <summary>
36  /// Decrements reference count of an <see cref="AssetReference"/>.
37  /// </summary>
38  /// <param name="assetReference"></param>
39  /// <param name="publicReference"></param>
40  internal void DecrementReference(AssetReference assetReference, bool publicReference)
41  {
42  int referenceCount;
43  if (publicReference)
44  {
45  referenceCount = --assetReference.PublicReferenceCount;
46  if (referenceCount < 0)
47  throw new InvalidOperationException("Cannot release an object that doesn't have active public references. Load/Unload pairs must match.");
48 
49  referenceCount += assetReference.PrivateReferenceCount;
50  }
51  else
52  {
53  referenceCount = --assetReference.PrivateReferenceCount;
54  if (referenceCount < 0)
55  throw new InvalidOperationException("Cannot release an object that doesn't have active private references. This is either due to non-matching Load/Unload pairs or an engine internal error.");
56 
57  referenceCount += assetReference.PublicReferenceCount;
58  }
59 
60  if (referenceCount == 0)
61  {
62  // Free the object itself
63  ReleaseAsset(assetReference);
64 
65  // Free all its referenced objects
66  foreach (var reference in assetReference.References)
67  {
68  DecrementReference(reference, false);
69  }
70  }
71  else if (publicReference && assetReference.PublicReferenceCount == 0)
72  {
73  // If there is no more public reference but object is still alive, let's kick a cycle GC
74  CollectUnreferencedCycles();
75  }
76  }
77 
78  /// <summary>
79  /// Releases an asset.
80  /// </summary>
81  /// <param name="assetReference">The asset reference.</param>
82  private void ReleaseAsset(AssetReference assetReference)
83  {
84  var referencable = assetReference.Object as IReferencable;
85  if (referencable != null)
86  {
87  referencable.Release();
88  }
89  else
90  {
91  var disposable = assetReference.Object as IDisposable;
92  if (disposable != null)
93  {
94  disposable.Dispose();
95  }
96  }
97 
98  // Remove AssetReference from loaded assets.
99  var oldPrev = assetReference.Prev;
100  var oldNext = assetReference.Next;
101  if (oldPrev != null)
102  oldPrev.Next = oldNext;
103  if (oldNext != null)
104  oldNext.Prev = oldPrev;
105 
106  if (oldPrev == null)
107  {
108  if (oldNext == null)
109  loadedAssetsByUrl.Remove(assetReference.ObjectId);
110  else
111  loadedAssetsByUrl[assetReference.ObjectId] = oldNext;
112  }
113  loadedAssetsUrl.Remove(assetReference.Object);
114 
115  assetReference.Object = null;
116  }
117 
118  internal void CollectUnreferencedCycles()
119  {
120  // Push everything on the stack
121  var currentCollectIndex = nextCollectIndex++;
122  foreach (var asset in loadedAssetsByUrl)
123  {
124  var currentAsset = asset.Value;
125  do
126  {
127  if (asset.Value.PublicReferenceCount > 0)
128  stack.Push(asset.Value);
129  currentAsset = currentAsset.Next;
130  }
131  while (currentAsset != null);
132  }
133 
134  // Until stack is empty, collect references and push them on the stack
135  while (stack.Count > 0)
136  {
137  var v = stack.Pop();
138 
139  // We use CollectIndex to know if object has already been processed during current collection
140  var collectIndex = v.CollectIndex;
141  if (collectIndex != currentCollectIndex)
142  {
143  v.CollectIndex = currentCollectIndex;
144  foreach (var reference in v.References)
145  {
146  if (reference.CollectIndex != currentCollectIndex)
147  stack.Push(reference);
148  }
149  }
150  }
151 
152  // Collect objects that are not referenceable.
153  // Reuse stack
154  // TODO: Use collections where you can iterate and remove at the same time?
155  foreach (var asset in loadedAssetsByUrl)
156  {
157  var currentAsset = asset.Value;
158  do
159  {
160  if (asset.Value.CollectIndex != currentCollectIndex)
161  {
162  stack.Push(asset.Value);
163  }
164  currentAsset = currentAsset.Next;
165  }
166  while (currentAsset != null);
167  }
168 
169  // Release those objects
170  // Note: order of release might be unexpected (i.e. if A ref B, B might be released before A)
171  // We don't really have a choice if there is cycle anyway, but still user could have reference himself to prevent or enforce this order if it's really important.
172  foreach (var assetReference in stack)
173  {
174  ReleaseAsset(assetReference);
175  }
176  }
177  }
178 }