Skip to content

Commit

Permalink
Refactored Promise.Merge(Settled) APIs to use new ref field in ref …
Browse files Browse the repository at this point in the history
…structs feature.

Added net8.0 build target (net7.0 is out of support).
  • Loading branch information
timcassell committed Jul 23, 2024
1 parent a6d5723 commit da8856c
Show file tree
Hide file tree
Showing 6 changed files with 510 additions and 1,176 deletions.
203 changes: 162 additions & 41 deletions Package/Core/Promises/Internal/MergeInternal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
using System.Runtime.CompilerServices;
using System.Threading;

#pragma warning disable IDE0074 // Use compound assignment
#pragma warning disable IDE0090 // Use 'new(...)'
#pragma warning disable IDE0251 // Make member 'readonly'

namespace Proto.Promises
{
Expand All @@ -32,74 +33,194 @@ internal readonly unsafe struct GetResultDelegate<TResult>
internal delegate void GetResultDelegate<TResult>(PromiseRefBase handler, int index, ref TResult result);
#endif

[MethodImpl(InlineOption)]
internal static void PrepareForMerge<TResult>(Promise promise, in TResult result, ref uint pendingCount,
ref PromiseRefBase.MergePromise<TResult> mergePromise, GetResultDelegate<TResult> getResultDelegate)
internal ref struct MergePreparer<TResult>
{
if (promise._ref != null)
private PromiseRefBase.MergePromise<TResult> _promise;
// ref fields are only supported in .Net 7 or later.
// We can fake it by using a Span<T> in .Net Standard 2.1 or later.
// The Span nuget package for .Net Standard 2.0 doesn't include MemoryMarshal, so we can't use it, unfortunately.
// TODO: update compilation symbol when Unity adopts .Net Core.
#if NET7_0_OR_GREATER
private ref TResult _resultRef;
#elif NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER || NETCOREAPP
private Span<TResult> _resultRef;
#endif
private readonly GetResultDelegate<TResult> _getResultDelegate;
private uint _pendingCount;

// Annoyingly, we can't return struct fields by reference, or assign struct field reference to a ref field, so we have to pass in the initial result.
[MethodImpl(InlineOption)]
internal MergePreparer(ref TResult initialResult, GetResultDelegate<TResult> getResultDelegate)
{
_promise = null;
#if NET7_0_OR_GREATER
_resultRef = ref initialResult;
#elif NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER || NETCOREAPP
_resultRef = System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref initialResult, 1);
#endif
_getResultDelegate = getResultDelegate;
_pendingCount = 0;
}

[MethodImpl(InlineOption)]
internal ref TResult GetResultRef(ref TResult initialResultRef)
#if NET7_0_OR_GREATER
=> ref _resultRef;
#elif NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER || NETCOREAPP
=> ref _resultRef[0];
#else
=> ref _promise == null ? ref initialResultRef : ref _promise._result;
#endif

[MethodImpl(InlineOption)]
private void PreparePending(in TResult initialResult)
{
checked { ++pendingCount; }
if (mergePromise == null)
checked { ++_pendingCount; }
if (_promise == null)
{
mergePromise = PromiseRefBase.GetOrCreateMergePromise(result, getResultDelegate);
_promise = PromiseRefBase.GetOrCreateMergePromise(initialResult, _getResultDelegate);
#if NET7_0_OR_GREATER
_resultRef = ref _promise._result;
#elif NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER || NETCOREAPP
_resultRef = System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref _promise._result, 1);
#endif
}
mergePromise.AddWaiter(promise._ref, promise._id);
}
}

[MethodImpl(InlineOption)]
internal static void PrepareForMerge<T, TResult>(Promise<T> promise, ref T value, in TResult result, ref uint pendingCount, int index,
ref PromiseRefBase.MergePromise<TResult> mergePromise, GetResultDelegate<TResult> getResultDelegate)
{
if (promise._ref == null)
[MethodImpl(InlineOption)]
internal void Prepare(Promise promise, in TResult initialResult)
{
value = promise._result;
if (promise._ref != null)
{
PreparePending(initialResult);
_promise.AddWaiter(promise._ref, promise._id);
}
}
else

[MethodImpl(InlineOption)]
internal void Prepare<T>(Promise<T> promise, ref T value, in TResult initialResult, int index)
{
checked { ++pendingCount; }
if (mergePromise == null)
if (promise._ref == null)
{
value = promise._result;
}
else
{
mergePromise = PromiseRefBase.GetOrCreateMergePromise(result, getResultDelegate);
PreparePending(initialResult);
_promise.AddWaiterWithIndex(promise._ref, promise._id, index);
}
mergePromise.AddWaiterWithIndex(promise._ref, promise._id, index);
}
}

[MethodImpl(InlineOption)]
internal static void PrepareForMergeSettled<TResult>(Promise promise, in TResult result, ref uint pendingCount, int index,
ref PromiseRefBase.MergeSettledPromise<TResult> mergePromise, GetResultDelegate<TResult> getResultDelegate)
{
if (promise._ref != null)
[MethodImpl(InlineOption)]
internal Promise<TResult> ToPromise(in TResult initialResult)
{
checked { ++pendingCount; }
if (mergePromise == null)
if (_promise == null)
{
mergePromise = PromiseRefBase.GetOrCreateMergeSettledPromise(result, getResultDelegate);
return Promise.Resolved(initialResult);
}
mergePromise.AddWaiterWithIndex(promise._ref, promise._id, index);
_promise.MarkReady(_pendingCount);
return new Promise<TResult>(_promise, _promise.Id);
}
}

[MethodImpl(InlineOption)]
internal static void PrepareForMergeSettled<T, TResult>(Promise<T> promise, ref Promise<T>.ResultContainer value, in TResult result, ref uint pendingCount, int index,
ref PromiseRefBase.MergeSettledPromise<TResult> mergePromise, GetResultDelegate<TResult> getResultDelegate)
internal ref struct MergeSettledPreparer<TResult>
{
if (promise._ref == null)
private PromiseRefBase.MergeSettledPromise<TResult> _promise;
// ref fields are only supported in .Net 7 or later.
// We can fake it by using a Span<T> in .Net Standard 2.1 or later.
// The Span nuget package for .Net Standard 2.0 doesn't include MemoryMarshal, so we can't use it, unfortunately.
// TODO: update compilation symbol when Unity adopts .Net Core.
#if NET7_0_OR_GREATER
private ref TResult _resultRef;
#elif NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER || NETCOREAPP
private Span<TResult> _resultRef;
#endif
private readonly GetResultDelegate<TResult> _getResultDelegate;
private uint _pendingCount;

// Annoyingly, we can't return struct fields by reference, or assign struct field reference to a ref field, so we have to pass in the initial result.
[MethodImpl(InlineOption)]
internal MergeSettledPreparer(ref TResult initialResult, GetResultDelegate<TResult> getResultDelegate)
{
value = promise._result;
_promise = null;
#if NET7_0_OR_GREATER
_resultRef = ref initialResult;
#elif NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER || NETCOREAPP
_resultRef = System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref initialResult, 1);
#endif
_getResultDelegate = getResultDelegate;
_pendingCount = 0;
}

[MethodImpl(InlineOption)]
internal ref TResult GetResultRef(ref TResult initialResultRef)
#if NET7_0_OR_GREATER
=> ref _resultRef;
#elif NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER || NETCOREAPP
=> ref _resultRef[0];
#else
=> ref _promise == null ? ref initialResultRef : ref _promise._result;
#endif

[MethodImpl(InlineOption)]
private void PreparePending(in TResult initialResult)
{
checked { ++_pendingCount; }
if (_promise == null)
{
_promise = PromiseRefBase.GetOrCreateMergeSettledPromise(initialResult, _getResultDelegate);
#if NET7_0_OR_GREATER
_resultRef = ref _promise._result;
#elif NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER || NETCOREAPP
_resultRef = System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref _promise._result, 1);
#endif
}
}

[MethodImpl(InlineOption)]
internal void Prepare(Promise promise, in TResult initialResult, int index)
{
if (promise._ref != null)
{
PreparePending(initialResult);
_promise.AddWaiterWithIndex(promise._ref, promise._id, index);
}
}

[MethodImpl(InlineOption)]
internal void Prepare<T>(Promise<T> promise, ref Promise<T>.ResultContainer value, in TResult initialResult, int index)
{
if (promise._ref == null)
{
value = promise._result;
}
else
{
PreparePending(initialResult);
_promise.AddWaiterWithIndex(promise._ref, promise._id, index);
}
}
else

[MethodImpl(InlineOption)]
internal Promise<TResult> ToPromise(in TResult initialResult)
{
checked { ++pendingCount; }
if (mergePromise == null)
if (_promise == null)
{
mergePromise = PromiseRefBase.GetOrCreateMergeSettledPromise(result, getResultDelegate);
return Promise.Resolved(initialResult);
}
mergePromise.AddWaiterWithIndex(promise._ref, promise._id, index);
_promise.MarkReady(_pendingCount);
return new Promise<TResult>(_promise, _promise.Id);
}
}

[MethodImpl(InlineOption)]
internal static MergePreparer<TResult> CreateMergePreparer<TResult>(ref TResult initialResult, GetResultDelegate<TResult> getResultDelegate)
=> new MergePreparer<TResult>(ref initialResult, getResultDelegate);

[MethodImpl(InlineOption)]
internal static MergeSettledPreparer<TResult> CreateMergeSettledPreparer<TResult>(ref TResult initialResult, GetResultDelegate<TResult> getResultDelegate)
=> new MergeSettledPreparer<TResult>(ref initialResult, getResultDelegate);

partial class PromiseRefBase
{
#if !PROTO_PROMISE_DEVELOPER_MODE
Expand Down
Loading

0 comments on commit da8856c

Please sign in to comment.