using System; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace Capnp.Rpc { /// /// A skeleton is a wrapper around a capability interface implementation which adapts it in the way it is /// expected by the . /// public abstract class Skeleton: IProvidedCapability { class SkeletonRelinquisher: IDisposable { readonly Skeleton _skeleton; public SkeletonRelinquisher(Skeleton skeleton) { _skeleton = skeleton; _skeleton.Claim(); } public void Dispose() { _skeleton.Relinquish(); } } /// /// Claims ownership on the given capability, preventing its automatic disposal. /// /// Capability interface /// Capability implementation /// A disposable object. Calling Dispose() on the returned instance relinquishes ownership again. public static IDisposable Claim(T impl) where T: class { return new SkeletonRelinquisher(CapabilityReflection.CreateSkeletonInternal(impl)); } /// /// Calls an interface method of this capability. /// /// ID of interface to call /// ID of method to call /// Method arguments ("params struct") /// Cancellation token, indicating when the call should cancelled. /// A Task which will resolve to the call result public abstract Task Invoke(ulong interfaceId, ushort methodId, DeserializerState args, CancellationToken cancellationToken = default); internal abstract void Claim(); internal abstract void Relinquish(); internal void Relinquish(int count) { if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); while (count-- > 0) Relinquish(); } internal virtual void Bind(object impl) { throw new NotSupportedException("Cannot bind"); } internal abstract ConsumedCapability AsCapability(); } /// /// Skeleton for a specific capability interface. /// /// Capability interface public abstract class Skeleton : RefCountingSkeleton, IMonoSkeleton { Func>[] _methods = null!; /// /// Constructs an instance. /// public Skeleton() { } /// /// Populates this skeleton's method table. The method table maps method IDs (which are consecutively numbered from 0 /// onwards) to the underlying capability's method implementations. /// /// The method table. Index is method ID. protected void SetMethodTable(params Func>[] methods) { _methods = methods; } /// /// Gets the underlying capability implementation. /// protected T Impl { get; private set; } = default!; /// /// Gets the ID of the implemented interface. /// public abstract ulong InterfaceId { get; } /// /// Calls an interface method of this capability. /// /// ID of interface to call /// ID of method to call /// Method arguments ("params struct") /// Cancellation token, indicating when the call should cancelled. /// A Task which will resolve to the call result /// This Skeleton was disposed public override async Task Invoke(ulong interfaceId, ushort methodId, DeserializerState args, CancellationToken cancellationToken = default) { if (InterfaceId != InterfaceId) throw new NotImplementedException("Wrong interface id"); if (methodId >= _methods.Length) throw new NotImplementedException("Wrong method id"); return await _methods[methodId](args, cancellationToken); } /// /// Dispose pattern implementation /// protected override void Dispose(bool disposing) { if (disposing && Impl is IDisposable disposable) { disposable.Dispose(); } } internal override void Bind(object impl) { if (Impl != null) throw new InvalidOperationException("Skeleton was already bound"); Impl = (T)impl; } } }