-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement ember concurrency examples #2
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ import { onDestroy } from 'svelte'; | |
import { writable } from 'svelte/store'; | ||
|
||
type SvelteConcurrencyUtils = { | ||
signal: AbortSignal; | ||
abortController: AbortController; | ||
link: <T extends { cancel: () => void }>(task: T) => T; | ||
}; | ||
|
||
|
@@ -52,7 +52,7 @@ export function task<TArgs = undefined, TReturn = unknown>( | |
cancel() { | ||
abort_controller.abort(); | ||
}, | ||
perform(...args: undefined extends TArgs ? [] : [TArgs]) { | ||
perform(...args: unknown[]) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’ve been playing with svelte-concurrency and I encounter something a bit counter-intuitive with the task arguments. As it is on main, it seems that const parent = task(async function* (param: number) {
(...)
return param * 2;
}); Let’s assume you think you have [4] * 2 Surprisingly it works because you can multiply values in arrays in JS, but in my "accelerating buttons" case I had 0 + [1] that resulted to a string
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok this is actually something else: basically this TS code is inferring the type of TArgs. If you don't pass any parameter it will actually be undefined and when you call perfom this type will disallow you to pass any values. But if you pass some argument then the arguments will be the same arguments that you expect in the task function. It doesn't need to be an array, you can pass a single value and ts will infer that you want that kind of value and force you to use the same value in perform. So basically you can do this const my_task = task((value: number)=>{
console.log(value);
});
my_task.perform(4); |
||
abort_controller.signal.removeEventListener('abort', cancel_linked_and_update_store); | ||
abort_controller = new AbortController(); | ||
abort_controller.signal.addEventListener('abort', cancel_linked_and_update_store); | ||
|
@@ -65,7 +65,7 @@ export function task<TArgs = undefined, TReturn = unknown>( | |
try { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const gen_or_value = await gen_or_fun(args as any, { | ||
signal: abort_controller.signal, | ||
abortController: abort_controller, | ||
link, | ||
}); | ||
const is_generator = | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<script lang="ts"> | ||
import { task } from '$lib/task.js'; | ||
|
||
let count: number = 0; | ||
|
||
const incrementBy = task(async function* ([increment]: [increment: number]) { | ||
let speed = 400; | ||
while (true) { | ||
count = count + increment; | ||
await new Promise((r) => setTimeout(r, speed)); | ||
yield; | ||
speed = Math.max(50, speed * 0.8); | ||
} | ||
}); | ||
|
||
</script> | ||
|
||
<p>Hold down the buttons to accelerate:</p> | ||
<p>Count: {count}</p> | ||
<button | ||
on:mousedown={async () => { | ||
await incrementBy.perform(-1); | ||
}} | ||
on:mouseup={async () => { | ||
await incrementBy.cancel(); | ||
}} | ||
>-- Decrease</button> | ||
<button | ||
on:mousedown={async () => { | ||
await incrementBy.perform(1); | ||
}} | ||
on:mouseup={async () => { | ||
await incrementBy.cancel(); | ||
}} | ||
>Increase ++</button> | ||
|
||
<p>This example is inspired by | ||
<a target="_blank" rel="noopener noreferrer" href="http://ember-concurrency.com/docs/examples/increment-buttons"> | ||
ember-concurrency example "Accelerating Increment / Decrement Buttons" | ||
</a> | ||
</p> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
<script lang="ts"> | ||
import { task, type Task } from '$lib/task.js'; | ||
|
||
type Challenger = { | ||
name: string; | ||
progress: number; | ||
}; | ||
|
||
let challengers: Challenger[] = []; | ||
for (let i = 1; i <= 3; i++) { | ||
challengers.push({ | ||
name: `Challenger ${i}`, | ||
progress: 0, | ||
}) | ||
} | ||
|
||
let winner: Challenger|undefined; | ||
|
||
const runTheRace = task(async function* (_, { abortController, link }) { | ||
console.log('Run the race') | ||
let challengerRaces = challengers.map(challenger => { | ||
return link(race).perform(challenger); | ||
}) | ||
let first = await Promise.race(challengerRaces); | ||
console.log(`First arrived is ${first.name}`); | ||
abortController.abort(); | ||
return first; | ||
}); | ||
|
||
const race = task(async function* ([challenger]: [challenger: Challenger]) { | ||
console.log(`${challenger.name} runs`); | ||
while (challenger.progress < 100) { | ||
await new Promise((r) => setTimeout(r, Math.random() * 100 + 100)); | ||
yield; | ||
challenger.progress = Math.min( | ||
100, | ||
Math.floor(challenger.progress + Math.random() * 20), | ||
); | ||
console.log(`${challenger.name} progress: ${challenger.progress}`); | ||
} | ||
return challenger; | ||
}); | ||
|
||
</script> | ||
|
||
<button | ||
on:click={async () => { | ||
winner = await runTheRace.perform(); | ||
}} | ||
>Start race</button> | ||
|
||
<button | ||
on:click={() => { | ||
challengers[0].progress = 50; | ||
challengers[1].progress = 20; | ||
challengers[2].progress = 70; | ||
}} | ||
>Test</button> | ||
Comment on lines
+52
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added this button to check the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you just need to call await tick(); after you update the value in the while loop. |
||
|
||
{#each challengers as challenger} | ||
<div> | ||
<span>{challenger.name}</span> | ||
<progress max="100" value={challenger.progress} /> | ||
</div> | ||
{/each} | ||
|
||
{#if winner} | ||
<p>The winner is {winner.name}</p> | ||
{/if} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure if I understand how
signal
is supposed to be used:In my race case, I wanted a parent task that triggers 3 children tasks, and as soon as one of the children is done, everyone stops.
I couldn't figure out how / when
onDestroy
is supposed to be called, and I couldn't usecancel
because it's kind of an "auto-cancellation", the parent task says in its own body "I am done, I stop all my children". I didn't see how to use the main branch's API to do that, I just wanted a way to run.abort()
for the current function.Considering everything you've already done in the task logic, what do you think is the cleanest way to achieve that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah we didn't implement that kind of concurrency yet. Probably i would just do something like