add all
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
using Best.HTTP.Shared.Extensions;
|
||||
using Best.HTTP.Shared.PlatformSupport.Memory;
|
||||
using Best.HTTP.Shared.PlatformSupport.Threading;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Best.HTTP.Shared.Databases
|
||||
{
|
||||
public sealed class FolderAndFileOptions
|
||||
{
|
||||
public string FolderName = "Best.HTTP.Shared.Databases";
|
||||
public string DatabaseFolderName = "Databases";
|
||||
public string MetadataExtension = "metadata";
|
||||
public string DatabaseExtension = "db";
|
||||
public string DatabaseFreeListExtension = "freelist";
|
||||
public string HashExtension = "hash";
|
||||
}
|
||||
|
||||
public abstract class Database<ContentType, MetadataType, IndexingServiceType, MetadataServiceType> : IDisposable, IHeartbeat
|
||||
where MetadataType : Metadata, new()
|
||||
where IndexingServiceType : IndexingService<ContentType, MetadataType>
|
||||
where MetadataServiceType : MetadataService<MetadataType, ContentType>
|
||||
{
|
||||
public static FolderAndFileOptions FolderAndFileOptions = new FolderAndFileOptions();
|
||||
|
||||
public string SaveDir { get; private set; }
|
||||
public string Name { get { return this.Options.Name; } }
|
||||
|
||||
public string MetadataFileName { get { return Path.ChangeExtension(Path.Combine(this.SaveDir, this.Name), FolderAndFileOptions.MetadataExtension); } }
|
||||
public string DatabaseFileName { get { return Path.ChangeExtension(Path.Combine(this.SaveDir, this.Name), FolderAndFileOptions.DatabaseExtension); } }
|
||||
public string DatabaseFreeListFileName { get { return Path.ChangeExtension(Path.Combine(this.SaveDir, this.Name), FolderAndFileOptions.DatabaseFreeListExtension); } }
|
||||
public string HashFileName { get { return Path.ChangeExtension(Path.Combine(this.SaveDir, this.Name), FolderAndFileOptions.HashExtension); } }
|
||||
|
||||
public MetadataServiceType MetadataService { get; private set; }
|
||||
|
||||
protected DatabaseOptions Options { get; private set; }
|
||||
protected IndexingServiceType IndexingService { get; private set; }
|
||||
protected DiskManager<ContentType> DiskManager { get; private set; }
|
||||
|
||||
protected int isDirty = 0;
|
||||
|
||||
protected ReaderWriterLockSlim rwlock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
|
||||
|
||||
public Database(string directory,
|
||||
DatabaseOptions options,
|
||||
IndexingServiceType indexingService,
|
||||
IDiskContentParser<ContentType> diskContentParser,
|
||||
MetadataServiceType metadataService)
|
||||
{
|
||||
this.SaveDir = directory;
|
||||
this.Options = options;
|
||||
this.IndexingService = indexingService;
|
||||
this.MetadataService = metadataService;
|
||||
|
||||
var dir = Path.GetDirectoryName(this.DatabaseFileName);
|
||||
if (!HTTPManager.IOService.DirectoryExists(dir))
|
||||
HTTPManager.IOService.DirectoryCreate(dir);
|
||||
|
||||
this.DiskManager = new DiskManager<ContentType>(
|
||||
HTTPManager.IOService.CreateFileStream(this.DatabaseFileName, Best.HTTP.Shared.PlatformSupport.FileSystem.FileStreamModes.OpenReadWrite),
|
||||
HTTPManager.IOService.CreateFileStream(this.DatabaseFreeListFileName, Best.HTTP.Shared.PlatformSupport.FileSystem.FileStreamModes.OpenReadWrite),
|
||||
diskContentParser,
|
||||
options.DiskManager);
|
||||
|
||||
using (var fileStream = HTTPManager.IOService.CreateFileStream(this.MetadataFileName, Best.HTTP.Shared.PlatformSupport.FileSystem.FileStreamModes.OpenReadWrite))
|
||||
using (var stream = new BufferedStream(fileStream))
|
||||
this.MetadataService.LoadFrom(stream);
|
||||
}
|
||||
|
||||
public int Clear()
|
||||
{
|
||||
using (new WriteLock(this.rwlock))
|
||||
{
|
||||
int count = this.MetadataService.Metadatas.Count;
|
||||
|
||||
this.IndexingService.Clear();
|
||||
this.DiskManager.Clear();
|
||||
this.MetadataService.Clear();
|
||||
FlagDirty(1);
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public int Delete(IEnumerable<MetadataType> metadatas)
|
||||
{
|
||||
if (metadatas == null)
|
||||
return 0;
|
||||
|
||||
using (new WriteLock(this.rwlock))
|
||||
{
|
||||
int deletedCount = 0;
|
||||
foreach (var metadata in metadatas)
|
||||
if (DeleteMetadata(metadata))
|
||||
deletedCount++;
|
||||
|
||||
FlagDirty(deletedCount);
|
||||
|
||||
return deletedCount;
|
||||
}
|
||||
}
|
||||
|
||||
public int Delete(IEnumerable<int> metadataIndexes)
|
||||
{
|
||||
if (metadataIndexes == null)
|
||||
return 0;
|
||||
|
||||
using (new WriteLock(this.rwlock))
|
||||
{
|
||||
int deletedCount = 0;
|
||||
foreach (int idx in metadataIndexes)
|
||||
{
|
||||
var metadata = this.MetadataService.Metadatas[idx];
|
||||
|
||||
if (DeleteMetadata(metadata))
|
||||
deletedCount++;
|
||||
}
|
||||
|
||||
FlagDirty(deletedCount);
|
||||
|
||||
return deletedCount;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool DeleteMetadata(MetadataType metadata)
|
||||
{
|
||||
if (metadata.Length > 0)
|
||||
this.DiskManager.Delete(metadata);
|
||||
this.MetadataService.Remove(metadata);
|
||||
FlagDirty(1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the first content from the metadata indexes.
|
||||
/// </summary>
|
||||
public ContentType FromFirstMetadataIndex(IEnumerable<int> metadataIndexes)
|
||||
{
|
||||
if (metadataIndexes == null)
|
||||
return default;
|
||||
|
||||
var index = metadataIndexes.DefaultIfEmpty(-1).First();
|
||||
if (index < 0)
|
||||
return default;
|
||||
|
||||
return FromMetadataIndex(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the content from the metadata index.
|
||||
/// </summary>
|
||||
public ContentType FromMetadataIndex(int metadataIndex)
|
||||
{
|
||||
if (metadataIndex < 0 || metadataIndex >= this.MetadataService.Metadatas.Count)
|
||||
return default;
|
||||
|
||||
//using (new ReadLock(this.rwlock))
|
||||
{
|
||||
var metadata = this.MetadataService.Metadatas[metadataIndex];
|
||||
return this.DiskManager.Load(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
public ContentType FromMetadata(MetadataType metadata) => this.DiskManager.Load(metadata);
|
||||
|
||||
/// <summary>
|
||||
/// Loads all content from the metadatas.
|
||||
/// </summary>
|
||||
public IEnumerable<ContentType> FromMetadatas(IEnumerable<MetadataType> metadatas) => FromMetadataIndexes(from m in metadatas select m.Index);
|
||||
|
||||
/// <summary>
|
||||
/// Loads all content from the metadata indexes.
|
||||
/// </summary>
|
||||
public IEnumerable<ContentType> FromMetadataIndexes(IEnumerable<int> metadataIndexes)
|
||||
{
|
||||
if (metadataIndexes == null)
|
||||
yield break;
|
||||
|
||||
//using (new ReadLock(this.rwlock))
|
||||
{
|
||||
foreach (int metadataIndex in metadataIndexes)
|
||||
{
|
||||
var metadata = this.MetadataService.Metadatas[metadataIndex];
|
||||
var content = this.DiskManager.Load(metadata);
|
||||
//result.Add(content);
|
||||
|
||||
yield return content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void FlagDirty(int dirty)
|
||||
{
|
||||
if (dirty != 0 && Interlocked.CompareExchange(ref this.isDirty, dirty, 0) == 0)
|
||||
HTTPManager.Heartbeats.Subscribe(this);
|
||||
}
|
||||
|
||||
public bool Save()
|
||||
{
|
||||
if (!this.rwlock.TryEnterWriteLock(TimeSpan.FromMilliseconds(0)))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
int itWasDirty = Interlocked.CompareExchange(ref this.isDirty, 0, 1);
|
||||
if (itWasDirty == 0)
|
||||
return true;
|
||||
|
||||
using (var fileStream = HTTPManager.IOService.CreateFileStream(this.MetadataFileName, Best.HTTP.Shared.PlatformSupport.FileSystem.FileStreamModes.Create))
|
||||
using (var stream = new BufferedStream(fileStream))
|
||||
this.MetadataService.SaveTo(stream);
|
||||
|
||||
if (this.Options.UseHashFile)
|
||||
{
|
||||
using (var hashStream = HTTPManager.IOService.CreateFileStream(this.HashFileName, Best.HTTP.Shared.PlatformSupport.FileSystem.FileStreamModes.Create))
|
||||
{
|
||||
var hash = this.DiskManager.CalculateHash();
|
||||
hashStream.Write(hash.Data, 0, hash.Count);
|
||||
BufferPool.Release(hash);
|
||||
}
|
||||
}
|
||||
|
||||
this.DiskManager.Save();
|
||||
|
||||
Interlocked.Exchange(ref this.isDirty, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.rwlock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
void IHeartbeat.OnHeartbeatUpdate(DateTime now, TimeSpan dif)
|
||||
{
|
||||
if (this.Save())
|
||||
HTTPManager.Heartbeats.Unsubscribe(this);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Save();
|
||||
HTTPManager.Heartbeats.Unsubscribe(this);
|
||||
|
||||
this.DiskManager.Dispose();
|
||||
this.DiskManager = null;
|
||||
|
||||
this.rwlock.Dispose();
|
||||
this.rwlock = null;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a166a5458783404a94c362f12b90e88
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Best.HTTP.Shared.Databases
|
||||
{
|
||||
public class DatabaseOptions
|
||||
{
|
||||
public string Name;
|
||||
public bool UseHashFile;
|
||||
|
||||
public DiskManagerOptions DiskManager = new DiskManagerOptions();
|
||||
|
||||
public DatabaseOptions(string dbName)
|
||||
{
|
||||
this.Name = dbName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a5df3217523b8e468cf636c4b5ee3fa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,295 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
using Best.HTTP.Shared.Extensions;
|
||||
using Best.HTTP.Shared.PlatformSupport.Memory;
|
||||
using Best.HTTP.Shared.PlatformSupport.Threading;
|
||||
using Best.HTTP.Shared.Streams;
|
||||
|
||||
namespace Best.HTTP.Shared.Databases
|
||||
{
|
||||
public sealed class DiskManagerOptions
|
||||
{
|
||||
// avg. size of certificates is 1390 (calculated from 2621 intermediate certificate)
|
||||
public int MaxCacheSizeInBytes = 5 * 1024;
|
||||
|
||||
public string HashDigest = "SHA256";
|
||||
}
|
||||
|
||||
public interface IDiskContentParser<T>
|
||||
{
|
||||
T Parse(Stream stream, int length);
|
||||
void Encode(Stream stream, T content);
|
||||
}
|
||||
|
||||
public sealed class DiskManager<T> : IDisposable
|
||||
{
|
||||
// TODO: store usage date/count and delete the oldest/least used?
|
||||
|
||||
struct CachePointer<CacheType>
|
||||
{
|
||||
public static readonly CachePointer<CacheType> Empty = new CachePointer<CacheType> { Position = -1, Length = -1, Content = default(CacheType) };
|
||||
|
||||
public int Position;
|
||||
public int Length;
|
||||
public CacheType Content;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[CachePointer<{this.Content.GetType().Name}>({Position}, {Length}, {Content})]";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sum size of the cached contents
|
||||
/// </summary>
|
||||
public int CacheSize { get; private set; }
|
||||
|
||||
private Stream stream;
|
||||
|
||||
private List<CachePointer<T>> cache = new List<CachePointer<T>>();
|
||||
private IDiskContentParser<T> diskContentParser;
|
||||
private DiskManagerOptions options;
|
||||
private FreeListManager freeListManager;
|
||||
|
||||
private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
|
||||
|
||||
public DiskManager(Stream stream, Stream freeListStream, IDiskContentParser<T> contentParser, DiskManagerOptions options)
|
||||
{
|
||||
if (!stream.CanSeek)
|
||||
throw new ArgumentException("DiskManager - stream can't seek!");
|
||||
|
||||
this.stream = stream;
|
||||
this.freeListManager = new FreeListManager(freeListStream);
|
||||
this.diskContentParser = contentParser;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public (int, int) Append(T content)
|
||||
{
|
||||
using (new WriteLock(this.rwLock))
|
||||
{
|
||||
int pos = -1;
|
||||
int length = -1;
|
||||
|
||||
using (var buffer = new BufferPoolMemoryStream())
|
||||
{
|
||||
diskContentParser.Encode(buffer, content);
|
||||
|
||||
length = (int)buffer.Length;
|
||||
|
||||
var idx = this.freeListManager.FindFreeIndex(length);
|
||||
|
||||
if (idx >= 0)
|
||||
pos = this.freeListManager.Occupy(idx, length);
|
||||
else
|
||||
pos = (int)this.stream.Length;
|
||||
|
||||
this.stream.Seek(pos, SeekOrigin.Begin);
|
||||
buffer.Seek(0, SeekOrigin.Begin);
|
||||
buffer.CopyTo(this.stream);
|
||||
}
|
||||
|
||||
this.stream.Flush();
|
||||
|
||||
return (pos, length);
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveChanged(Metadata metadata, T content)
|
||||
{
|
||||
using var _ = new WriteLock(this.rwLock);
|
||||
|
||||
int pos = -1;
|
||||
int length = -1;
|
||||
|
||||
using (var buffer = new BufferPoolMemoryStream())
|
||||
{
|
||||
diskContentParser.Encode(buffer, content);
|
||||
|
||||
length = (int)buffer.Length;
|
||||
|
||||
this.freeListManager.Add(metadata.FilePosition, metadata.Length);
|
||||
var idx = this.freeListManager.FindFreeIndex(length);
|
||||
|
||||
if (idx >= 0)
|
||||
pos = this.freeListManager.Occupy(idx, length);
|
||||
else
|
||||
pos = (int)this.stream.Length;
|
||||
|
||||
this.stream.Seek(pos, SeekOrigin.Begin);
|
||||
buffer.Seek(0, SeekOrigin.Begin);
|
||||
buffer.CopyTo(this.stream);
|
||||
}
|
||||
|
||||
this.stream.Flush();
|
||||
|
||||
metadata.FilePosition = pos;
|
||||
metadata.Length = length;
|
||||
}
|
||||
|
||||
public void Delete(Metadata metadata)
|
||||
{
|
||||
using (new WriteLock(this.rwLock))
|
||||
{
|
||||
this.freeListManager.Add(metadata.FilePosition, metadata.Length);
|
||||
this.stream.Seek(metadata.FilePosition, SeekOrigin.Begin);
|
||||
|
||||
var buffer = BufferPool.Get(BufferPool.MIN_BUFFER_SIZE, true);
|
||||
Array.Clear(buffer, 0, (int)BufferPool.MIN_BUFFER_SIZE);
|
||||
|
||||
int length = metadata.Length;
|
||||
int iterationCount = length / (int)BufferPool.MIN_BUFFER_SIZE;
|
||||
for (int i = 0; i < iterationCount; ++i)
|
||||
{
|
||||
this.stream.Write(buffer, 0, (int)BufferPool.MIN_BUFFER_SIZE);
|
||||
length -= (int)BufferPool.MIN_BUFFER_SIZE;
|
||||
}
|
||||
this.stream.Write(buffer, 0, length);
|
||||
|
||||
this.stream.Flush();
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public T Load(Metadata metadata)
|
||||
{
|
||||
using (new WriteLock(this.rwLock))
|
||||
{
|
||||
T parsedContent = default(T);
|
||||
|
||||
var cachePointer = GetCached(metadata.FilePosition);
|
||||
if (cachePointer.Position != -1 && cachePointer.Content != null)
|
||||
return cachePointer.Content;
|
||||
|
||||
this.stream.Seek(metadata.FilePosition, SeekOrigin.Begin);
|
||||
|
||||
parsedContent = diskContentParser.Parse(this.stream, metadata.Length);
|
||||
|
||||
AddToCache(parsedContent, metadata.FilePosition, metadata.Length);
|
||||
|
||||
return parsedContent;
|
||||
}
|
||||
}
|
||||
|
||||
public List<KeyValuePair<Meta, T>> LoadAll<Meta>(List<Meta> metadatas) where Meta : Metadata
|
||||
{
|
||||
using (new WriteLock(this.rwLock))
|
||||
{
|
||||
if (metadatas == null || metadatas.Count == 0)
|
||||
return null;
|
||||
|
||||
metadatas.Sort((m1, m2) => m1.FilePosition.CompareTo(m2.FilePosition));
|
||||
|
||||
List<KeyValuePair<Meta, T>> result = new List<KeyValuePair<Meta, T>>(metadatas.Count);
|
||||
|
||||
for (int i = 0; i < metadatas.Count; ++i)
|
||||
{
|
||||
var metadata = metadatas[i];
|
||||
|
||||
result.Add(new KeyValuePair<Meta, T>(metadata, Load(metadata)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
using (new WriteLock(this.rwLock))
|
||||
{
|
||||
this.freeListManager.Clear();
|
||||
this.stream.SetLength(0);
|
||||
this.stream.Flush();
|
||||
this.cache.Clear();
|
||||
this.CacheSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private CachePointer<T> GetCached(int position)
|
||||
{
|
||||
for (int i = 0; i < this.cache.Count; ++i)
|
||||
{
|
||||
var cache = this.cache[i];
|
||||
|
||||
if (cache.Position == position)
|
||||
return cache;
|
||||
}
|
||||
|
||||
return CachePointer<T>.Empty;
|
||||
}
|
||||
|
||||
private void AddToCache(T parsedContent, int pos, int length)
|
||||
{
|
||||
if (this.options.MaxCacheSizeInBytes >= length)
|
||||
{
|
||||
this.cache.Insert(0, new CachePointer<T>
|
||||
{
|
||||
Position = pos,
|
||||
Length = length,
|
||||
Content = parsedContent
|
||||
});
|
||||
this.CacheSize += length;
|
||||
}
|
||||
|
||||
while (this.CacheSize > this.options.MaxCacheSizeInBytes && this.cache.Count > 0)
|
||||
{
|
||||
var removingCache = this.cache[this.cache.Count - 1];
|
||||
this.cache.RemoveAt(this.cache.Count - 1);
|
||||
|
||||
this.CacheSize -= removingCache.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public BufferSegment CalculateHash()
|
||||
{
|
||||
using (new WriteLock(this.rwLock))
|
||||
{
|
||||
this.stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
var hash = System.Security.Cryptography.HashAlgorithm.Create(this.options.HashDigest);
|
||||
var result = hash.ComputeHash(this.stream);
|
||||
return new BufferSegment(result, 0, result.Length);
|
||||
#elif !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
var digest = Best.HTTP.SecureProtocol.Org.BouncyCastle.Security.DigestUtilities.GetDigest(this.options.HashDigest);
|
||||
|
||||
byte[] buffer = BufferPool.Get(4 * 1024, true);
|
||||
int readCount = 0;
|
||||
while ((readCount = this.stream.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
digest.BlockUpdate(buffer, 0, readCount);
|
||||
}
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
|
||||
byte[] result = BufferPool.Get(digest.GetDigestSize(), true);
|
||||
|
||||
int length = digest.DoFinal(result, 0);
|
||||
return new BufferSegment(result, 0, length);
|
||||
#else
|
||||
throw new NotImplementedException(nameof(CalculateHash));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
this.stream.Flush();
|
||||
this.freeListManager.Save();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.freeListManager.Dispose();
|
||||
this.stream.Flush();
|
||||
this.stream.Close();
|
||||
this.stream = null;
|
||||
this.rwLock.Dispose();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07c2801d01675b0479bc135ee8327fd1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,181 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using Best.HTTP.Shared.Databases.Utils;
|
||||
|
||||
namespace Best.HTTP.Shared.Databases
|
||||
{
|
||||
public sealed class FreeListManager : IDisposable
|
||||
{
|
||||
struct FreeSpot
|
||||
{
|
||||
public int pos;
|
||||
public int length;
|
||||
}
|
||||
|
||||
private Stream stream;
|
||||
private List<FreeSpot> freeList = new List<FreeSpot>();
|
||||
|
||||
public FreeListManager(Stream stream)
|
||||
{
|
||||
this.stream = stream;
|
||||
Load();
|
||||
}
|
||||
|
||||
private void Load()
|
||||
{
|
||||
this.freeList.Clear();
|
||||
this.stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
if (this.stream.Length == 0)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
uint count = (uint)stream.DecodeUnsignedVariableByteInteger();
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
int pos = (int)stream.DecodeUnsignedVariableByteInteger();
|
||||
int length = (int)stream.DecodeUnsignedVariableByteInteger();
|
||||
|
||||
this.freeList.Add(new FreeSpot { pos = pos, length = length });
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
this.freeList.Clear();
|
||||
this.stream.SetLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
if (this.freeList.Count == 0)
|
||||
{
|
||||
this.stream.SetLength(0);
|
||||
return;
|
||||
}
|
||||
|
||||
int count = this.freeList.Count;
|
||||
|
||||
this.stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
stream.EncodeUnsignedVariableByteInteger((uint)count);
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
FreeSpot spot = this.freeList[i];
|
||||
|
||||
stream.EncodeUnsignedVariableByteInteger((uint)spot.pos);
|
||||
stream.EncodeUnsignedVariableByteInteger((uint)spot.length);
|
||||
}
|
||||
|
||||
this.stream.Flush();
|
||||
}
|
||||
|
||||
public int FindFreeIndex(int length)
|
||||
{
|
||||
for (int i = 0; i < this.freeList.Count; ++i)
|
||||
{
|
||||
FreeSpot spot = this.freeList[i];
|
||||
|
||||
if (spot.length >= length)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int Occupy(int idx, int length)
|
||||
{
|
||||
FreeSpot spot = this.freeList[idx];
|
||||
int position = spot.pos;
|
||||
|
||||
if (spot.length < length)
|
||||
throw new Exception($"Can't Occupy a free spot with smaller space ({spot.length} < {length})!");
|
||||
|
||||
if (spot.length > length)
|
||||
{
|
||||
spot.pos += length;
|
||||
spot.length -= length;
|
||||
|
||||
this.freeList[idx] = spot;
|
||||
}
|
||||
else
|
||||
this.freeList.RemoveAt(idx);
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
public void Add(int pos, int length)
|
||||
{
|
||||
int insertToIdx = 0;
|
||||
|
||||
while (insertToIdx < this.freeList.Count && this.freeList[insertToIdx].pos < pos)
|
||||
insertToIdx++;
|
||||
|
||||
if (insertToIdx > this.freeList.Count)
|
||||
throw new Exception($"Couldn't find free spot with position '{pos}'!");
|
||||
|
||||
bool merged = false;
|
||||
FreeSpot spot = new FreeSpot { pos = pos, length = length };
|
||||
|
||||
if (insertToIdx > 0)
|
||||
{
|
||||
var prev = this.freeList[insertToIdx - 1];
|
||||
|
||||
// Merge with previous
|
||||
if (prev.pos + prev.length == pos)
|
||||
{
|
||||
prev.length += length;
|
||||
|
||||
this.freeList[insertToIdx - 1] = prev;
|
||||
|
||||
spot = prev;
|
||||
merged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (insertToIdx < this.freeList.Count)
|
||||
{
|
||||
var next = this.freeList[insertToIdx];
|
||||
|
||||
// merge with next?
|
||||
if (spot.pos + spot.length == next.pos)
|
||||
{
|
||||
spot.length += next.length;
|
||||
|
||||
if (!merged)
|
||||
{
|
||||
// Not already merged, extend the one in place
|
||||
this.freeList[insertToIdx] = spot;
|
||||
merged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Already merged. Further extend the previous, and remove the next.
|
||||
this.freeList[insertToIdx - 1] = spot;
|
||||
this.freeList.RemoveAt(insertToIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!merged)
|
||||
this.freeList.Insert(insertToIdx, spot);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
this.freeList.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.stream != null)
|
||||
this.stream.Close();
|
||||
this.stream = null;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0aa37cc0a1f5054f8ffb64d2cef5c5a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc04de29f38ec7c48b0517fc0ba4d8b2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,941 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Best.HTTP.Shared.Databases.Indexing
|
||||
{
|
||||
public enum Side
|
||||
{
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements most common list functions. With best case (no or only one item) it doesn't do any allocation.
|
||||
/// </summary>
|
||||
public struct NoAllocList<T>
|
||||
{
|
||||
private T _value;
|
||||
private bool _hasValue;
|
||||
|
||||
private List<T> _values;
|
||||
|
||||
public NoAllocList(T value)
|
||||
{
|
||||
this._value = value;
|
||||
this._hasValue = true;
|
||||
this._values = null;
|
||||
}
|
||||
|
||||
public T this[int index] {
|
||||
get => this._hasValue ? this._value : this._values[index];
|
||||
set
|
||||
{
|
||||
if (index < 0 || (this._values == null && index > 0))
|
||||
throw new IndexOutOfRangeException(index.ToString());
|
||||
|
||||
if (this._values != null)
|
||||
this._values[index] = value;
|
||||
else
|
||||
{
|
||||
this._value = value;
|
||||
this._hasValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Count { get => this._values != null ? this._values.Count : (this._hasValue ? 1 : 0); }
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
if (this._values != null)
|
||||
this._values.Add(item);
|
||||
else if (this._hasValue)
|
||||
{
|
||||
this._values = new List<T> { this._value, item };
|
||||
this._value = default(T);
|
||||
this._hasValue = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
this._value = item;
|
||||
this._hasValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
this._values?.Clear();
|
||||
this._values = null;
|
||||
this._value = default(T);
|
||||
this._hasValue = false;
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
if (this._values != null)
|
||||
return this._values.Contains(item);
|
||||
|
||||
// This can thrown a NullRefException if _value is null!
|
||||
return this._hasValue ? this._value.Equals(item) : false;
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
if (this._values != null)
|
||||
return this._values.Remove(item);
|
||||
else if (this._hasValue && this._value.Equals(item))
|
||||
{
|
||||
this._value = default(T);
|
||||
this._hasValue = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
if (index < 0 || (this._values == null && index > 0))
|
||||
throw new IndexOutOfRangeException(index.ToString());
|
||||
|
||||
if (this._values != null)
|
||||
this._values.RemoveAt(index);
|
||||
else
|
||||
{
|
||||
this._value = default(T);
|
||||
this._hasValue = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Node<KeyT, ValueT>
|
||||
{
|
||||
public Node<KeyT, ValueT> Parent, Left, Right;
|
||||
|
||||
public KeyT Key { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Depth of the node.
|
||||
/// </summary>
|
||||
public int Depth;
|
||||
|
||||
/// <summary>
|
||||
/// Difference between LeftDepth and RightDepth.
|
||||
/// </summary>
|
||||
public int BalanceFactor { get { return this.LeftDepth - this.RightDepth; } }
|
||||
|
||||
/// <summary>
|
||||
/// Left node's Depth, or -1 if it's null.
|
||||
/// </summary>
|
||||
public int LeftDepth { get { return this.Left == null ? -1 : this.Left.Depth; } }
|
||||
|
||||
/// <summary>
|
||||
/// Right node's Depth, or -1 if it's null.
|
||||
/// </summary>
|
||||
public int RightDepth { get { return this.Right == null ? -1 : this.Right.Depth; } }
|
||||
|
||||
public bool IsRoot { get { return this.Parent == null; } }
|
||||
|
||||
public int ChildCount { get { return (this.Left == null ? 0 : 1) + (this.Right == null ? 0 : 1); } }
|
||||
|
||||
// Stored values aren't public as modifing them requires special care.
|
||||
private NoAllocList<ValueT> _item;
|
||||
|
||||
public Node(Node<KeyT, ValueT> parent, KeyT key, ValueT value)
|
||||
{
|
||||
this.Parent = parent;
|
||||
this.Key = key;
|
||||
this._item = new NoAllocList<ValueT>(value);
|
||||
|
||||
// Depth is 0 by default, as it has no child
|
||||
this.Depth = 0;
|
||||
}
|
||||
|
||||
public void BubbleUpDepthChange()
|
||||
{
|
||||
var current = this;
|
||||
|
||||
while (current != null)
|
||||
{
|
||||
var oldDepth = current.Depth;
|
||||
current.Depth = Math.Max(current.LeftDepth, current.RightDepth) + 1;
|
||||
|
||||
if (oldDepth != current.Depth)
|
||||
current = current.Parent;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public ValueT this[int index] { get => this._item[index]; }
|
||||
|
||||
public int Count { get => this._item.Count; }
|
||||
|
||||
public void Clear() => this._item = new NoAllocList<ValueT>();
|
||||
|
||||
public void Add(ValueT value)
|
||||
{
|
||||
var tmp = this._item;
|
||||
tmp.Add(value);
|
||||
this._item = tmp;
|
||||
}
|
||||
|
||||
public bool Remove(ValueT value)
|
||||
{
|
||||
var tmp = this._item;
|
||||
var result = tmp.Remove(value);
|
||||
if (result)
|
||||
this._item = tmp;
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<ValueT> ToList()
|
||||
{
|
||||
var list = new List<ValueT>(this._item.Count);
|
||||
|
||||
for (int i = 0; i < this._item.Count; ++i)
|
||||
list.Add(this._item[i]);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Left?.Key.ToString()} <- {this.Key.ToString()} -> {this.Right?.Key.ToString()}";
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.codesdope.com/course/data-structures-avl-trees/
|
||||
public sealed class AVLTree<Key, Value>
|
||||
{
|
||||
public int ElemCount { get; private set; }
|
||||
public int NodeCount { get; private set; }
|
||||
public IComparer<Key> Comparer;
|
||||
|
||||
public Node<Key, Value> RootNode { get; private set; } = null;
|
||||
|
||||
public AVLTree(IComparer<Key> comparer)
|
||||
{
|
||||
this.Comparer = comparer;
|
||||
}
|
||||
|
||||
public void Add(Key key, Value item, bool clearValues = false)
|
||||
{
|
||||
if (this.RootNode == null) {
|
||||
this.NodeCount++;
|
||||
this.ElemCount++;
|
||||
this.RootNode = new Node<Key, Value>(null, key, item);
|
||||
return;
|
||||
}
|
||||
|
||||
var current = this.RootNode;
|
||||
do
|
||||
{
|
||||
// +--------------------+-----------------------+
|
||||
// | Value | Meaning |
|
||||
// +--------------------+-----------------------+
|
||||
// | Less than zero | x is less than y. |
|
||||
// | Zero | x equals y. |
|
||||
// | Greater than zero | x is greater than y. |
|
||||
// +--------------------------------------------+
|
||||
int comp = this.Comparer.Compare(/*x: */ current.Key, /*y: */ key);
|
||||
|
||||
// equals
|
||||
if (comp == 0)
|
||||
{
|
||||
if (clearValues)
|
||||
{
|
||||
this.ElemCount -= current.Count;
|
||||
current.Clear();
|
||||
}
|
||||
|
||||
current.Add(item);
|
||||
break;
|
||||
}
|
||||
|
||||
// current's key > key
|
||||
if (comp > 0)
|
||||
{
|
||||
// insert new node
|
||||
if (current.Left == null)
|
||||
{
|
||||
current.Left = new Node<Key, Value>(current, key, item);
|
||||
current.BubbleUpDepthChange(/*Side.Left, 1*/);
|
||||
|
||||
current = current.Left;
|
||||
|
||||
this.NodeCount++;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
current = current.Left;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// current's key < key
|
||||
if (comp < 0)
|
||||
{
|
||||
// insert new node
|
||||
if (current.Right == null)
|
||||
{
|
||||
current.Right = new Node<Key, Value>(current, key, item);
|
||||
current.BubbleUpDepthChange(/*Side.Right, 1*/);
|
||||
|
||||
current = current.Right;
|
||||
|
||||
this.NodeCount++;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
current = current.Right;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
|
||||
this.ElemCount++;
|
||||
|
||||
while (RebalanceFrom(current) != null)
|
||||
;
|
||||
|
||||
//TestBalance(this.root);
|
||||
}
|
||||
|
||||
public bool TestBalance() => TestBalance(this.RootNode);
|
||||
|
||||
private bool TestBalance(Node<Key, Value> node)
|
||||
{
|
||||
if (node == null)
|
||||
return true;
|
||||
|
||||
if (Math.Abs(node.BalanceFactor) > 1)
|
||||
{
|
||||
//UnityEngine.Debug.Break();
|
||||
return false;
|
||||
}
|
||||
|
||||
return TestBalance(node.Left) && TestBalance(node.Right);
|
||||
}
|
||||
|
||||
List<Side> path = new List<Side>(2);
|
||||
|
||||
private Node<Key, Value> RebalanceFrom(Node<Key, Value> newNode)
|
||||
{
|
||||
if (newNode.IsRoot || newNode.Parent.IsRoot)
|
||||
return null;
|
||||
|
||||
path.Clear();
|
||||
|
||||
// find first unbalanced node or exit when found the root node (root still can be unbalanced!)
|
||||
var current = newNode;
|
||||
var balanceFactor = current.BalanceFactor;
|
||||
while (!current.IsRoot && Math.Abs(balanceFactor) <= 1)
|
||||
{
|
||||
if (current.Parent.Left == current)
|
||||
path.Add(Side.Left);
|
||||
else
|
||||
path.Add(Side.Right);
|
||||
|
||||
current = current.Parent;
|
||||
balanceFactor = current.BalanceFactor;
|
||||
}
|
||||
|
||||
// it's a balanced tree
|
||||
if (Math.Abs(balanceFactor) <= 1)
|
||||
return null;
|
||||
|
||||
Side last = path[path.Count - 1];// path[path.StartIdx];
|
||||
Side prev = path[path.Count - 2];// path[path.EndIdx];
|
||||
|
||||
if (last == Side.Left && prev == Side.Left)
|
||||
{
|
||||
// insertion to a left child of a left child
|
||||
RotateRight(current)
|
||||
.BubbleUpDepthChange();
|
||||
current.BubbleUpDepthChange();
|
||||
}
|
||||
else if (last == Side.Right && prev == Side.Right)
|
||||
{
|
||||
// insertion to a right child of a right child
|
||||
RotateLeft(current)
|
||||
.BubbleUpDepthChange();
|
||||
current.BubbleUpDepthChange();
|
||||
}
|
||||
else if (last == Side.Right && prev == Side.Left)
|
||||
{
|
||||
// insertion to a left child of a right child
|
||||
var current_right = current.Right;
|
||||
RotateRight(current.Right);
|
||||
RotateLeft(current);
|
||||
|
||||
current_right.BubbleUpDepthChange();
|
||||
current.BubbleUpDepthChange();
|
||||
}
|
||||
else if (last == Side.Left && prev == Side.Right)
|
||||
{
|
||||
// insertion to a right child of a left child
|
||||
|
||||
var current_left = current.Left;
|
||||
RotateLeft(current.Left);
|
||||
RotateRight(current);
|
||||
|
||||
current_left.BubbleUpDepthChange();
|
||||
current.BubbleUpDepthChange();
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
this.RootNode = null;
|
||||
this.ElemCount = 0;
|
||||
this.NodeCount = 0;
|
||||
}
|
||||
|
||||
public List<Value> Remove(Key key)
|
||||
{
|
||||
if (this.RootNode == null)
|
||||
return null;
|
||||
|
||||
var current = this.RootNode;
|
||||
do
|
||||
{
|
||||
int comp = this.Comparer.Compare(current.Key, key);
|
||||
|
||||
// equals
|
||||
if (comp == 0)
|
||||
{
|
||||
this.NodeCount--;
|
||||
this.ElemCount -= current.Count;
|
||||
|
||||
// remove current node from the tree
|
||||
RemoveNode(current);
|
||||
|
||||
return current.ToList();
|
||||
}
|
||||
|
||||
// current's key > key
|
||||
if (comp > 0)
|
||||
{
|
||||
if (current.Left == null)
|
||||
return null;
|
||||
else
|
||||
{
|
||||
current = current.Left;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// current's key < key
|
||||
if (comp < 0)
|
||||
{
|
||||
if (current.Right == null)
|
||||
return null;
|
||||
else
|
||||
{
|
||||
current = current.Right;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
public void Remove(Key key, Value value)
|
||||
{
|
||||
if (this.RootNode == null)
|
||||
return;
|
||||
|
||||
var current = this.RootNode;
|
||||
do
|
||||
{
|
||||
int comp = this.Comparer.Compare(current.Key, key);
|
||||
|
||||
// equals
|
||||
if (comp == 0)
|
||||
{
|
||||
if (current.Remove(value))
|
||||
this.ElemCount--;
|
||||
|
||||
if (current.Count == 0)
|
||||
{
|
||||
// remove current node from the tree
|
||||
RemoveNode(current);
|
||||
|
||||
this.NodeCount--;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// current's key > key
|
||||
if (comp > 0)
|
||||
{
|
||||
if (current.Left == null)
|
||||
return ;
|
||||
else
|
||||
{
|
||||
current = current.Left;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// current's key < key
|
||||
if (comp < 0)
|
||||
{
|
||||
if (current.Right == null)
|
||||
return ;
|
||||
else
|
||||
{
|
||||
current = current.Right;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes node and reparent any child it has.
|
||||
/// </summary>
|
||||
private void RemoveNode(Node<Key, Value> node)
|
||||
{
|
||||
var parent = node.Parent;
|
||||
Side side = parent?.Left == node ? Side.Left : Side.Right;
|
||||
int childCount = node.ChildCount;
|
||||
|
||||
var testForRebalanceNode = parent;
|
||||
|
||||
switch(childCount)
|
||||
{
|
||||
case 0:
|
||||
// node has no child
|
||||
|
||||
if (parent == null)
|
||||
{
|
||||
this.RootNode = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parent.Left == node)
|
||||
parent.Left = null;
|
||||
else
|
||||
parent.Right = null;
|
||||
parent.BubbleUpDepthChange();
|
||||
}
|
||||
|
||||
node.Parent = null;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// re-parent the only child
|
||||
// Example: Removing node 25 will replace it with 30
|
||||
//
|
||||
// 20
|
||||
// 15 25
|
||||
// 30
|
||||
//
|
||||
var child = node.Left ?? node.Right;
|
||||
|
||||
if (parent == null)
|
||||
{
|
||||
this.RootNode = child;
|
||||
this.RootNode.Parent = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parent.Left == node)
|
||||
parent.Left = child;
|
||||
else
|
||||
parent.Right = child;
|
||||
child.Parent = parent;
|
||||
|
||||
parent.BubbleUpDepthChange();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// two child
|
||||
|
||||
// 1: Replace 20 with 25
|
||||
//
|
||||
// 20 20
|
||||
// 15 25 15 25
|
||||
// 30
|
||||
//
|
||||
|
||||
// 2: Replace 20 with 22
|
||||
//
|
||||
// 20
|
||||
// 15 25
|
||||
// 22
|
||||
|
||||
// 3: Re-parent 23 for 25, replace 20 with 22
|
||||
//
|
||||
// 20
|
||||
// 15 25
|
||||
// 22
|
||||
// 23
|
||||
|
||||
// Cases 1 and 3 are the same, both 25 and 22 has a right child. But in case 3, 22 isn't first child of 20!
|
||||
|
||||
// Find node with the least Key, that's a node without a left node so we have to deal only with its right node.
|
||||
var nodeToReplaceWith = FindMin(node.Right);
|
||||
|
||||
testForRebalanceNode = nodeToReplaceWith;
|
||||
side = Side.Right;
|
||||
|
||||
// re-parent 23 in case 3:
|
||||
if (nodeToReplaceWith.Parent != node)
|
||||
{
|
||||
testForRebalanceNode = nodeToReplaceWith.Parent;
|
||||
if (nodeToReplaceWith.Parent.Left == nodeToReplaceWith)
|
||||
{
|
||||
nodeToReplaceWith.Parent.Left = nodeToReplaceWith.Right;
|
||||
side = Side.Left;
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeToReplaceWith.Parent.Right = nodeToReplaceWith.Right;
|
||||
side = Side.Right;
|
||||
}
|
||||
|
||||
if (nodeToReplaceWith.Right != null)
|
||||
nodeToReplaceWith.Right.Parent = nodeToReplaceWith.Parent;
|
||||
}
|
||||
|
||||
if (parent == null)
|
||||
this.RootNode = nodeToReplaceWith;
|
||||
else
|
||||
{
|
||||
if (parent.Left == node)
|
||||
parent.Left = nodeToReplaceWith;
|
||||
else
|
||||
parent.Right = nodeToReplaceWith;
|
||||
}
|
||||
nodeToReplaceWith.Parent = parent;
|
||||
|
||||
// Reparent node's left
|
||||
nodeToReplaceWith.Left = node.Left;
|
||||
node.Left.Parent = nodeToReplaceWith;
|
||||
|
||||
// Reparent node's right node, if it's not the one we replaceing it with
|
||||
if (node.Right != nodeToReplaceWith)
|
||||
{
|
||||
nodeToReplaceWith.Right = node.Right;
|
||||
node.Right.Parent = nodeToReplaceWith;
|
||||
}
|
||||
//else
|
||||
// nodeToReplaceWith.Right = null;
|
||||
|
||||
if (testForRebalanceNode != nodeToReplaceWith)
|
||||
testForRebalanceNode.BubbleUpDepthChange();
|
||||
nodeToReplaceWith.BubbleUpDepthChange();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
while (RebalanceForRemoval(testForRebalanceNode, side) != null)
|
||||
;
|
||||
|
||||
//TestBalance(this.root);
|
||||
}
|
||||
|
||||
private Node<Key, Value> RebalanceForRemoval(Node<Key, Value> removedParentNode, Side side)
|
||||
{
|
||||
if (removedParentNode == null)
|
||||
return null;
|
||||
|
||||
path.Clear();
|
||||
path.Add(side);
|
||||
|
||||
// find first unbalanced node or exit when found the root node (root still can be unbalanced!)
|
||||
var current = removedParentNode;
|
||||
while (!current.IsRoot && Math.Abs(current.BalanceFactor) <= 1)
|
||||
{
|
||||
if (current.Parent.Left == current)
|
||||
path.Add(Side.Left);
|
||||
else
|
||||
path.Add(Side.Right);
|
||||
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
// it's a balanced tree
|
||||
if (Math.Abs(current.BalanceFactor) <= 1)
|
||||
return null;
|
||||
|
||||
// from what direction we came from
|
||||
Side fromDirection = path[path.Count - 1];
|
||||
|
||||
// check weather it's an inside or outside case
|
||||
switch (fromDirection)
|
||||
{
|
||||
case Side.Right:
|
||||
{
|
||||
bool isOutside = current.Left.LeftDepth >= current.Left.RightDepth;
|
||||
if (isOutside)
|
||||
{
|
||||
RotateRight(current)
|
||||
.BubbleUpDepthChange();
|
||||
current.BubbleUpDepthChange();
|
||||
}
|
||||
else
|
||||
{
|
||||
var current_left = current.Left;
|
||||
RotateLeft(current.Left);
|
||||
RotateRight(current);
|
||||
|
||||
current_left.BubbleUpDepthChange();
|
||||
current.BubbleUpDepthChange();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Side.Left:
|
||||
{
|
||||
bool isOutside = current.Right.RightDepth >= current.Right.LeftDepth;
|
||||
if (isOutside)
|
||||
{
|
||||
// Example: Removing node 14 result in a disbalance in node 20
|
||||
//
|
||||
// 20
|
||||
// 15 25
|
||||
// (14) 22 26
|
||||
// 27
|
||||
|
||||
var current_right = current.Right; // node 25
|
||||
RotateLeft(current);
|
||||
// After RotateLeft(current: node 20):
|
||||
// 25
|
||||
// 20 26
|
||||
// 15 22 27
|
||||
|
||||
current.BubbleUpDepthChange(); // node 20
|
||||
current_right.BubbleUpDepthChange(); // node 25
|
||||
}
|
||||
else
|
||||
{
|
||||
// Example: Removing node 14 results in a disbalance in node 20.
|
||||
//
|
||||
// 20
|
||||
// 15 25
|
||||
// (14) 22 26
|
||||
// 23
|
||||
|
||||
var current_right = current.Right;
|
||||
|
||||
RotateRight(current.Right);
|
||||
// After RotateRight(current.Right: node 22):
|
||||
// 20
|
||||
// 15 22
|
||||
// 25
|
||||
// 23 26
|
||||
|
||||
RotateLeft(current);
|
||||
// After RotateLeft(current: node 20):
|
||||
// 22
|
||||
// 20 25
|
||||
// 15 23 26
|
||||
|
||||
current.BubbleUpDepthChange();
|
||||
current_right.BubbleUpDepthChange();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
private Node<Key, Value> FindMin(Node<Key, Value> node)
|
||||
{
|
||||
var current = node;
|
||||
|
||||
while (current.Left != null)
|
||||
current = current.Left;
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
private Node<Key, Value> FindMax(Node<Key, Value> node)
|
||||
{
|
||||
var current = node;
|
||||
while (current.Right != null)
|
||||
current = current.Right;
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
public List<Value> Find(Key key) {
|
||||
if (this.RootNode == null)
|
||||
return null;
|
||||
|
||||
var current = this.RootNode;
|
||||
do
|
||||
{
|
||||
int comp = this.Comparer.Compare(current.Key, key);
|
||||
|
||||
// equals
|
||||
if (comp == 0)
|
||||
return current.ToList();
|
||||
|
||||
// current's key > key
|
||||
if (comp > 0)
|
||||
{
|
||||
if (current.Left == null)
|
||||
return null;
|
||||
else
|
||||
{
|
||||
current = current.Left;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// current's key < key
|
||||
if (comp < 0)
|
||||
{
|
||||
if (current.Right == null)
|
||||
return null;
|
||||
else
|
||||
{
|
||||
current = current.Right;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
public IEnumerable<Value> WalkHorizontal()
|
||||
{
|
||||
if (this.RootNode == null)
|
||||
yield break;
|
||||
|
||||
Queue<Node<Key, Value>> toWalk = new Queue<Node<Key, Value>>();
|
||||
|
||||
toWalk.Enqueue(this.RootNode);
|
||||
|
||||
while (toWalk.Count > 0)
|
||||
{
|
||||
var current = toWalk.Dequeue();
|
||||
|
||||
if (current.Left != null)
|
||||
toWalk.Enqueue(current.Left);
|
||||
if (current.Right != null)
|
||||
toWalk.Enqueue(current.Right);
|
||||
|
||||
for (int i = 0; i < current.Count; i++)
|
||||
yield return current[i];
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsKey(Key key)
|
||||
{
|
||||
if (this.RootNode == null)
|
||||
return false;
|
||||
|
||||
var current = this.RootNode;
|
||||
do
|
||||
{
|
||||
int comp = this.Comparer.Compare(current.Key, key);
|
||||
|
||||
// equals
|
||||
if (comp == 0)
|
||||
return true;
|
||||
|
||||
// current's key > key
|
||||
if (comp > 0)
|
||||
{
|
||||
if (current.Left == null)
|
||||
return false;
|
||||
else
|
||||
{
|
||||
current = current.Left;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// current's key < key
|
||||
if (comp < 0)
|
||||
{
|
||||
if (current.Right == null)
|
||||
return false;
|
||||
else
|
||||
{
|
||||
current = current.Right;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
private Node<Key, Value> RotateRight(Node<Key, Value> current)
|
||||
{
|
||||
// Current\
|
||||
// 20 15
|
||||
// 15 10 20
|
||||
// 10 ? ?
|
||||
var parent = current.Parent;
|
||||
var leftChild = current.Left;
|
||||
|
||||
// re-parent left child
|
||||
if (parent != null)
|
||||
{
|
||||
if (parent.Left == current)
|
||||
parent.Left = leftChild;
|
||||
else
|
||||
parent.Right = leftChild;
|
||||
}
|
||||
else
|
||||
this.RootNode = leftChild;
|
||||
leftChild.Parent = parent;
|
||||
|
||||
// re-parent left child's right child
|
||||
if (leftChild.Right != null)
|
||||
leftChild.Right.Parent = current;
|
||||
current.Left = leftChild.Right;
|
||||
|
||||
// re-parent current
|
||||
current.Parent = leftChild;
|
||||
leftChild.Right = current;
|
||||
|
||||
// return with the node that took the place of current
|
||||
return leftChild;
|
||||
}
|
||||
|
||||
private Node<Key, Value> RotateLeft(Node<Key, Value> current)
|
||||
{
|
||||
// /Current
|
||||
// 20 15
|
||||
// 15 20 10
|
||||
// ? 10 ?
|
||||
var parent = current.Parent;
|
||||
var rightChild = current.Right;
|
||||
|
||||
// re-parent right child
|
||||
if (parent != null)
|
||||
{
|
||||
if (parent.Left == current)
|
||||
parent.Left = rightChild;
|
||||
else
|
||||
parent.Right = rightChild;
|
||||
}
|
||||
else
|
||||
this.RootNode = rightChild;
|
||||
rightChild.Parent = parent;
|
||||
|
||||
// re-parent right child's left child
|
||||
if (rightChild.Left != null)
|
||||
rightChild.Left.Parent = current;
|
||||
current.Right = rightChild.Left;
|
||||
|
||||
// re-parent current
|
||||
current.Parent = rightChild;
|
||||
rightChild.Left = current;
|
||||
|
||||
return rightChild;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7cc4a6484b91bfd489fab519ef4f61eb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1cefb9c5ecaff6a44b77039a56050aac
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Best.HTTP.Shared.Databases.Indexing.Comparers
|
||||
{
|
||||
public sealed class ByteArrayComparer : IComparer<byte[]>
|
||||
{
|
||||
public int Compare(byte[] x, byte[] y)
|
||||
{
|
||||
int result = x.Length.CompareTo(y.Length);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
for (int i = 0; i < x.Length; ++i)
|
||||
{
|
||||
result = x[i].CompareTo(y[i]);
|
||||
|
||||
if (result != 0)
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e92d1b70cb4146d47b88bfb3f43c49f8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Best.HTTP.Shared.Databases.Indexing.Comparers
|
||||
{
|
||||
public sealed class DateTimeComparer : IComparer<DateTime>
|
||||
{
|
||||
public int Compare(DateTime x, DateTime y)
|
||||
{
|
||||
return x.CompareTo(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c788ada3967bfe4dac4ce9e7522dc8c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Best.HTTP.Shared.Databases.Indexing.Comparers
|
||||
{
|
||||
public sealed class Hash128Comparer : IComparer<UnityEngine.Hash128>
|
||||
{
|
||||
public int Compare(UnityEngine.Hash128 x, UnityEngine.Hash128 y)
|
||||
{
|
||||
return x.CompareTo(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d8da4c912810a742a1af50bb90fb26e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Best.HTTP.Shared.Databases.Indexing.Comparers
|
||||
{
|
||||
public sealed class StringComparer : IComparer<string>
|
||||
{
|
||||
public int Compare(string x, string y)
|
||||
{
|
||||
return x.CompareTo(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c484a2e93b3a84746939c4d2d5cdf8d9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Best.HTTP.Shared.Databases.Indexing.Comparers
|
||||
{
|
||||
public sealed class UInt16Comparer : IComparer<UInt16>
|
||||
{
|
||||
public int Compare(ushort x, ushort y)
|
||||
{
|
||||
return x.CompareTo(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bedd5a1e08a99404fbc747e79b92f17c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Best.HTTP.Shared.Databases.Indexing.Comparers
|
||||
{
|
||||
public sealed class UInt32Comparer : IComparer<UInt32>
|
||||
{
|
||||
public int Compare(UInt32 x, UInt32 y)
|
||||
{
|
||||
return x.CompareTo(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b03ac89356268b40b90d8a46678c699
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Best.HTTP.Shared.Databases
|
||||
{
|
||||
public abstract class IndexingService<ContentType, MetadataType> where MetadataType : Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Index newly added metadata
|
||||
/// </summary>
|
||||
public virtual void Index(MetadataType metadata) { }
|
||||
|
||||
/// <summary>
|
||||
/// Remove metadata from all indexes.
|
||||
/// </summary>
|
||||
public virtual void Remove(MetadataType metadata) { }
|
||||
|
||||
/// <summary>
|
||||
/// Clear all indexes
|
||||
/// </summary>
|
||||
public virtual void Clear() { }
|
||||
|
||||
/// <summary>
|
||||
/// Get indexes in an optimized order. This is usually one of the indexes' WalkHorizontal() call.
|
||||
/// </summary>
|
||||
public virtual IEnumerable<int> GetOptimizedIndexes() => null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 161054408a1ecbe4383c76ea478332ed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
using Best.HTTP.Shared.Databases.Utils;
|
||||
|
||||
namespace Best.HTTP.Shared.Databases
|
||||
{
|
||||
public abstract class Metadata
|
||||
{
|
||||
public int Index;
|
||||
public int FilePosition;
|
||||
public int Length;
|
||||
|
||||
public bool IsDeleted => this.FilePosition == -1 && this.Length == -1;
|
||||
|
||||
public void MarkForDelete()
|
||||
{
|
||||
this.FilePosition = -1;
|
||||
this.Length = -1;
|
||||
}
|
||||
|
||||
public virtual void SaveTo(Stream stream)
|
||||
{
|
||||
if (this.IsDeleted)
|
||||
throw new Exception($"Trying to save a deleted metadata({this.ToString()})!");
|
||||
|
||||
stream.EncodeUnsignedVariableByteInteger((uint)this.FilePosition);
|
||||
stream.EncodeUnsignedVariableByteInteger((uint)this.Length);
|
||||
}
|
||||
|
||||
public virtual void LoadFrom(Stream stream)
|
||||
{
|
||||
this.FilePosition = (int)stream.DecodeUnsignedVariableByteInteger();
|
||||
this.Length = (int)stream.DecodeUnsignedVariableByteInteger();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[Metadata Idx: {Index}, Pos: {FilePosition}, Length: {Length}, IsDeleted: {IsDeleted}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc2cf4b60fcca484e9aafe612ba9ac51
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bb24b6286ceff24abd558107641cd8b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Best.HTTP.Shared.Databases.MetadataIndexFinders
|
||||
{
|
||||
public sealed class DefaultEmptyMetadataIndexFinder<MetadataType> : IEmptyMetadataIndexFinder<MetadataType> where MetadataType : Metadata
|
||||
{
|
||||
public int FindFreeIndex(List<MetadataType> metadatas)
|
||||
{
|
||||
return metadatas.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3a9d8df83b70404782d3d67884dc1a9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Best.HTTP.Shared.Databases.MetadataIndexFinders
|
||||
{
|
||||
public sealed class FindDeletedMetadataIndexFinder<MetadataType> : IEmptyMetadataIndexFinder<MetadataType> where MetadataType : Metadata
|
||||
{
|
||||
public int FindFreeIndex(List<MetadataType> metadatas)
|
||||
{
|
||||
for (int i = 0; i < metadatas.Count; ++i)
|
||||
if (metadatas[i].IsDeleted)
|
||||
return i;
|
||||
|
||||
return metadatas.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a63cdf33c00b6443bcbd6aa81aa6562
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Best.HTTP.Shared.Databases.MetadataIndexFinders
|
||||
{
|
||||
public interface IEmptyMetadataIndexFinder<MetadataType> where MetadataType : Metadata
|
||||
{
|
||||
int FindFreeIndex(List<MetadataType> metadatas);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e825306d81906834b84135765d5c3174
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,152 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using Best.HTTP.Shared.Databases.MetadataIndexFinders;
|
||||
|
||||
namespace Best.HTTP.Shared.Databases
|
||||
{
|
||||
public struct MetadataStats
|
||||
{
|
||||
public int min;
|
||||
public int max;
|
||||
public int count;
|
||||
public int sum;
|
||||
}
|
||||
|
||||
public abstract class MetadataService<MetadataType, ContentType> where MetadataType : Metadata, new()
|
||||
{
|
||||
public List<MetadataType> Metadatas { get; protected set; }
|
||||
public IndexingService<ContentType, MetadataType> IndexingService { get; protected set; }
|
||||
public IEmptyMetadataIndexFinder<MetadataType> EmptyMetadataIndexFinder { get; protected set; }
|
||||
|
||||
protected MetadataService(IndexingService<ContentType, MetadataType> indexingService, IEmptyMetadataIndexFinder<MetadataType> emptyMetadataIndexFinder)
|
||||
{
|
||||
this.IndexingService = indexingService;
|
||||
this.EmptyMetadataIndexFinder = emptyMetadataIndexFinder;
|
||||
|
||||
this.Metadatas = new List<MetadataType>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when metadata loaded from file
|
||||
/// </summary>
|
||||
public virtual MetadataType CreateFrom(Stream stream)
|
||||
{
|
||||
var metadata = new MetadataType();
|
||||
|
||||
metadata.Index = this.Metadatas.Count;
|
||||
metadata.LoadFrom(stream);
|
||||
|
||||
this.Metadatas.Add(metadata);
|
||||
|
||||
this.IndexingService.Index(metadata);
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by a concrete MetadataService implementation to create a new metadata
|
||||
/// </summary>
|
||||
protected MetadataType CreateDefault(ContentType content, int filePos, int length, Action<ContentType, MetadataType> setupCallback)
|
||||
{
|
||||
var metadata = new MetadataType();
|
||||
metadata.FilePosition = filePos;
|
||||
metadata.Length = length;
|
||||
|
||||
metadata.Index = this.EmptyMetadataIndexFinder.FindFreeIndex(this.Metadatas);
|
||||
|
||||
if (setupCallback != null)
|
||||
setupCallback(content, metadata);
|
||||
|
||||
if (metadata.Index == this.Metadatas.Count)
|
||||
this.Metadatas.Add(metadata);
|
||||
else
|
||||
this.Metadatas[metadata.Index] = metadata;
|
||||
|
||||
this.IndexingService.Index(metadata);
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public void Remove(MetadataType metadata)
|
||||
{
|
||||
this.IndexingService.Remove(metadata);
|
||||
|
||||
// Mark metadata for deletion. Next time we save, it's not going to written out to disk.
|
||||
metadata.MarkForDelete();
|
||||
}
|
||||
|
||||
public void SaveTo(Stream stream)
|
||||
{
|
||||
stream.SetLength(0);
|
||||
|
||||
var optimizedIndexes = this.IndexingService.GetOptimizedIndexes();
|
||||
|
||||
if (optimizedIndexes != null)
|
||||
{
|
||||
foreach (var index in optimizedIndexes)
|
||||
{
|
||||
var metadata = this.Metadatas[index];
|
||||
|
||||
if (metadata.IsDeleted)
|
||||
continue;
|
||||
|
||||
metadata.SaveTo(stream);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < this.Metadatas.Count; ++i)
|
||||
{
|
||||
var metadata = this.Metadatas[i];
|
||||
|
||||
if (metadata.IsDeleted)
|
||||
continue;
|
||||
|
||||
metadata.SaveTo(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadFrom(Stream stream)
|
||||
{
|
||||
while (stream.Position < stream.Length)
|
||||
CreateFrom(stream);
|
||||
}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
this.Metadatas.Clear();
|
||||
this.IndexingService.Clear();
|
||||
}
|
||||
|
||||
public MetadataStats GetStats()
|
||||
{
|
||||
int min = int.MaxValue;
|
||||
int max = 0;
|
||||
int sum = 0;
|
||||
int count = 0;
|
||||
foreach (var metadata in this.Metadatas)
|
||||
{
|
||||
if (metadata.IsDeleted)
|
||||
continue;
|
||||
|
||||
if (metadata.Length > max)
|
||||
max = metadata.Length;
|
||||
if (metadata.Length < min)
|
||||
min = metadata.Length;
|
||||
sum += metadata.Length;
|
||||
count++;
|
||||
}
|
||||
|
||||
return new MetadataStats
|
||||
{
|
||||
count = count,
|
||||
min = min,
|
||||
max = max,
|
||||
sum = sum
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36cd9f102ecc80746bc7ff850f782e32
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea59aadbd0882aa46a9b5c86d137d70a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
using Best.HTTP.Shared.PlatformSupport.Memory;
|
||||
|
||||
namespace Best.HTTP.Shared.Databases.Utils
|
||||
{
|
||||
public static class StreamUtil
|
||||
{
|
||||
public static void WriteLengthPrefixedString(this Stream stream, string str)
|
||||
{
|
||||
if (str != null)
|
||||
{
|
||||
var byteCount = Encoding.UTF8.GetByteCount(str);
|
||||
|
||||
if (byteCount >= 1 << 16)
|
||||
throw new ArgumentException($"byteCount({byteCount})");
|
||||
|
||||
stream.EncodeUnsignedVariableByteInteger((ulong)byteCount);
|
||||
|
||||
byte[] tmp = BufferPool.Get(byteCount, true);
|
||||
|
||||
Encoding.UTF8.GetBytes(str, 0, str.Length, tmp, 0);
|
||||
stream.Write(tmp, 0, byteCount);
|
||||
|
||||
BufferPool.Release(tmp);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.WriteByte(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ReadLengthPrefixedString(this Stream stream)
|
||||
{
|
||||
int strLength = (int)stream.DecodeUnsignedVariableByteInteger();
|
||||
string result = null;
|
||||
|
||||
if (strLength != 0)
|
||||
{
|
||||
byte[] buffer = BufferPool.Get(strLength, true);
|
||||
|
||||
stream.Read(buffer, 0, strLength);
|
||||
result = System.Text.Encoding.UTF8.GetString(buffer, 0, strLength);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void EncodeUnsignedVariableByteInteger(this Stream encodeTo, ulong value)
|
||||
{
|
||||
if (value < 0)
|
||||
throw new NotSupportedException($"Can't encode negative value({value:N0})!");
|
||||
|
||||
byte encodedByte;
|
||||
do
|
||||
{
|
||||
encodedByte = (byte)(value % 128);
|
||||
value /= 128;
|
||||
// if there are more data to encode, set the top bit of this byte
|
||||
if (value > 0)
|
||||
encodedByte = (byte)(encodedByte | 128);
|
||||
|
||||
encodeTo.WriteByte(encodedByte);
|
||||
}
|
||||
while (value > 0);
|
||||
}
|
||||
|
||||
public static ulong DecodeUnsignedVariableByteInteger(this Stream decodeFrom)
|
||||
{
|
||||
ulong multiplier = 1;
|
||||
ulong value = 0;
|
||||
byte encodedByte = 0;
|
||||
do
|
||||
{
|
||||
encodedByte = (byte)decodeFrom.ReadByte();
|
||||
value += (ulong)((ulong)(encodedByte & 127) * multiplier);
|
||||
multiplier *= 128;
|
||||
} while ((encodedByte & 128) != 0);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void EncodeSignedVariableByteInteger(this Stream encodeTo, long value)
|
||||
{
|
||||
bool more = true;
|
||||
|
||||
while (more)
|
||||
{
|
||||
byte chunk = (byte)(value & 0x7fL); // extract a 7-bit chunk
|
||||
value >>= 7;
|
||||
|
||||
bool signBitSet = (chunk & 0x40) != 0; // sign bit is the msb of a 7-bit byte, so 0x40
|
||||
more = !((value == 0 && !signBitSet) || (value == -1 && signBitSet));
|
||||
if (more) { chunk |= 0x80; } // set msb marker that more bytes are coming
|
||||
|
||||
encodeTo.WriteByte(chunk);
|
||||
};
|
||||
}
|
||||
|
||||
public static long DecodeSignedVariableByteInteger(this Stream stream)
|
||||
{
|
||||
long value = 0;
|
||||
int shift = 0;
|
||||
bool more = true;
|
||||
bool signBitSet = false;
|
||||
|
||||
while (more)
|
||||
{
|
||||
var next = stream.ReadByte();
|
||||
if (next < 0)
|
||||
throw new InvalidOperationException("Unexpected end of stream");
|
||||
|
||||
byte b = (byte)next;
|
||||
|
||||
more = (b & 0x80) != 0; // extract msb
|
||||
signBitSet = (b & 0x40) != 0; // sign bit is the msb of a 7-bit byte, so 0x40
|
||||
|
||||
long chunk = b & 0x7fL; // extract lower 7 bits
|
||||
value |= chunk << shift;
|
||||
shift += 7;
|
||||
};
|
||||
|
||||
// extend the sign of shorter negative numbers
|
||||
if (shift < (sizeof(long) * 8) && signBitSet) { value |= -1L << shift; }
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 031b34e0ebacb3649a074863a87dd5ed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user