-
Notifications
You must be signed in to change notification settings - Fork 757
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
Topic/thread reschedule #5038
Topic/thread reschedule #5038
Conversation
A really good new feature, thank you. I'll test it. |
SCClassLibrary/Common/Core/Thread.sc
Outdated
}; | ||
clock = argClock; | ||
}; | ||
if(this === thisThread) { |
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.
if there is a good name for what happens here, I think this should be a method on function, similar to forkIfNeeded
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.
How would it look? If it's a method of Function, then you wouldn't have implicit access to the thread as this
, so maybe it would have to be like this, in context of this PR: { ... }.deferFromThread(this/*.threadPlayer*/)
. (I'm not sure this is the place to bikeshed a new method though.)
(Edit) I'm tempted to suggest a method of Thread/PauseStream; then, using function-style syntax: deferFromThread(aThread) { ... }
(though it reads slightly strangely using message syntax: aThread.deferFromThread { ... }
but that isn't too bad).
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'm not sure this is the place to bikeshed a new method though
As a non native speaker, the meaning of bikeshedding remains obscure to me (I'm pretty sure I've never done it, although I do have a bicycle ;-)).
Joke aside, this definitely is the place to discuss how to factor a problem. This may include a new method, and the general usefulness of such a method. If it turns out to be a large discussion, we can still do this in a separate thread.
I'm tempted to suggest a method of Thread/PauseStream
I think your second version is good, if we assume that the intention is to defer away from or defer off a given thread. This method makes clear what reading the code leaves obscure.
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.
OK, I've done that (and even instinctively named the method deferAwayFrom
).
I have to admit, writing the documentation for the new method renewed my doubts about it (though its usages in both reschedule
implementations do look cleaner). But, let's leave that to the review process.
It's a delicate balance. We've already seen some PRs spiral out of control. This PR isn't in danger of that... I think I probably err on the side of keeping PRs close to the initial scope. I don't mind to change it here. It'll be a couple of days later. |
This forum is not necessarily the adequate place for developer cooperation, as you said, it depends. |
I'd like to add the WIP tag for now -- I just realized that this is implemented for streams based on Routines, but not for any variety of FuncStream. That would be an exceptional usage but for completeness, it should be there. |
I'm afraid I will have to give up on this. I couldn't figure out how to get the intrinsic variables to work. Currently, we have This PR adds FuncStream inherits from Stream, not Thread. That suggests moving I'm also reconsidering that maybe this isn't a big deal. You can play a Pfunc, and rescheduling works because it's really an EventStreamPlayer that's accessing the FuncStream. (
p = Pfunc {
var delta = exprand(0.05, 0.25);
(midinote: rrand(48, 72), dur: delta, legato: 0.1)
}.play;
)
p.reschedule(quant: 3); // works
p.stop; But you can't do the same with FuncStream: (
p = FuncStream({
var delta = exprand(0.05, 0.25);
(midinote: rrand(48, 72), dur: delta, legato: 0.1).play;
delta // return/reschedule
}).play;
)
p.reschedule(quant: 3); // Message 'reschedule' not understood
thisProcess.stop; // it's not a stream player so, no 'stop' ... but in almost 20 years, I've never seen anyone do this -- not least because there's no convenient way to stop it. I had been thinking of the case of Except here:
... but nobody does that either, because of the same problem (no way to stop TL;DR except for abstracting out |
I agree that it would be too much to expect the full scheduling abilities on |
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.
A few minor suggestions.
HelpSource/Classes/Routine.schelp
Outdated
discussion:: | ||
|
||
list:: | ||
## Rescheduling to a different clock will be continuous in terms of seconds, but not necessarily continuous in terms of beats. Time-based patterns such as link::Classes/Pseg::, or the use of link::Classes/Env:: as a stream, are likely to break. |
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.
"are likely to break." is both too strong and too imprecise. Maybe better: "may behave not as expected"?
HelpSource/Classes/Routine.schelp
Outdated
list:: | ||
## Rescheduling to a different clock will be continuous in terms of seconds, but not necessarily continuous in terms of beats. Time-based patterns such as link::Classes/Pseg::, or the use of link::Classes/Env:: as a stream, are likely to break. | ||
|
||
When switching the clock, code::reschedule:: will first calculate the new "awake" time, in beats, based on the given code::quant::. Then it converts that code::beats:: value into the new clock's code::beats:: for the same instant in time. If the old clock's tempo is 1 and the Routine was waiting for 1 beat, the next "awake" should happen after one second. When rescheduling without a code::quant::, the next "awake" will emphasis::still:: happen after one second -- but the code::beats:: value will adjust for the new clock. |
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.
If the old clock's tempo is 1 and the Routine was waiting for 1 beat, the next "awake" should happen after one second.
Not sure I understand. If it "was waiting", then the time has passed -- it should happen instantly?
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.
First question, then, is: Do you think I should explain it in complete, exhaustive detail, or just give a high-level overview and let users infer the behavior from examples?
The complete explanation goes something like this:
There are four relevant moments in time:
A. When the thread last awoke.
B. Now.
C. When the thread is next scheduled to awake.
D. The rescheduled time. This may be the same moment as C, but translated to a different clock; or later than C (on the same or a different clock).
While it's supported to reschedule a thread from within the thread, often the reschedule
caller will be outside the thread. In that case, B is not constrained to line up with any of the routine times: A <= B < C <= D (if B == C, behavior might be undefined).
If it "was waiting", then the time has passed -- it should happen instantly?
"... was waiting for 1 beat" means: at A, a 1.0.wait
call occurred. So we know C = A + 1, and B must be within 1 beat of A. From the reference point of B, the time may not have passed.
Rescheduling does not change C -- even if you reschedule onto a different clock, the thread will awake at time C. I think that's why I used past tense here -- you can't change a previously issued wait
(whereas, if I said it "is waiting," you might think you could make it "be waiting" for a different length of time). (And, if D == translateToOtherClock(C), then the thread will continue from the same moment when it would have awakened, but with a new beats
base and possibly a different tempo.)
I think it should be clear by now why I'm struggling with the right balance between clarity and concision. The above is too much detail for one method's documentation -- but trying to streamline the discussion ends up confusing some crucial details.
HelpSource/Classes/Task.schelp
Outdated
|
||
Switch the Task to a different clock, or a different time, without stopping. See link::Classes/Routine#-reschedule:: for complete documentation. | ||
|
||
NOTE:: If you want to reschedule a Task from within the Task itself, code::thisThread.reschedule(...):: will not work (as it does in a Routine). You must write code::thisThread.threadPlayer.reschedule(...):: instead. |
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.
Maybe better:
If you want to reschedule a Task from within the Task itself, code::thisThread.reschedule(...):: will not work, because the task is what plays the thread. This is unlike in a Routine, which plays itself.
I believe I addressed the review comments. (And, on second thought, I changed that "was waiting" to "is waiting" -- maybe it's OK now? Though that section is difficult to explain.) |
Thank you, this reads very well, and the |
I'd like to take another look at this interface tomorrow, to be sure it will really work for rescheduling. |
I think my suspicion that last night was correct -- my change to TempoClock:nextTimeOnGrid calls IdentityDictionary says Committed a fix. |
fb6d696
to
e94e908
Compare
In my experience, the best thing is always a page of concise examples. |
@telephon @jamshark70 - sorry about the delay on this PR - what is the current consensus? Should we pull this in to 3.13, and if so, anything else needed? |
At this point, I don't remember. It generally looks OK...? |
Yes, I think it is good. |
I resolved a merge conflict in the helpfile (I think). I assume this is okay to merge, but because the conversation was stalled a while back I'll wait a bit before pulling this @telephon @jamshark70 |
IIRC this is ready...? Thanks for fixing the conflict. |
Hi folks, there seems to be an issue with this merge, considering PauseStream/Task when playing on AppClock. Reproduce like this: --> ERROR: Message 'schedAbs' not understood. This is because AppClock doesn't understand schedAbs.. |
@jamshark70 @telephon what do you think about the issue brought up by @woutersnoei ? |
hm, I never really understood why |
@woutersnoei after seeing response from @telephon I checked and this issue was introduced in the current development version of SC, after 3.12, but before this PR. It is reported in #5700. It would be good to fix it before releasing 3.13 as it is a regression (AFAIU). Let's continue the conversation in #5700. |
Purpose and Motivation
New feature suggested in #4903: Make it possible to reschedule a Routine, Task or EventStreamPlayer transparently onto a different clock or to a different (later) time.
Along the way, I found out that our documentation for
nextTimeOnGrid
is quite poor. I haven't updated it yet. It's tangentially related to this PR, so I'm not sure if I should just fold it in here or make another PR. (nextTimeOnGrid
doesn't change substantially here, except for a newreferenceBeat
argument. Default behavior is unchanged.)(There is one unrelated documentation cleanup: Task help said that Condition could not be used within a Task. That hasn't been true for some years, so I took the liberty of removing that remark.)
Types of changes
(The
nextTimeOnGrid
third argument could be a breaking change if someone has created an extension with additional arguments for this method selector. AFAICS within the main class library, there is no breaking change.)To-do list