Giter VIP home page Giter VIP logo

Comments (6)

timcassell avatar timcassell commented on May 27, 2024

I'm thinking AwaitWithProgress(float min, float max) would be a better method name. Then the other ConfigureAwait with cancelation token proposal #11 can change to WaitAsync with more options like what TPL is doing dotnet/runtime#48842.

from protopromise.

timcassell avatar timcassell commented on May 27, 2024

Placing this here to remove it from the source until it's implemented...

Unfortunately, we can't do a simple if (typeof(TAwaiter) == typeof(PromiseAwaiter<T>)), because we don't have access to the <T> type. (This would work for PromiseAwaiter without a generic type, though.)

internal interface IPromiseAwaiter
{
    void AwaitOnCompleted(Internal.AsyncPromiseRef asyncPromiseRef);
}

PromiseAwaiters implement this interface. This can be used to subscribe to progress for the async Promise().

public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
    where TAwaiter : INotifyCompletion
    where TStateMachine : IAsyncStateMachine
{
    if (typeof(TAwaiter).IsValueType && typeof(IPromiseAwaiter).IsAssignableFrom(typeof(TAwaiter)))
    {
        AwaitOverrider<TAwaiter>.awaitOptimizer.AwaitOnCompleted(ref awaiter, _promise);
    }
    else
    {
        awaiter.OnCompleted(_promise.GetContinuation(ref stateMachine));
    }
}

In the builder, the IsValueType and IsAssignableFrom can theoretically be constant-optimized by the JIT/AOT compiler. IsValueType I believe is already optimized this way in some runtimes/compilers, while IsAssignableFrom I don't think is, but there is an open issue in .Net runtime to add that optimization. In any case, the IsValueType helps it avoid that check when it's not necessary.

internal abstract class AwaitOverrider<T> where T : INotifyCompletion
{
    internal static AwaitOverrider<T> awaitOptimizer;

    private AwaitOverrider() { }

    internal static void Create<TAwaiter>() where TAwaiter : struct, T, ICriticalNotifyCompletion, IPromiseAwaiter
    {
        awaitOptimizer = new Impl<TAwaiter>();
    }

    internal abstract void AwaitOnCompleted(ref T awaiter, Internal.AsyncPromiseRef asyncPromiseRef);

    private sealed class Impl<TAwaiter> : AwaitOverrider<T> where TAwaiter : struct, T, IPromiseAwaiter
    {
        internal override void AwaitOnCompleted(ref T awaiter, Internal.AsyncPromiseRef asyncPromiseRef)
        {
            if (typeof(T) == typeof(TAwaiter))
            {
                Unsafe.As<T, TAwaiter>(ref awaiter).AwaitOnCompleted(asyncPromiseRef);
                return;
            }
            throw new System.InvalidOperationException("type T {" + typeof(T).FullName + "} is not TAwaiter {" + typeof(TAwaiter).FullName + "}");
        }
    }
}

This is the meat of how it works. typeof(T) == typeof(TAwaiter) is already optimized away by the JIT/AOT compiler. This also takes advantage of Unsafe.As. I'm not sure the best way of using that in Unity, if there's a way to link to the Unsafe assembly as a dependency without including the dll for old runtimes. If necessary, we can do a (TAwaiter)(object) awaiter cast (.Net 4.0+ optimizes that operation to not box, though we still end up making a copy of the struct).

static PromiseAwaiter()
{
    AwaitOverrider<PromiseAwaiter<T>>.Create<PromiseAwaiter<T>>();
}

And this is how the awaiters actually subscribe themselves to the AwaitOverrider while still working in AOT/IL2CPP environments without reflection.

from protopromise.

timcassell avatar timcassell commented on May 27, 2024

I actually think I figured out a cleaner way to handle this without Unsafe.As (though the type checks are still necessary in the builder):

internal abstract class AwaitOverrider<T> where T : INotifyCompletion
{
    internal static AwaitOverrider<T> awaitOptimizer;

    internal abstract void AwaitOnCompleted(ref T awaiter, Internal.AsyncPromiseRef asyncPromiseRef);
}

internal sealed class AwaitOverriderImpl<TAwaiter> : AwaitOverrider<TAwaiter> where TAwaiter : struct, ICriticalNotifyCompletion, IPromiseAwaiter
{
    internal static void Create()
    {
        awaitOptimizer = new AwaitOverriderImpl<TAwaiter>();
    }

    internal override void AwaitOnCompleted(ref TAwaiter awaiter, Internal.AsyncPromiseRef asyncPromiseRef)
    {
        awaiter.AwaitOnCompleted(asyncPromiseRef);
    }
}
static PromiseAwaiter()
{
    AwaitOverriderImpl<PromiseAwaiter<T>>.Create();
}

from protopromise.

timcassell avatar timcassell commented on May 27, 2024

It is unfortunate that we have to resort to a virtual function call here and allocate an object to enable the virtual call, when the call itself should be directly callable. If the compiler would allow method overloads in the AsyncMethodBuilder, we could add an overload very easily without all this extra ceremony:

public void AwaitOnCompleted<T, TStateMachine>(ref PromiseAwaiter<T> awaiter, ref TStateMachine stateMachine)
    where TStateMachine : IAsyncStateMachine
{
    awaiter.AwaitOnCompleted(_promise);
}

But that's not possible (the compiler-generated state machine bakes in the default method call).

We could use a delegate instead of a class with a virtual function, but that still has to allocate the delegate and actually costs more than a virtual call.

C# 9 added function pointers, which we can take advantage of to remove the allocation and extra overhead of virtual function/delegates, so it just calls the function directly via its pointer:

internal abstract unsafe class AwaitOverrider<T> where T : INotifyCompletion
{
    internal static delegate*<ref T, Internal.AsyncPromiseRef, void> action;
}

internal sealed unsafe class AwaitOverriderImpl<TAwaiter> : AwaitOverrider<TAwaiter> where TAwaiter : struct, ICriticalNotifyCompletion, IPromiseAwaiter
{
    internal static void Create()
    {
        action = &AwaitOnCompleted;
    }

    private static void AwaitOnCompleted(ref TAwaiter awaiter, Internal.AsyncPromiseRef asyncPromiseRef)
    {
        awaiter.AwaitOnCompleted(asyncPromiseRef);
    }
}

Unfortunately, the call cannot be in-lined, as it's determined at runtime, but I think that's the best we can do until/unless the compiler ever allows overloads in the builder.

from protopromise.

timcassell avatar timcassell commented on May 27, 2024

According to Stephen Toub, the JIT will optimize away ((IPromiseAwaiter)awaiter).AwaitOnCompleted(_promise) to not box, so we can simplify the builder method:

public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
    where TAwaiter : INotifyCompletion
    where TStateMachine : IAsyncStateMachine
{
    if (typeof(TAwaiter).IsValueType && typeof(IPromiseAwaiter).IsAssignableFrom(typeof(TAwaiter)))
    {
        ((IPromiseAwaiter)awaiter).AwaitOnCompleted(_promise);
    }
    else
    {
        awaiter.OnCompleted(_promise.GetContinuation(ref stateMachine));
    }
}

But, we can't rely on that optimization in Unity with Mono or IL2CPP, so we still have to use the AwaitOverrider method to avoid boxing. We can #ifdef to use AwaitOverrider for Unity and this method for other runtimes.

from protopromise.

timcassell avatar timcassell commented on May 27, 2024

I'm thinking of pulling this into v2.0 to be able to create a nuget package with progress enabled (#51). It wouldn't make much sense to have progress enabled by default without async Promise progress support. I will need to do some initial work to see if this can be done like ((IPromiseAwaiter)awaiter).AwaitOnCompleted(_promise) without boxing in Unity which would make this much easier.

[Edit] I just tested in latest Unity, and it still doesn't optimize away the box there (it still allocates). Looks like I'll have to do this the hard way.

from protopromise.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.