Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
Store.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.IO;
6 using System.Runtime.InteropServices;
7 using System.Threading.Tasks;
8 using SiliconStudio.Core.Serialization;
9 
10 #if SILICONSTUDIO_PLATFORM_WINDOWS_DESKTOP
11 using Microsoft.Win32.SafeHandles;
12 #endif
13 
14 namespace SiliconStudio.Core.IO
15 {
16  class Store
17  {
18 #if SILICONSTUDIO_PLATFORM_WINDOWS_DESKTOP
19  [StructLayout(LayoutKind.Sequential)]
20  public struct OVERLAPPED
21  {
22  public uint internalLow;
23  public uint internalHigh;
24  public uint offsetLow;
25  public uint offsetHigh;
26  public IntPtr hEvent;
27  }
28 
29  [DllImport("Kernel32.dll", SetLastError = true)]
30  public static extern bool LockFileEx(SafeFileHandle handle, uint flags, uint reserved, uint countLow, uint countHigh, ref OVERLAPPED overlapped);
31 
32  [DllImport("Kernel32.dll", SetLastError = true)]
33  public static extern bool UnlockFileEx(SafeFileHandle handle, uint reserved, uint countLow, uint countHigh, ref OVERLAPPED overlapped);
34 
35  public const uint LOCKFILE_EXCLUSIVE_LOCK = 0x00000002;
36 #endif
37  }
38 
39  /// <summary>
40  /// A store that will be incrementally saved on the HDD.
41  /// Thread-safe and process-safe.
42  /// </summary>
43  /// <typeparam name="T">The type of elements in the store.</typeparam>
44  public abstract class Store<T> : IDisposable where T : new()
45  {
46  protected Stream stream;
47 
48  protected int transaction;
49  protected object lockObject = new object();
50 
51  /// <summary>
52  /// Gets or sets a flag specifying if the index map changes should be kept aside instead of being committed immediately.
53  /// </summary>
54  public bool UseTransaction { get; set; }
55 
56  /// <summary>
57  /// Gets or sets a flag specifying if a Save should also load new values that happened in between.
58  /// </summary>
59  public bool AutoLoadNewValues { get; set; }
60 
61  protected Store(Stream stream)
62  {
63  AutoLoadNewValues = true;
64  this.stream = stream;
65  }
66 
67  /// <summary>
68  /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
69  /// Waits for pending operation to finish, if any. Note that it does not write pending transaction if <see cref="Save"/> has not been called before.
70  /// </summary>
71  public void Dispose()
72  {
73  if (stream != null)
74  {
75  lock (stream)
76  {
77  stream.Dispose();
78  stream = null;
79  }
80  }
81  }
82 
83  /// <summary>
84  /// Adds multiple values to the store
85  /// </summary>
86  /// <param name="values">The values.</param>
87  /// <returns></returns>
88  public void AddValues(IEnumerable<T> values)
89  {
90  lock (lockObject)
91  {
92  var useTransaction = UseTransaction;
93  int currentTransaction = transaction;
94  if (!useTransaction)
95  transaction++;
96 
97  foreach (var value in values)
98  {
99  // Use unsavedIdMap so that loadedIdMap is still coherent before flushed to disk asynchronously (since other processes/threads might write to it as well).
100  AddUnsaved(value, currentTransaction);
101  }
102 
103  if (!useTransaction)
104  {
105  SaveValues(values, currentTransaction);
106  }
107  }
108  }
109 
110  /// <summary>
111  /// Adds a value to the store.
112  /// </summary>
113  /// <param name="item">The item.</param>
114  /// <returns></returns>
115  public void AddValue(T item)
116  {
117  lock (lockObject)
118  {
119  var useTransaction = UseTransaction;
120  int currentTransaction = transaction;
121  if (!useTransaction)
122  transaction++;
123 
124  // Use unsavedIdMap so that loadedIdMap is still coherent before flushed to disk asynchronously (since other processes/threads might write to it as well).
125  AddUnsaved(item, currentTransaction);
126 
127  if (!useTransaction)
128  {
129  SaveValue(item, currentTransaction);
130  }
131  }
132  }
133 
134  private void SaveValues(IEnumerable<T> values, int currentTransaction)
135  {
136  if (stream == null)
137  throw new InvalidOperationException("No active stream.");
138 
139  lock (stream)
140  {
141  var indexStreamPosition = stream.Position;
142 
143  // Acquire lock on end of file (for appending)
144  // This will prevent another thread from writing at the same time, or reading before it is flushed.
145  LockFile(indexStreamPosition, long.MaxValue, true);
146 
147  try
148  {
149  // Make sure we read up entries up to end of file (or skip it if AutoLoadNewValues is not set)
150  if (AutoLoadNewValues)
151  RefreshData(stream.Length);
152  else
153  stream.Position = stream.Length;
154 
155  foreach (var value in values)
156  {
157  WriteEntry(stream, value);
158  }
159  stream.Flush();
160 
161  // Transfer from temporary mapping to real mapping (so that loadedIdMap is updated in right order)
162  lock (lockObject)
163  {
164  RemoveUnsaved(values, currentTransaction);
165  foreach (var value in values)
166  {
167  AddLoaded(value);
168  }
169  }
170  }
171  finally
172  {
173  UnlockFile(indexStreamPosition, long.MaxValue);
174  }
175  }
176  }
177 
178  private void SaveValue(T item, int currentTransaction)
179  {
180  if (stream == null)
181  throw new InvalidOperationException("No active stream.");
182 
183  lock (stream)
184  {
185  var indexStreamPosition = stream.Position;
186 
187  // Acquire lock on end of file (for appending)
188  // This will prevent another thread from writing at the same time, or reading before it is flushed.
189  LockFile(indexStreamPosition, long.MaxValue, true);
190 
191  try
192  {
193  // Make sure we read up entries up to end of file (or skip it if AutoLoadNewValues is not set)
194  if (AutoLoadNewValues)
195  RefreshData(stream.Length);
196  else
197  stream.Position = stream.Length;
198 
199  WriteEntry(stream, item);
200  stream.Flush();
201 
202  // Transfer from temporary mapping to real mapping (so that loadedIdMap is updated in right order)
203  lock (lockObject)
204  {
205  RemoveUnsaved(item, currentTransaction);
206  AddLoaded(item);
207  }
208  }
209  finally
210  {
211  UnlockFile(indexStreamPosition, long.MaxValue);
212  }
213  }
214  }
215 
216  /// <summary>
217  /// Saves the newly added mapping (only necessary when UseTransaction is set to true).
218  /// </summary>
219  /// <param name="saveNow">if set to <c>true</c> save now the map without using a task.</param>
220  public void Save()
221  {
222  lock (lockObject)
223  {
224  int currentTransaction = transaction++;
225  var transactionIds = GetPendingItems(currentTransaction);
226 
227  SaveValues(transactionIds, currentTransaction);
228 
229 
230  }
231  }
232 
233  /// <summary>
234  /// Resets the store to an empty state.
235  /// </summary>
236  public void Reset()
237  {
238  lock (stream)
239  {
240  lock (lockObject)
241  {
242  stream.Position = 0;
243  stream.SetLength(0);
244  ResetInternal();
245  }
246  }
247  }
248 
249  /// <summary>
250  /// Resets the store to an empty state, to be implemented by subclasses if necessary.
251  /// </summary>
252  protected virtual void ResetInternal()
253  {
254  }
255 
256  /// <summary>
257  /// Refreshes URL to ObjectId mapping from the latest results in the index file.
258  /// </summary>
259  /// <returns></returns>
260  public bool LoadNewValues()
261  {
262  if (stream == null)
263  throw new InvalidOperationException("No active stream.");
264 
265  lock (stream)
266  {
267  var position = stream.Position;
268  var fileSize = stream.Length;
269 
270  if (position == fileSize)
271  return true;
272 
273  // Lock content that will be read.
274  // This lock doesn't prevent concurrent writing since we lock only until current known filesize.
275  // In the case where fileSize was taken at the time of an incomplete append, this lock will also wait for completion of the last write.
276  // Note: Maybe we should release the lock quickly so that two threads can read at the same time?
277  // Or if the previously described case doesn't happen, maybe no lock at all is required?
278  // Otherwise, last possibility would be deterministic filesize (with size encoded at the beginning of each block).
279 #if !SILICONSTUDIO_PLATFORM_WINDOWS_RUNTIME
280  if (stream is FileStream)
281  LockFile(position, long.MaxValue, false);
282 #endif
283 
284  try
285  {
286  // update the size after the lock
287  fileSize = stream.Length;
288  RefreshData(fileSize);
289  }
290  finally
291  {
292 #if !SILICONSTUDIO_PLATFORM_WINDOWS_RUNTIME
293  // Release the lock
294  if (stream is FileStream)
295  UnlockFile(position, long.MaxValue);
296 #endif
297  }
298 
299  return true;
300  }
301  }
302 
303  private void LockFile(long offset, long count, bool exclusive)
304  {
305 #if !SILICONSTUDIO_PLATFORM_WINDOWS_RUNTIME && !SILICONSTUDIO_PLATFORM_MONO_MOBILE
306  var fileStream = (FileStream)stream;
307 
308 #if SILICONSTUDIO_PLATFORM_WINDOWS_DESKTOP
309  var countLow = (uint)count;
310  var countHigh = (uint)(count >> 32);
311 
312  var overlapped = new Store.OVERLAPPED()
313  {
314  internalLow = 0,
315  internalHigh = 0,
316  offsetLow = (uint)offset,
317  offsetHigh = (uint)(offset >> 32),
318  hEvent = IntPtr.Zero,
319  };
320 
321  if (!Store.LockFileEx(fileStream.SafeFileHandle, exclusive ? Store.LOCKFILE_EXCLUSIVE_LOCK : 0, 0, countLow, countHigh, ref overlapped))
322  {
323  throw new IOException("Couldn't lock file.");
324  }
325 #else
326  bool tryAgain;
327  do
328  {
329  tryAgain = false;
330  try
331  {
332  fileStream.Lock(offset, count);
333  }
334  catch (IOException)
335  {
336  tryAgain = true;
337  }
338  } while (tryAgain);
339 #endif
340 #endif
341  }
342 
343  private void UnlockFile(long offset, long count)
344  {
345 #if !SILICONSTUDIO_PLATFORM_WINDOWS_RUNTIME && !SILICONSTUDIO_PLATFORM_MONO_MOBILE
346  var fileStream = (FileStream)stream;
347 
348 #if SILICONSTUDIO_PLATFORM_WINDOWS_DESKTOP
349  var countLow = (uint)count;
350  var countHigh = (uint)(count >> 32);
351 
352  var overlapped = new Store.OVERLAPPED()
353  {
354  internalLow = 0,
355  internalHigh = 0,
356  offsetLow = (uint)offset,
357  offsetHigh = (uint)(offset >> 32),
358  hEvent = IntPtr.Zero,
359  };
360 
361  if (!Store.UnlockFileEx(fileStream.SafeFileHandle, 0, countLow, countHigh, ref overlapped))
362  {
363  throw new IOException("Couldn't unlock file.");
364  }
365 #else
366  fileStream.Unlock(offset, count);
367 #endif
368 #endif
369  }
370 
371  private void RefreshData(long fileSize)
372  {
373  var streamBeginPosition = stream.Position;
374 
375  // Precache everything in a MemoryStream
376  var length = (int)(fileSize - stream.Position);
377  var bufferToRead = new byte[length];
378  stream.Read(bufferToRead, 0, length);
379  var memoryStream = new MemoryStream(bufferToRead);
380 
381  try
382  {
383  var entries = ReadEntries(memoryStream);
384 
385  lock (lockObject)
386  {
387  foreach (var entry in entries)
388  {
389  AddLoaded(entry);
390  }
391  }
392  }
393  catch
394  {
395  // If there was an exception, go back to previous position
396  stream.Position = streamBeginPosition;
397  throw;
398  }
399  }
400 
401  /// <summary>
402  /// Adds a value that has not yet been saved in the store (pending state).
403  /// </summary>
404  /// <param name="item">The item.</param>
405  /// <param name="transaction">The transaction index.</param>
406  protected abstract void AddUnsaved(T item, int transaction);
407 
408  /// <summary>
409  /// Removes a value that has not yet been saved (pending state).
410  /// </summary>
411  /// <param name="item">The item.</param>
412  /// <param name="transaction">The transaction index.</param>
413  protected abstract void RemoveUnsaved(T item, int transaction);
414 
415  /// <summary>
416  /// Removes values that have not yet been saved (pending state).
417  /// </summary>
418  /// <param name="items">The items.</param>
419  /// <param name="transaction">The transaction index.</param>
420  protected virtual void RemoveUnsaved(IEnumerable<T> items, int transaction)
421  {
422  foreach (var item in items)
423  {
424  RemoveUnsaved(item, transaction);
425  }
426  }
427 
428  /// <summary>
429  /// Adds a value that has already been saved in the store (saved state).
430  /// </summary>
431  /// <param name="item">The item.</param>
432  protected abstract void AddLoaded(T item);
433 
434  /// <summary>
435  /// Gets the list of pending items for a given transaction index.
436  /// </summary>
437  /// <param name="transaction">The transaction index.</param>
438  /// <returns></returns>
439  protected abstract IEnumerable<T> GetPendingItems(int transaction);
440 
441 
442  protected virtual object BuildContext(Stream stream)
443  {
444  return stream;
445  }
446 
447  protected virtual List<T> ReadEntries(Stream localStream)
448  {
449  var reader = new BinarySerializationReader(localStream);
450  var entries = new List<T>();
451  while (localStream.Position < localStream.Length)
452  {
453  var entry = new T();
454  reader.Serialize(ref entry, ArchiveMode.Deserialize);
455  entries.Add(entry);
456  }
457  return entries;
458  }
459 
460  protected virtual void WriteEntry(Stream stream, T value)
461  {
462  var reader = new BinarySerializationWriter(stream);
463  reader.Serialize(ref value, ArchiveMode.Serialize);
464  }
465  }
466 }
Uncompressed storage
virtual void WriteEntry(Stream stream, T value)
Definition: Store.cs:460
_In_ size_t _In_ DXGI_FORMAT _In_ size_t _In_ DXGI_FORMAT _In_ DWORD flags
Definition: DirectXTexP.h:170
virtual List< T > ReadEntries(Stream localStream)
Definition: Store.cs:447
Implements SerializationStream as a binary writer.
_In_ size_t count
Definition: DirectXTexP.h:174
bool LoadNewValues()
Refreshes URL to ObjectId mapping from the latest results in the index file.
Definition: Store.cs:260
Implements SerializationStream as a binary reader.
void AddValue(T item)
Adds a value to the store.
Definition: Store.cs:115
void Reset()
Resets the store to an empty state.
Definition: Store.cs:236
void AddValues(IEnumerable< T > values)
Adds multiple values to the store
Definition: Store.cs:88
void Dispose()
Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resourc...
Definition: Store.cs:71
virtual void ResetInternal()
Resets the store to an empty state, to be implemented by subclasses if necessary. ...
Definition: Store.cs:252
virtual void RemoveUnsaved(IEnumerable< T > items, int transaction)
Removes values that have not yet been saved (pending state).
Definition: Store.cs:420
void Save()
Saves the newly added mapping (only necessary when UseTransaction is set to true).
Definition: Store.cs:220
virtual object BuildContext(Stream stream)
Definition: Store.cs:442