新增 FlowScope Git 包雏形
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FlowScope.Resources
|
||||
{
|
||||
public interface IResourceBackend
|
||||
{
|
||||
string Name { get; }
|
||||
|
||||
Task<T> LoadAsync<T>(
|
||||
string key,
|
||||
CancellationToken cancellationToken)
|
||||
where T : class;
|
||||
|
||||
void Release<T>(string key, T asset)
|
||||
where T : class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76d9b18ff17949a987f26418e1f1c978
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace FlowScope.Resources
|
||||
{
|
||||
public interface IResourceGroup : IDisposable
|
||||
{
|
||||
void Add<T>(IResourceHandle<T> handle) where T : class;
|
||||
int Count { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e613834058f49db95c2814ab52d7cc4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace FlowScope.Resources
|
||||
{
|
||||
public interface IResourceHandle<T> : IDisposable where T : class
|
||||
{
|
||||
string Key { get; }
|
||||
T Asset { get; }
|
||||
bool IsDisposed { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f248abc2018449e3be0905dc01ef7e11
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FlowScope.Resources
|
||||
{
|
||||
public interface IResourceService
|
||||
{
|
||||
Task<IResourceHandle<T>> LoadAsync<T>(
|
||||
string key,
|
||||
CancellationToken cancellationToken)
|
||||
where T : class;
|
||||
|
||||
IResourceGroup CreateGroup();
|
||||
}
|
||||
|
||||
public interface ICompletedResourceService
|
||||
{
|
||||
bool TryLoadCompleted<T>(string key, out IResourceHandle<T> handle)
|
||||
where T : class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52a701ee3b744171b5e01e927c83b645
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
|
||||
namespace FlowScope.Resources
|
||||
{
|
||||
internal sealed class ResourceEntry
|
||||
{
|
||||
private readonly Action<string, Type, object> _release;
|
||||
private int _referenceCount;
|
||||
private bool _released;
|
||||
|
||||
public ResourceEntry(string key, object asset, Action<string, object> release)
|
||||
: this(key, asset?.GetType(), asset, (_, _, releasedAsset) => release(key, releasedAsset))
|
||||
{
|
||||
}
|
||||
|
||||
public ResourceEntry(string key, Type assetType, object asset, Action<string, Type, object> release)
|
||||
{
|
||||
Key = string.IsNullOrWhiteSpace(key)
|
||||
? throw new ArgumentException("Resource key is required.", nameof(key))
|
||||
: key;
|
||||
AssetType = assetType ?? throw new ArgumentNullException(nameof(assetType));
|
||||
Asset = asset ?? throw new ArgumentNullException(nameof(asset));
|
||||
_release = release ?? throw new ArgumentNullException(nameof(release));
|
||||
}
|
||||
|
||||
public string Key { get; }
|
||||
public Type AssetType { get; }
|
||||
public object Asset { get; private set; }
|
||||
public int ReferenceCount => _referenceCount;
|
||||
|
||||
public ResourceHandle<T> CreateHandle<T>() where T : class
|
||||
{
|
||||
if (_released)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(ResourceEntry));
|
||||
}
|
||||
|
||||
_referenceCount++;
|
||||
return new ResourceHandle<T>(this);
|
||||
}
|
||||
|
||||
public void ReleaseHandle()
|
||||
{
|
||||
if (_released)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_referenceCount--;
|
||||
if (_referenceCount > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_released = true;
|
||||
var asset = Asset;
|
||||
Asset = null;
|
||||
_release(Key, AssetType, asset);
|
||||
}
|
||||
|
||||
public void ReleaseWithoutHandle()
|
||||
{
|
||||
if (_released)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_released = true;
|
||||
var asset = Asset;
|
||||
Asset = null;
|
||||
_release(Key, AssetType, asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02477283d77540ed9be0b002bb05f37a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FlowScope.Resources
|
||||
{
|
||||
public sealed class ResourceGroup : IResourceGroup
|
||||
{
|
||||
private readonly HashSet<IDisposable> _handles = new();
|
||||
private bool _disposed;
|
||||
|
||||
public int Count => _handles.Count;
|
||||
|
||||
public void Add<T>(IResourceHandle<T> handle) where T : class
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(ResourceGroup));
|
||||
}
|
||||
|
||||
if (handle == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(handle));
|
||||
}
|
||||
|
||||
_handles.Add(handle);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
foreach (var handle in _handles)
|
||||
{
|
||||
handle.Dispose();
|
||||
}
|
||||
|
||||
_handles.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 896ce428279a4740b44cebeb2c0b635e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
|
||||
namespace FlowScope.Resources
|
||||
{
|
||||
public sealed class ResourceHandle<T> : IResourceHandle<T> where T : class
|
||||
{
|
||||
private readonly ResourceEntry _entry;
|
||||
private readonly Action<string, object> _release;
|
||||
private T _asset;
|
||||
|
||||
internal ResourceHandle(ResourceEntry entry)
|
||||
{
|
||||
_entry = entry ?? throw new ArgumentNullException(nameof(entry));
|
||||
Key = entry.Key;
|
||||
_asset = (T)entry.Asset;
|
||||
}
|
||||
|
||||
public ResourceHandle(string key, T asset, Action<string, object> release)
|
||||
{
|
||||
Key = string.IsNullOrWhiteSpace(key)
|
||||
? throw new ArgumentException("Resource key is required.", nameof(key))
|
||||
: key;
|
||||
_asset = asset ?? throw new ArgumentNullException(nameof(asset));
|
||||
_release = release ?? throw new ArgumentNullException(nameof(release));
|
||||
}
|
||||
|
||||
public string Key { get; }
|
||||
public T Asset => IsDisposed ? null : _asset;
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsDisposed = true;
|
||||
var asset = _asset;
|
||||
_asset = null;
|
||||
|
||||
if (_entry != null)
|
||||
{
|
||||
_entry.ReleaseHandle();
|
||||
return;
|
||||
}
|
||||
|
||||
_release(Key, asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab4e98e842c34a09a324062b57b09009
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,239 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FlowScope.Resources
|
||||
{
|
||||
public sealed class ResourceService : IResourceService, ICompletedResourceService
|
||||
{
|
||||
private readonly object _gate = new();
|
||||
private readonly IResourceBackend _backend;
|
||||
private readonly Dictionary<ResourceKey, ResourceEntry> _entries = new();
|
||||
private readonly Dictionary<ResourceKey, SharedLoad> _loads = new();
|
||||
|
||||
public ResourceService(IResourceBackend backend)
|
||||
{
|
||||
_backend = backend ?? throw new ArgumentNullException(nameof(backend));
|
||||
}
|
||||
|
||||
public async Task<IResourceHandle<T>> LoadAsync<T>(
|
||||
string key,
|
||||
CancellationToken cancellationToken)
|
||||
where T : class
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
throw new ArgumentException("Resource key is required.", nameof(key));
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var resourceKey = new ResourceKey(key, typeof(T));
|
||||
SharedLoad sharedLoad;
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
if (_entries.TryGetValue(resourceKey, out var entry))
|
||||
{
|
||||
return entry.CreateHandle<T>();
|
||||
}
|
||||
|
||||
if (!_loads.TryGetValue(resourceKey, out sharedLoad))
|
||||
{
|
||||
sharedLoad = new SharedLoad();
|
||||
_loads.Add(resourceKey, sharedLoad);
|
||||
}
|
||||
|
||||
sharedLoad.WaiterCount++;
|
||||
if (sharedLoad.LoadTask == null)
|
||||
{
|
||||
sharedLoad.LoadTask = LoadAndOwnEntryAsync<T>(resourceKey, key, sharedLoad);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var loaded = await WaitForSharedLoad(sharedLoad.LoadTask, cancellationToken);
|
||||
lock (_gate)
|
||||
{
|
||||
return loaded.CreateHandle<T>();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
if (sharedLoad.WaiterCount > 0)
|
||||
{
|
||||
sharedLoad.WaiterCount--;
|
||||
}
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryLoadCompleted<T>(string key, out IResourceHandle<T> handle)
|
||||
where T : class
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
throw new ArgumentException("Resource key is required.", nameof(key));
|
||||
}
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
if (_entries.TryGetValue(new ResourceKey(key, typeof(T)), out var entry))
|
||||
{
|
||||
handle = entry.CreateHandle<T>();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
handle = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public IResourceGroup CreateGroup()
|
||||
{
|
||||
return new ResourceGroup();
|
||||
}
|
||||
|
||||
private async Task<ResourceEntry> LoadEntryAsync<T>(string key)
|
||||
where T : class
|
||||
{
|
||||
T asset;
|
||||
try
|
||||
{
|
||||
asset = await _backend.LoadAsync<T>(key, CancellationToken.None);
|
||||
}
|
||||
catch (Exception exception) when (exception is not OperationCanceledException)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to load resource '{key}' as {typeof(T).Name} from backend '{_backend.Name}'.",
|
||||
exception);
|
||||
}
|
||||
|
||||
if (asset == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Resource '{key}' returned null for {typeof(T).Name} from backend '{_backend.Name}'.");
|
||||
}
|
||||
|
||||
return new ResourceEntry(
|
||||
key,
|
||||
typeof(T),
|
||||
asset,
|
||||
(entryKey, assetType, releasedAsset) =>
|
||||
ReleaseBackendEntry(entryKey, assetType, (T)releasedAsset));
|
||||
}
|
||||
|
||||
private async Task<ResourceEntry> LoadAndOwnEntryAsync<T>(
|
||||
ResourceKey resourceKey,
|
||||
string key,
|
||||
SharedLoad sharedLoad)
|
||||
where T : class
|
||||
{
|
||||
ResourceEntry entry = null;
|
||||
try
|
||||
{
|
||||
entry = await LoadEntryAsync<T>(key);
|
||||
lock (_gate)
|
||||
{
|
||||
_loads.Remove(resourceKey);
|
||||
if (sharedLoad.WaiterCount > 0)
|
||||
{
|
||||
_entries.Add(resourceKey, entry);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
ReleaseEntry(entry);
|
||||
return entry;
|
||||
}
|
||||
catch
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
_loads.Remove(resourceKey);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<ResourceEntry> WaitForSharedLoad(
|
||||
Task<ResourceEntry> loadTask,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!cancellationToken.CanBeCanceled)
|
||||
{
|
||||
return await loadTask;
|
||||
}
|
||||
|
||||
var cancellation = new TaskCompletionSource<bool>();
|
||||
using (cancellationToken.Register(() => cancellation.TrySetResult(true)))
|
||||
{
|
||||
if (await Task.WhenAny(loadTask, cancellation.Task) == cancellation.Task)
|
||||
{
|
||||
throw new OperationCanceledException(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
return await loadTask;
|
||||
}
|
||||
|
||||
private void ReleaseEntry(ResourceEntry entry)
|
||||
{
|
||||
entry.ReleaseWithoutHandle();
|
||||
}
|
||||
|
||||
private void ReleaseBackendEntry<T>(string key, Type assetType, T asset)
|
||||
where T : class
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
_entries.Remove(new ResourceKey(key, assetType));
|
||||
}
|
||||
|
||||
_backend.Release(key, asset);
|
||||
}
|
||||
|
||||
private sealed class SharedLoad
|
||||
{
|
||||
public Task<ResourceEntry> LoadTask;
|
||||
public int WaiterCount;
|
||||
}
|
||||
|
||||
private readonly struct ResourceKey : IEquatable<ResourceKey>
|
||||
{
|
||||
private readonly string _key;
|
||||
private readonly Type _type;
|
||||
|
||||
public ResourceKey(string key, Type type)
|
||||
{
|
||||
_key = key;
|
||||
_type = type;
|
||||
}
|
||||
|
||||
public bool Equals(ResourceKey other)
|
||||
{
|
||||
return _key == other._key && _type == other._type;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ResourceKey other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return ((_key != null ? _key.GetHashCode() : 0) * 397) ^
|
||||
(_type != null ? _type.GetHashCode() : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d123a5c6220843b18a09611f77e8cd31
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user