Skip to content

Commit

Permalink
Merge pull request #103 from timcassell/rel_2_2_0
Browse files Browse the repository at this point in the history
v2.2.0
  • Loading branch information
timcassell authored Aug 6, 2022
2 parents fd4ca74 + fa6de2d commit fe43b33
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 26 deletions.
21 changes: 18 additions & 3 deletions DeveloperNotes.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
For each new release, update the version in both `ProtoPromise.csproj` and `package.json`.
For each new release...


VERY IMPORTANT - Before doing any of this, run unit tests locally on the IL2CPP player (standalone) to make sure no crashes occur and all tests pass. IL2CPP can be quite finnicky, and unfortunately, the tests cannot be run in CI.
Also test the demo scene in Unity 5.5 to make sure it works (no need to run tests as the test runner didn't exist back then).


Update the version in both `ProtoPromise.csproj` and `package.json`.

To create the UnityPackage for releases:

1. Edit `README.md` to remove the `## Package Installation` and `## Latest Updates` sections, then convert it to `README.pdf` (can use https://www.markdowntopdf.com/) and place it in the `ProtoPromise_Unity\Assets\Plugins\ProtoPromise` directory (discard the changes to `README.md` before git commit).
1. Edit `README.md` to remove the `## Package Installation` and `## Latest Updates` sections, then convert it to `README.pdf` and place it in the `ProtoPromise_Unity\Assets\Plugins\ProtoPromise` directory (discard the changes to `README.md` before git commit).
2. Repeat step 1 for `ReleaseNotes.md`.
3. In the Unity editor, right-click the `ProtoPromise` folder and click `Export Package...`, uncheck `Include dependencies`, include everything, then click `Export...` and name the exported file `ProtoPromise.unitypackage`.
4. Upload `ProtoPromise.unitypackage` with the release on GitHub.
Expand All @@ -12,4 +19,12 @@ To create the UnityPackage for releases:
Unity version may need to be updated to use the earliest version that the Asset Store Tools supports.


By default, ProtoPromise types are marked with the `DebuggerNonUserCodeAttribute`, making it impossible to step into the library code with Visual Studio's debugger. To disable this (or rather, to enable debugging), define the compiler symbol `PROTO_PROMISE_DEVELOPER_MODE` in Unity, or change `<DeveloperMode>` to `true` in the non-unity csproj.
By default, ProtoPromise types are marked with the `DebuggerNonUserCodeAttribute`, making it impossible to step into the library code with Visual Studio's debugger. To disable this (or rather, to enable debugging), define the compiler symbol `PROTO_PROMISE_DEVELOPER_MODE` in Unity, or change `<DeveloperMode>` to `true` in the non-unity csproj.


To convert markdown to pdf:

1. grip README.md --export README.html
2. Use browser to print html to pdf
3. Merge the pages to a single page (can use an online tool).
4. Crop the outline (including the title) that grip created.
2 changes: 1 addition & 1 deletion ProtoPromise/ProtoPromise.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFrameworks>net35;net40;net45;net47;netstandard2.0;netstandard2.1;netcoreapp2.1;net5.0;net6.0</TargetFrameworks>
<DefineConstants>CSHARP_7_3_OR_NEWER</DefineConstants>
<Configurations>Release;Debug;Release_NoProgress;Debug_NoProgress</Configurations>
<Version>2.1.0</Version>
<Version>2.2.0</Version>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<!--Only for function pointers in await override implementation. All other code should be C# 4 compatible for old Unity versions.-->
<LangVersion>9</LangVersion>
Expand Down
1 change: 1 addition & 0 deletions ProtoPromise/nuget/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Robust and efficient library for management of asynchronous operations.
- .Then API and async/await
- Easily switch to foreground or background context
- Combine async operations
- Circular await detection
- CLS compliant

This library was built to work in all C#/.Net ecosystems, including Unity, Mono, .Net Framework, .Net Core, UI frameworks, and AOT compilation. It is CLS compliant, so it is not restricted to only C#, and will work with any .Net language.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "com.timcassell.protopromise",
"version": "2.1.0",
"version": "2.2.0",
"displayName": "ProtoPromise",
"description": "Robust and efficient library for management of asynchronous operations.",
"changelogUrl": "https://github.com/timcassell/ProtoPromise/blob/master/ReleaseNotes.md",
Expand Down
59 changes: 41 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Robust and efficient library for management of asynchronous operations.
- .Then API and async/await
- Easily switch to foreground or background context
- Combine async operations
- Circular await detection
- CLS compliant

This library was built to work in all C#/.Net ecosystems, including Unity, Mono, .Net Framework, .Net Core, UI frameworks, and AOT compilation. It is CLS compliant, so it is not restricted to only C#, and will work with any .Net language.
Expand All @@ -26,13 +27,17 @@ This library took inspiration from [ES6 Promises](https://developer.mozilla.org/

## Latest Updates

## v 2.1.0 - June 19, 2022
## v 2.2.0 - August 6, 2022

- Added `AsyncLocal<T>` support in `async Promise` functions.
- Added `ValueTask(<T>)` interoperability.
- Added `System.Threading.CancellationToken` interoperability.
- 2x - 3x performance improvement for promises and cancelation tokens.
- Reduced memory consumption of pending promises.
- Added `Promise(<T>).AwaitWithProgress(maxProgress)` API to use current progress instead of passing in minProgress.
- Added `Promise(<T>).{RaceWithIndex, FirstWithIndex}` APIs to be able to tell which promise won the race.
- Added `Promise(<T>).WaitAsync(CancelationToken)` APIs, and added optional `CancelationToken` arguments to existing `WaitAsync` APIs.
- Added optional `bool forceAsync` arguments to existing `WaitAsync` and other APIs that allow changing context.
- Fixed `CancelationToken` callbacks not being invoked after they are registered after the original `System.Threading.CancellationTokenSource` has been reset (.Net 6.0+).
- Fixed a deadlock in `PromiseSynchronizationContext.Send` if the callback throws an exception. The exception is rethrown in .Net 4.5+.
- Fixed `WaitAsync` and `Progress` if null `SynchronizationContext` is passed in.
- Fixed compile errors in Unity 5.
- Slightly increased performance and decreased memory.

See [Release Notes](ReleaseNotes.md) for the full changelog.

Expand Down Expand Up @@ -504,6 +509,18 @@ async Promise Func()
}
```

or

```cs
async Promise Func()
{
await Download("google.com").AwaitWithProgress(0.25f); // <---- This will report 0.0f - 0.25f
await WaitForSeconds(1f).AwaitWithProgress(0.5f); // <---- This will report 0.25f - 0.5f
await Download("bing.com").AwaitWithProgress(0.75f); // <---- This will report 0.5f - 0.75f
await WaitForSeconds(1f).AwaitWithProgress(1f); // <---- This will report 0.75f - 1.0f
}
```

## Combining Multiple Async Operations

### All Parallel
Expand Down Expand Up @@ -633,19 +650,25 @@ You can check whether the token is already canceled:
public IEnumerator FuncEnumerator(CancelationToken token)
{
bool retained = token.TryRetain();
while (!token.IsCancelationRequested)
try
{
Console.Log("Doing something");
if (DoSomething())
while (!token.IsCancelationRequested)
{
yield break;
Console.Log("Doing something");
if (DoSomething())
{
yield break;
}
yield return null;
}
yield return null;
Console.Log("token was canceled");
}
Console.Log("token was canceled");
if (retained)
finally
{
token.Release();
if (retained)
{
token.Release();
}
}
}
```
Expand Down Expand Up @@ -756,7 +779,7 @@ Normally, an `Exception` thrown in an `onResolved` or `onRejected` callback will

### Error Retries and Async Recursion

What I especially love above this system is you can implement retries through a technique I call "Asynchronous Recursion".
What I especially love above this system is you can implement retries through asynchronous recursion.

```cs
public Promise<string> Download(string url, int maxRetries = 0)
Expand Down Expand Up @@ -797,7 +820,7 @@ public async Promise<string> Download(string url, int maxRetries = 0)
}
```

Async recursion is just as powerful as regular recursion, but it is also just as dangerous, if not more. If you mess up on regular recursion, your program will immediately crash from a `StackOverflowException`. Async recursion with this library will never crash from a stack overflow due to the iterative implementation, however if you don't do it right, it will eventually crash from an `OutOfMemoryException` due to each call waiting for the next and creating a new promise each time, consuming your heap space.
Async recursion is just as powerful as regular recursion, but it is also just as dangerous. If you mess up on regular recursion, your program will immediately crash from a `StackOverflowException`. You may prevent stack overflows with async recursion by using a [context switching API](#switching-execution-context) with the `forceAsync` flag set to true. However, you should be aware that if your async recursion continues forever, your program will eventually crash from an `OutOfMemoryException` due to each call waiting for the next and creating a new promise each time, consuming your heap space.
Because promises can remain pending for an indeterminate amount of time, this error can potentially take a long time to show itself and be difficult to track down. So be very careful when implementing async recursion, and remember to always have a base case!

Of course, async functions are powerful enough where this retry behavior can be done in a loop without blowing up the heap.
Expand Down Expand Up @@ -826,7 +849,7 @@ Retry:

Most promises can only be awaited once, and if they are not awaited, they must be returned or forgotten (see [Forget](#forget)).
You can preserve a promise so that it can be awaited multiple times via the `promise.Preserve()` API. When you are finished with the promise, you must call `promise.Forget()`.
Callbacks added to a preserved promise which will be invoked in the order that they are added.
Callbacks added to a preserved promise will be invoked in the order that they are added.

Note: a preserved promise should not be returned from a public API, because the consumer could immediately call `Forget()` and invalidate the promise. Instead, you should use `promise.Duplicate()` to get a promise that will adopt its state, but can only be awaited once.

Expand All @@ -853,7 +876,7 @@ public Promise<string> Download(string url, int maxRetries = 0)
}
```

When the C# compiler sees a lamda expression that does not capture/close any variables, it will cache the delegate statically, so there is only one instance in the program. If the lambda only captures `this`, it's not quite as bad as capturing local variables, as the compiler will generate a cached delegate in the class. This means there is one delegate per instance. We can reduce that to one delegate in the program by passing `this` as the capture value.
When the C# compiler sees a lamda expression that does not capture/close any variables, it will cache the delegate statically, so there is only one instance in the program, and no extra memory will be allocated every time it's used.

Note: Visual Studio will tell you what variables are captured/closed if you hover the `=>`. You can use that information to optimize your delegates. In C# 9 and later, you can use the `static` modifier on your lambdas so that the compiler will not let you accidentally capture variables the expensive way.

Expand Down
29 changes: 26 additions & 3 deletions ReleaseNotes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
# Release Notes

## v 2.2.0 - August 6, 2022

Enhancements:

- Added `Promise(<T>).AwaitWithProgress(maxProgress)` API to use current progress instead of passing in minProgress.
- Added `Promise(<T>).{RaceWithIndex, FirstWithIndex}` APIs to be able to tell which promise won the race.
- Added `Promise(<T>).WaitAsync(CancelationToken)` APIs, and added optional `CancelationToken` arguments to existing `WaitAsync` APIs.
- Added optional `bool forceAsync` arguments to existing `WaitAsync` and other APIs that allow changing context.

Fixes:

- Fixed `CancelationToken` callbacks not being invoked after they are registered after the original `System.Threading.CancellationTokenSource` has been reset (.Net 6.0+).
- Fixed a deadlock in `PromiseSynchronizationContext.Send` if the callback throws an exception. The exception is rethrown in .Net 4.5+.
- Fixed `WaitAsync` and `Progress` if null `SynchronizationContext` is passed in.
- Fixed compile errors in Unity 5.

Optimizations:

- Slightly increased performance and decreased memory (at the cost of no longer unwinding the stack, which users can now do with the `forceAsync` flag).

Misc:

- Completely removed internal stacktraces in .Net 6 or later.
- Added net6.0 build target.

## v 2.1.0 - June 19, 2022

Enhancements:
Expand Down Expand Up @@ -70,7 +95,6 @@ Enhancements:
- Added `CancelationToken.(Try)Register<TCancelable>(TCancelable cancelable) where TCancelable : ICancelable`.
- Added static `CancelationToken.Canceled()` to get a token already in the canceled state without allocating.
- Added `Promise(<T>).Progress<TProgress>(TProgress progressListener, ...) where TProgress : IProgress<float>)` overload.
- Added `Promise.Config.IsProgressEnabled`.
- Added `Promise(<T>).WaitAsync(SynchronizationOption)` and `Promise(<T>).WaitAsync(SynchronizationContext)` to schedule the next callback/await on the desired context. (Continuations now execute synchronously without `WaitAsync`.)
- Added `Promise.Config.ForegroundContext` and `Promise.Config.BackgroundContext` to compliment `SynchronizationOption`s.
- Added `Promise.Run` static functions.
Expand Down Expand Up @@ -137,9 +161,8 @@ Misc:
- Added support for installing from a git url in Unity's package manager. https://github.com/timcassell/ProtoPromise.git?path=ProtoPromise_Unity/Assets/Plugins/ProtoPromise
- Added `InvalidArgumentException`.
- Fixed FormatStacktrace in Unity when a stack frame is captured that it can't inspect.
- Added version `2.0.0` to csproj with appending `.0` without progress or `.1` with progress.
- Added `PromiseSynchronizationContext` and updated `PromiseBehaviour` to utilize it.
- Change `UnreleasedObjectException` to `UnobservedPromiseException` when a promise is garbage collected without being awaited or forgotten.
- Changed `UnreleasedObjectException` to `UnobservedPromiseException` when a promise is garbage collected without being awaited or forgotten.

## v 1.0.3 - December 11, 2021

Expand Down

0 comments on commit fe43b33

Please sign in to comment.