disposable_resource_management 4.1.0
disposable_resource_management: ^4.1.0 copied to clipboard
Provides base types and utilities for managing the loading and disposal of resources.
Provides base types and utilities for managing the loading and disposal of resources, for example when working with packages that do so via ffi such as flutter_soloud.
Set-up #
Just add following dependencies to your pubspec.yaml:
dependencies:
disposable_resource_management: ^4.0.0
Usage #
AsyncDisposableMixin (or DisposableMixin in scenarios where disposal is
synchronous) can be used to streamline the implementation of disposable types:
class SomeFFIWrapper with AsyncDisposableMixin {
final SomeAsyncFFIInteropService _ffi;
final int _handle;
SomeFFIWrapper._(int handle, SomeAsyncFFIInteropService ffi)
: _handle = handle,
_ffi = ffi;
Future<void> doSomething() {
// Since AsyncDisposableMixin implements the isDisposed property for us, we
// can use it for checks like this to make sure objects are not used after
// disposal.
if (isDisposed) {
throw StateError('Connot use disposed resource.');
}
return _ffi.doSomethingWithResource(_handle);
}
/// Thanks to [AsyncDisposableMixin] the object will not throw if accidentally
/// disposed multiple times, as the [onDisposeAsync] logic will only be run
/// the first time [disposeAsync] is called.
@protected
@override
Future<void> onDisposeAsync() =>
_ffi.asynchronouslyReleaseUnmanagedResource(_handle);
static Future<SomeFFIWrapper> create(SomeAsyncFFIInteropService ffi) async {
final handle = await ffi.asynchronouslyObtainUnmanagedResource();
return SomeFFIWrapper._(handle, ffi);
}
}
AsyncResourceManager (or ResourceManager in scenarios where obtaining +
releasing the resource is synchronous) can then be used to manage loading and
disposal of a resource via tokens, similar to how reference counters work in
languages like C++:
class SomeService with AsyncDisposableMixin {
AsyncResourceToken<SomeFFIWrapper> token;
SomeService(this.token);
// ...
@protected
@override
Future<void> onDisposeAsync() => token.disposeAsync();
}
void main() async {
final ffi = SomeAsyncFFIInteropService();
final resourceManager = AsyncResourceManager<SomeFFIWrapper>(
loadResource: () => SomeFFIWrapper.create(ffi),
releaseResource: (wrapper) => wrapper.disposeAsync(),
);
// The resource gets obtained on the first obtainToken() call.
final service1 = SomeService(await resourceManager.obtainToken());
final service2 = SomeService(await resourceManager.obtainToken());
// The resource will not be released yet since service2 still has an
// un-disposed token.
await service1.disposeAsync();
// The resource is now released when service2's token gets disposed.
await service2.disposeAsync();
// This obtains the resource again
final token1 = await resourceManager.obtainToken();
// We can also propagate the token to get another token.
// This is done synchronously, since propagation isn't allowed for disposed
// tokens and therefore does not require loading the resource as it is
// guaranteed to already be loaded.
final token2 = token1.propagate();
// The resource will not be released yet because the propagated token2 still
// is not disposed.
await token1.disposeAsync();
// The resource gets released again when all tokens for the resource are
// disposed.
await token2.disposeAsync();
}