mahlenmorris / VCVRack

Set of modules for use with VCV Rack 2.0
Other
18 stars 1 forks source link

BASICally - Strange behaviour when code recompiles #34

Closed giohappy closed 5 months ago

giohappy commented 10 months ago

I'm testing BASICally for the first time. Wonderful concept! I'm on Windows with VCV Rack Pro 2.4.1

Probably I'm doing something wrong, but I get unexpected behaviour when I do certain changes to the code of a running patch while it's playing. In paritcular, I was changing the pause_length value of the "Also Sprach 2" preset:

' A trigger to IN9 resets the speed the notes are played.
FOR n = 0 TO 3
  out1 = note[n]
  WAIT pause_length
NEXT

WHEN start() or trigger(in9)
  note[0] = { c3, g3, c4, c4 }  ' The notes in my score.
  pause_length = random(100, 1000)  '  How fast we play the notes.
  RESET  ' Forces main block to restart now
END WHEN

What happens is easier to explain with a video, since many things happen and in (apparently) unpredictable ways. In a few words, it seems that when code recompiles the value assigned to the variable is not always the one that is currently set in code. It seems that a smaller value is assigned, making the sequence run at audio rate. The video will explain this much better...

Am I doing something wrong? It's not clear to me when the code is recompiled. It seems it takes a while to grab the changes. Is there a way to delay recompiliation until a trigger is received?

https://github.com/mahlenmorris/VCVRack/assets/571129/ea77f0db-3e8c-4204-bfbb-aca430c594dc

giohappy commented 10 months ago

As another test, when I change the WAIT time in the second END block (gates output) in this patch it goes banana. It gets fine again after moving the cursor back and forth sometimes...

ALSO
FOR n = 0 TO 3
  out1 = note[n]
  WAIT pause_length
NEXT
END ALSO

ALSO
    out2 = 10
    WAIT 50 
    out2 = 0
    WAIT 50
END ALSO

WHEN start() or trigger(in9)
  note[0] = { c3, g3, c4, c4 }  ' The notes in my score.
  pause_length = 100
  RESET  ' Forces main block to restart now
END WHEN
mahlenmorris commented 10 months ago

There was a bug like this, but it was fixed a while ago. I know this is a long shot, but can you verify the version number for the plugin? It should be 2.0.13.

Screenshot 2024-01-19 133049

giohappy commented 10 months ago

It is

image

giohappy commented 10 months ago

@mahlenmorris FYI I was able to reproduce the behaviour with the plugin built from the main branch, so it doesn't seem a matter of version.

mahlenmorris commented 10 months ago

Ahhh, I think I know what causes this to occur. I normally set the number of threads (VCV Menu/Engine/Threads) to 4, but if i set it to 1, I see behavior similar to what you demonstrated. If you set the threads to, say, 2, does compilation seem faster?

giohappy commented 10 months ago

I will try as soon as I'm back to my pc. I'm really curious to understand why the threading configuration of VCV Rack affects the creation of threads by modules...

squinkylabs commented 10 months ago

My multi-threaded modules create a single worker thread and just send it messages when I want it to do something. It's a little simple, but it works fine

mahlenmorris commented 10 months ago

It would be news to me as well that Rack's thread limit has an effect on a module's ability to start threads. I can't look at code effectively now, but I suspect I'm starting new threads, rather than signaling an existing thread to start, as squinky suggests; if you confirm that this effect is real, I'll want to refactor a number of my modules. It may be wise to do this anyway, as the time to start a new thread is almost surely longer than signaling an existing thread.

squinkylabs commented 10 months ago

Probably no longer true, but there was a time in OSX where signaling a thread could be a blocking operation, depending on which API you used. I don't really remember, but I think my worker thread just used polling and atomic variables for "waking it up". that said, signaling should be ok. for sure creating a thread can block, it sort of has to call malloc, doesn't it? I don't know if your question was addressed to this point "if you confirm that this effect is real". If so, of course it's real.

mahlenmorris commented 10 months ago

Now that I can see the code, i can see that, contrary to what I supposed earlier, for BASICally compilation, in the thread is created once and signaled to do new compiles. So "creating a new thread can be slow" is NOT the issue here, I'll maintain.

And yet I still visually observe a difference in compilation speed between when (VCV Menu/Engine/Threads) is 1 or 2. Which I don't understand. My belief from looking at the VCV Rack code a while back was that Rack creates a thread pool of the specified size and pushes the process() calls of each module to the thread(s) in the pool. I don't know how that could affect the behavior of my thread, but maybe my thread is not as "in the background" as I think?

I suppose, if @giohappy confirms this as the source of his "delayed" compilation, I should do some measurements and dive more into the Rack code.

giohappy commented 10 months ago

@mahlenmorris at the moment I'm with another PC and I can't reproduce the issue even with 1 thread. Strange...

Now that I can see the code, i can see that, contrary to what I supposed earlier, for BASICally compilation, in the thread is created once and signaled to do new compiles

I've digged a bit in the code. You check compile_in_progress here. If it's true you check if there's a compilation in progress (compiler->running) otherwise you set compile_in_progress to false, you reset the blocks and finally join and delete the thread.

The next sample it will see compile_in_progress is false, so a new thread for the compiler is created.

Maybe I'm doing wrong assumptions but a breakpoint at: https://github.com/mahlenmorris/VCVRack/blob/27ea6df97a8b7263718f7e854110311031e8df94/src/Basically.cpp#L627 is hit every time I make a change to the text, confirming that a new thread is created every time.

Anyway, wouldn't it be safer if the thread was created (only once) from the UI thread, instead of the audio thread?

giohappy commented 10 months ago

Here it is a short video showing the breakpoint being hit every time the text is changed:

thread_creation

mahlenmorris commented 10 months ago

Ha! My memory was right, and my investigation went wrong.

OK, I'll work on this sometime soon, and it'll be in the next release. At the very least, I now understand why the behavior observed could happen.

Thanks again to both of you.

mahlenmorris commented 5 months ago

Oh, forgot, this was fixed in 2.0.14, which went live on May 6, 2024. Thanks again for your help with this.