Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
PackageAssetCollection.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;
5 using System.Collections.Generic;
6 using System.Collections.Specialized;
7 using System.Diagnostics;
8 using System.Threading;
9 using SiliconStudio.Core;
10 using SiliconStudio.Core.Diagnostics;
11 using SiliconStudio.Core.IO;
12 
13 namespace SiliconStudio.Assets
14 {
15  /// <summary>
16  /// A collection of <see cref="AssetItem"/> that contains only absolute location without any drive information. This class cannot be inherited.
17  /// </summary>
18  [DebuggerTypeProxy(typeof(CollectionDebugView))]
19  [DebuggerDisplay("Count = {Count}")]
21  {
22  private readonly Package package;
23  private object syncRoot;
24 
25  // Maps Asset.Location to Asset.Id
26  private readonly Dictionary<string, Guid> mapPathToId;
27 
28  // Maps Asset.Id to Asset.Location
29  private readonly Dictionary<Guid, string> mapIdToPath;
30 
31  // Maps Id to AssetItem
32  private readonly Dictionary<Guid, AssetItem> mapIdToAsset;
33 
34  // All registered items
35  private readonly HashSet<AssetItem> registeredItems;
36 
37  private bool collectionChangedSuspended;
38 
39  /// <summary>
40  /// Occurs when the collection changes.
41  /// </summary>
42  public event NotifyCollectionChangedEventHandler CollectionChanged;
43 
44  /// <summary>
45  /// Initializes a new instance of the <see cref="PackageAssetCollection" /> class.
46  /// </summary>
47  /// <param name="package">The package that will contain assets.</param>
49  {
50  if (package == null) throw new ArgumentNullException("package");
51  this.package = package;
52  mapPathToId = new Dictionary<string, Guid>();
53  mapIdToPath = new Dictionary<Guid, string>();
54  mapIdToAsset = new Dictionary<Guid, AssetItem>();
55  registeredItems = new HashSet<AssetItem>(new ReferenceEqualityComparer<AssetItem>());
56  }
57 
58  /// <summary>
59  /// Gets the package this collection is attached to.
60  /// </summary>
61  /// <value>The package.</value>
62  public Package Package
63  {
64  get
65  {
66  return package;
67  }
68  }
69 
70  /// <summary>
71  /// Gets or sets a value indicating whether this instance is dirty. Sets this flag when moving assets between packages
72  /// or removing assets.
73  /// </summary>
74  /// <value><c>true</c> if this instance is dirty; otherwise, <c>false</c>.</value>
75  public bool IsDirty { get; set; }
76 
77  /// <summary>
78  /// Determines whether this instance contains an asset with the specified identifier.
79  /// </summary>
80  /// <param name="assetId">The asset identifier.</param>
81  /// <returns><c>true</c> if this instance contains an asset with the specified identifier; otherwise, <c>false</c>.</returns>
82  public bool ContainsById(Guid assetId)
83  {
84  return mapIdToAsset.ContainsKey(assetId);
85  }
86 
87  /// <summary>
88  /// Finds an asset by its location.
89  /// </summary>
90  /// <param name="location">The location of the assets.</param>
91  /// <returns>AssetItem.</returns>
92  public AssetItem Find(string location)
93  {
94  Guid id;
95  if (!mapPathToId.TryGetValue(location, out id))
96  {
97  return null;
98  }
99  return Find(id);
100  }
101 
102  /// <summary>
103  /// Finds an asset by its location.
104  /// </summary>
105  /// <param name="assetId">The asset unique identifier.</param>
106  /// <returns>AssetItem.</returns>
107  public AssetItem Find(Guid assetId)
108  {
109  AssetItem value;
110  mapIdToAsset.TryGetValue(assetId, out value);
111  return value;
112  }
113 
114  /// <summary>
115  /// Adds an <see cref="AssetItem"/> to this instance.
116  /// </summary>
117  /// <param name="item">The item to add to this instance.</param>
118  public void Add(AssetItem item)
119  {
120  // Item is already added. Just skip it
121  if (registeredItems.Contains(item))
122  {
123  return;
124  }
125 
126  // Verify item integrity
127  CheckCanAdd(item);
128 
129  // Set the parent of the item to the package
130  item.Package = package;
131 
132  // Add the item
133  var asset = item.Asset;
134  asset.IsIdLocked = true;
135 
136  // Maintain all internal maps
137  mapPathToId.Add(item.Location, item.Id);
138  mapIdToPath.Add(item.Id, item.Location);
139  mapIdToAsset.Add(item.Id, item);
140  registeredItems.Add(item);
141 
142  // Handle notification - insert
143  if (!collectionChangedSuspended)
144  {
145  var handler = CollectionChanged;
146  if (handler != null)
147  {
148  handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
149  }
150  }
151  }
152 
153  /// <summary>
154  /// Removes all items from this instance.
155  /// </summary>
156  public void Clear()
157  {
158  // Clean parent
159  foreach (var registeredItem in registeredItems)
160  {
161  RemoveInternal(registeredItem);
162  }
163 
164  // Unregister all items / clear all internal maps
165  registeredItems.Clear();
166  mapIdToPath.Clear();
167  mapPathToId.Clear();
168  mapIdToAsset.Clear();
169 
170  // Handle notification - clear items
171  if (!collectionChangedSuspended)
172  {
173  var handler = CollectionChanged;
174  if (handler != null)
175  {
176  handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
177  }
178  }
179  }
180 
181  /// <summary>
182  /// Checks this collection contains the specified asset reference, throws an exception if not found.
183  /// </summary>
184  /// <param name="assetItem">The asset item.</param>
185  /// <exception cref="System.ArgumentNullException">assetItem</exception>
186  /// <exception cref="System.Collections.Generic.KeyNotFoundException">Asset [{0}] was not found.ToFormat(assetItem)</exception>
187  public bool Contains(AssetItem assetItem)
188  {
189  if (assetItem == null) throw new ArgumentNullException("assetItem");
190  return registeredItems.Contains(assetItem);
191  }
192 
193  /// <summary>
194  /// Copies items to the specified array.
195  /// </summary>
196  /// <param name="array">The array.</param>
197  /// <param name="arrayIndex">Index of the array.</param>
198  public void CopyTo(AssetItem[] array, int arrayIndex)
199  {
200  registeredItems.CopyTo(array, arrayIndex);
201  }
202 
203  /// <summary>
204  /// Removes an <see cref="AssetItem"/> from this instance.
205  /// </summary>
206  /// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
207  /// <returns>true if <paramref name="item" /> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1" />; otherwise, false. This method also returns false if <paramref name="item" /> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1" />.</returns>
208  public bool Remove(AssetItem item)
209  {
210  if (registeredItems.Remove(item))
211  {
212  // Remove from all internal maps
213  registeredItems.Remove(item);
214  mapIdToAsset.Remove(item.Id);
215  mapIdToPath.Remove(item.Id);
216  mapPathToId.Remove(item.Location);
217 
218  RemoveInternal(item);
219 
220  // Handle notification - replace
221  if (!collectionChangedSuspended)
222  {
223  var handler = CollectionChanged;
224  if (handler != null)
225  {
226  handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
227  }
228  }
229  return true;
230  }
231  return false;
232  }
233 
234  /// <summary>
235  /// Removes an <see cref="AssetItem" /> from this instance.
236  /// </summary>
237  /// <param name="itemId">The item identifier.</param>
238  /// <returns>true if <paramref name="itemId" /> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1" />; otherwise, false. This method also returns false if <paramref name="item" /> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1" />.</returns>
239  public bool RemoveById(Guid itemId)
240  {
241  var item = Find(itemId);
242  if (item == null)
243  {
244  return false;
245  }
246  return Remove(item);
247  }
248 
249  /// <summary>
250  /// Suspends the collection changed that can happen on this collection.
251  /// </summary>
253  {
254  collectionChangedSuspended = true;
255  }
256 
257  /// <summary>
258  /// Resumes the collection changed that happened on this collection and fire a <see cref="NotifyCollectionChangedAction.Reset"/>
259  /// </summary>
261  {
262  collectionChangedSuspended = false;
263 
264  // Handle notification - clear items
265  var handler = CollectionChanged;
266  if (handler != null)
267  {
268  handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
269  }
270  }
271 
272  private void RemoveInternal(AssetItem item)
273  {
274  item.Package = null;
275  item.Asset.IsIdLocked = false;
276  }
277 
278  /// <summary>
279  /// Gets the number of elements contained in this instance.
280  /// </summary>
281  /// <returns>The number of elements contained in this instance.</returns>
282  public int Count
283  {
284  get
285  {
286  return registeredItems.Count;
287  }
288  }
289 
290  /// <summary>
291  /// Gets a value indicating whether this collection is read-only. Default is false.
292  /// </summary>
293  /// <returns>false</returns>
294  public bool IsReadOnly
295  {
296  get
297  {
298  return false;
299  }
300  }
301 
302  /// <summary>
303  /// Checks if the specified item can be add to this collection.
304  /// </summary>
305  /// <param name="item">The item.</param>
306  /// <exception cref="System.ArgumentNullException">
307  /// item;Cannot add an empty asset item reference
308  /// or
309  /// item;Cannot add an item with an empty asset
310  /// or
311  /// item;Cannot add an asset with an empty Id
312  /// or
313  /// item;Location cannot be null when adding an asset reference
314  /// </exception>
315  /// <exception cref="System.ArgumentException">
316  /// An asset with the same location is already registered [{0}].ToFormat(location.Path);item
317  /// or
318  /// An asset with the same id [{0}] is already registered with the location [{1}].ToFormat(item.Id, location.Path);item
319  /// or
320  /// Asset location [{0}] cannot contain drive information.ToFormat(location);item
321  /// or
322  /// Asset location [{0}] must be relative and not absolute (not start with '/').ToFormat(location);item
323  /// or
324  /// Asset location [{0}] cannot start with relative '..'.ToFormat(location);item
325  /// </exception>
326  public void CheckCanAdd(AssetItem item)
327  {
328  // TODO better handle interaction
329  if (item == null)
330  {
331  throw new ArgumentNullException("item", "Cannot add an empty asset item reference");
332  }
333 
334  if (registeredItems.Contains(item))
335  {
336  throw new ArgumentException("Asset already exist in this collection", "item");
337  }
338 
339  if (item.Id == Guid.Empty)
340  {
341  throw new ArgumentException("Cannot add an asset with an empty Id", "item");
342  }
343 
344  if (item.Package != null && item.Package != package)
345  {
346  throw new ArgumentException("Cannot add an asset that is already added to another package", "item");
347  }
348 
349  var location = item.Location;
350  if (mapPathToId.ContainsKey(location))
351  {
352  throw new ArgumentException("An asset [{0}] with the same location [{1}] is already registered ".ToFormat(mapPathToId[location], location.GetDirectoryAndFileName()), "item");
353  }
354 
355  if (!string.IsNullOrEmpty(location.GetFileExtension()))
356  {
357  throw new ArgumentException("An asset [{0}] cannot be registered with an extension [{1}]".ToFormat(location, location.GetFileExtension()), "item");
358  }
359 
360  if (mapIdToPath.ContainsKey(item.Id))
361  {
362  throw new ArgumentException("An asset with the same id [{0}] is already registered with the location [{1}]".ToFormat(item.Id, location.GetDirectoryAndFileName()), "item");
363  }
364 
365  if (location.HasDrive)
366  {
367  throw new ArgumentException("Asset location [{0}] cannot contain drive information".ToFormat(location), "item");
368  }
369 
370  if (location.IsAbsolute)
371  {
372  throw new ArgumentException("Asset location [{0}] must be relative and not absolute (not start with '/')".ToFormat(location), "item");
373  }
374 
375  if (location.GetDirectory() != null && location.GetDirectory().StartsWith(".."))
376  {
377  throw new ArgumentException("Asset location [{0}] cannot start with relative '..'".ToFormat(location), "item");
378  }
379 
380  // Double check that this asset is not already stored in another package for this session
381  if (Package.Session != null)
382  {
383  foreach (var otherPackage in Package.Session.Packages)
384  {
385  if (otherPackage != Package)
386  {
387  if (otherPackage.Assets.ContainsById(item.Id))
388  {
389  throw new ArgumentException("Cannot add the asset [{0}] that is already in different package [{1}] in the current session".ToFormat(item.Id, otherPackage.Id));
390  }
391  }
392  }
393  }
394  }
395 
396  public IEnumerator<AssetItem> GetEnumerator()
397  {
398  return registeredItems.GetEnumerator();
399  }
400 
401  IEnumerator IEnumerable.GetEnumerator()
402  {
403  return GetEnumerator();
404  }
405 
406 
407  void ICollection.CopyTo(Array array, int index)
408  {
409  foreach (var item in this)
410  {
411  array.SetValue(item, index++);
412  }
413  }
414 
415  object ICollection.SyncRoot
416  {
417  get
418  {
419  if (syncRoot == null)
420  Interlocked.CompareExchange<object>(ref syncRoot, new object(), (object)null);
421  return this.syncRoot;
422  }
423  }
424 
425  bool ICollection.IsSynchronized
426  {
427  get
428  {
429  return false;
430  }
431  }
432  }
433 }
bool Contains(AssetItem assetItem)
Checks this collection contains the specified asset reference, throws an exception if not found...
void Add(AssetItem item)
Adds an AssetItem to this instance.
void SuspendCollectionChanged()
Suspends the collection changed that can happen on this collection.
void CheckCanAdd(AssetItem item)
Checks if the specified item can be add to this collection.
Guid Id
Gets the unique identifier of this asset.
Definition: AssetItem.cs:85
PackageAssetCollection(Package package)
Initializes a new instance of the PackageAssetCollection class.
PackageCollection Packages
Gets the packages.
bool ContainsById(Guid assetId)
Determines whether this instance contains an asset with the specified identifier. ...
void Clear()
Removes all items from this instance.
A collection of AssetItem that contains only absolute location without any drive information. This class cannot be inherited.
An asset item part of a Package accessible through SiliconStudio.Assets.Package.Assets.
Definition: AssetItem.cs:17
bool Remove(AssetItem item)
Removes an AssetItem from this instance.
Package Package
Gets the package where this asset is stored.
Definition: AssetItem.cs:97
void CopyTo(AssetItem[] array, int arrayIndex)
Copies items to the specified array.
PackageSession Session
Gets the session.
Definition: Package.cs:219
void ResumeCollectionChanged()
Resumes the collection changed that happened on this collection and fire a NotifyCollectionChangedAct...
AssetItem Find(string location)
Finds an asset by its location.
Use this class to provide a debug output in Visual Studio debugger.
AssetItem Find(Guid assetId)
Finds an asset by its location.
NotifyCollectionChangedEventHandler CollectionChanged
Occurs when the collection changes.
A package managing assets.
Definition: Package.cs:28
bool RemoveById(Guid itemId)
Removes an AssetItem from this instance.
An interface that tags if an object is dirty.
Definition: IDirtyable.cs:8