This commit is contained in:
2026-06-15 18:18:16 +08:00
parent 97c9fba14e
commit 2b9f134e5f
4164 changed files with 386922 additions and 79 deletions

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8a166a5458783404a94c362f12b90e88
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2a5df3217523b8e468cf636c4b5ee3fa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 07c2801d01675b0479bc135ee8327fd1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b0aa37cc0a1f5054f8ffb64d2cef5c5a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fc04de29f38ec7c48b0517fc0ba4d8b2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7cc4a6484b91bfd489fab519ef4f61eb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1cefb9c5ecaff6a44b77039a56050aac
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e92d1b70cb4146d47b88bfb3f43c49f8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4c788ada3967bfe4dac4ce9e7522dc8c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0d8da4c912810a742a1af50bb90fb26e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c484a2e93b3a84746939c4d2d5cdf8d9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bedd5a1e08a99404fbc747e79b92f17c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7b03ac89356268b40b90d8a46678c699
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 161054408a1ecbe4383c76ea478332ed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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}]";
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dc2cf4b60fcca484e9aafe612ba9ac51
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0bb24b6286ceff24abd558107641cd8b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e3a9d8df83b70404782d3d67884dc1a9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8a63cdf33c00b6443bcbd6aa81aa6562
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e825306d81906834b84135765d5c3174
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 36cd9f102ecc80746bc7ff850f782e32
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ea59aadbd0882aa46a9b5c86d137d70a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 031b34e0ebacb3649a074863a87dd5ed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: