-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Add validation to control objects #9
Conversation
@@ -93,6 +96,7 @@ void ControlDoublePrivate::set(const double& value, QObject* pSender) { | |||
if (pBehavior && !pBehavior->setFilter(&dValue)) { | |||
return; | |||
} | |||
if (m_pValidator && !m_pValidator->validateChange(dValue)) return; |
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.
This has a race condition -- m_pValidator could become NULL between the m_pValidator check and m_pValidator->validateChange call. You should copy it to a local first.
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.
Also not a giant fan of the one line if-statement/body ;).
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.
that's approved google style!
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.
+1 for always use braces
if ( ... ) {
return;
}
Good ..
- for recognising control flow.
- clean diffs if we insert a statement before return later
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.
set() should become bool set() to inform the caller if the set was successful
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.
Yea :) it's a personal preference and not in the Google C++ style guide. My eyes can easily scan over a 1-liner with return dangling at the end while a block is harder to miss.
I wander how much flexibility of the control object we really need (vs. simplicity and performance). I am not sure if this is possible, but I would really like to have an owner/user concept for the control concept. What is the difference between validator and behaviour? Is it possible to join them? |
I think validator and behavior are useful to keep separate. I think of validators as a user-defined validation function while the behavior is more intrinsic to the type of the control. |
Updated with some notes addressed. Still no enable/disable, but with better pointer ownership and cleaner implementation. |
@@ -26,6 +26,8 @@ | |||
#include "controllers/midi/midimessage.h" | |||
#include "control/control.h" | |||
|
|||
class ControlValidator; |
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.
Since you are providing an inline definition of a method that uses the class you can't forward declare it. The only reason this works is this file includes control.h which includes the controlvalidator.h.
Nice! But this still has the lifecycle and threading issues I pointed out in the last round of comments. The validator outlives the EngineBuffer so any sets to the control after the EngineBuffer is deleted will segfault. Also, no thread should touch EngineBuffer except the callback thread and letting the valdiator have a pointer to EngineBuffer means any thread that changes the play control can call code in EngineBuffer. I think the right solution here is either an enabled/disabled property for controls or an EnabledValidator / DisabledValidator that you set on the play control when the state changes within EngineBuffer. |
OK, I thought adding the check in the validator for if(m_pEngine ==NULL) was enough to solve the first problem. but you're right, a more generic enabled/disabled is needed. What about controls that require more validation than yes, no, however, like the seek validation? |
|
||
class ControlValidator { | ||
public: | ||
ControlValidator() { } |
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.
that's weird because when I didn't have the constructor/destructor the compiler complained about possible undefined behavior without a virtual destructor
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.
Yea you're right, it should have a virtual destructor. I've done that elsewhere in Mixxx and I should fix that too.
Hi Owen, I had time to think a little bit about your validation concept ... and I think it will not work in all cases. We have always the tiny moment between validation and the actual set of the new value. The high priority audio thread can always interrupt the GUI thread in particular this moment. So all validation might be invalid after that. The problem in the current trunk solution is that parts of mixxx rely on the play control as indicator for play. e.g. The he feedback LED might be on without a playing track. One solution might be to handle controls like play as the user command. (You can't stop the user to press play) In addition we need a "playing" or "play_fb"control, which has always the value for the play button feedback LED. Since Mixxx can only react on play commands each time the audio callback is executed. It is fine to evaluate the "play" control there and set the feedback "playing" and the "play" control back to the real "Play value from the engine buffer. Kind regards, Daniel |
The fact that validation can be interrupted after it runs is fine I think. If the validator validates a value, it has validated that value regardless of whether the control has been updated in the meantime. Take for example playposition. The user wants to seek to 0 and the current value is 0.5. Let's say there's a validator that makes sure that you set it to a positive number (contrived I know since we support the lead-in) but it validates 0 then is interrupted. The engine runs and updates playposition to 0.6. The GUI thread resumes, and the set to 0 is completed. It is still valid to seek to 0 because that's what the user requested. |
Making a play indicator control would work but there is already tons of existing state (skins, controller scripts) using play as a toggle button and indicator combined. |
Hi RJ, in your case everything is fine, because we have several seek controls for the user command and the play position control for the "truth" feedback. If a user seeks a track to 0.5 which is currently unloaded nothing bad will happen. Owens Validator concept is fine if the validate rules are not changed during run time. Introducing a play indicator will not effect the current solutions in skins if they are not changed. The still have the "problem" that the playbutton is lit when no track is loaded for a short moment, but so what. All the critical things like AutoDJ and also new skins could work with the new "play indicator" |
OK I've added enabled/disabled. I'm worried that we might want to call set() and overwrite the value even if the control is disabled. See how I had to be careful where to set enabled/disabled on the play button. |
As I pointed out, there might be a problem with interrupting after changing m_Enabled. What are the controls which have a benefit of this? Where it is really needed? We may offer a thread save solution for this: We may put checking m_Enabled and writing in a critical section. |
I would like to have enable/disable for: |
If we take a look at rotary switches. There is a problem with the "Enabled solution", because if a control is disabled the requested value is lost. But the rotary on the midi controller is still pointing to the requested value. I have made some drawings with the free yEd, shared with you at If we finally fix the race condition, all solutions would work.
I am not sure whats my favorite, but I am heading towards the three state solution, because it represents a Toggle button with a Feedback LED the best. |
Well I guess for EQ it's more complicated because it's fine to change the value via midi while it's disabled, but it shouldn't be possible to change it through the GUI. But for other controls if the user disables the control and then twists a knob, they shouldn't be surprised that when they reenable the control the value isn't correct. That's what midi-takeover is for. as for the multithreading stuff, I don't really understand the issues so I'll let you guys figure it out. If it was up to me I would just have a system where we still have mutexes and try to move away from locks in the engine gradually. trying to predict all of these race conditions just gives me a headache. |
Has any one read: I have currently exactly this problem with the play button in the https://github.com/daschuer/mixxx/tree/advanced_autodj_2 branch. I need the play state of each deck, not if there is a pending play request. After new thoughts about it, I think while "Tree State Solution" is closes to the truth, it brakes the most with the current view of a CO -> a single global" So I like currently the feedback sloution most. because it is straight forward and easy to understand. I will prepare a branch for review, that introduce a new "play_fb" control and leave the "play" control unchanged. |
can we close this one? |
oh yeah |
There is no need for signal upstream shoutcast because user can't change options when shoutcast sending is on-air.
Some beat size box improvements
ColumnCache: Verify consistency of all column maps
…h sync When loading a track that is not yet present in the library (and thus doesn't have any BPM because it hasn't been analyzed yet) while another deck is playing and both decks have sync enabled, a debug assertion is triggered: DEBUG ASSERT: "isValid()" in function double mixxx::Bpm::value() const at src/track/bpm.h:53 Aborted (core dumped) The backtrace looks as follows: #0 0x00007f175c87234c in __pthread_kill_implementation () at /usr/lib/libc.so.6 #1 0x00007f175c8254b8 in raise () at /usr/lib/libc.so.6 #2 0x00007f175c80f534 in abort () at /usr/lib/libc.so.6 #3 0x00007f175cf05ee4 in qt_assert(char const*, char const*, int) () at /usr/lib/libQt5Core.so.5 #4 0x000055deb2e67e1c in mixxx::(anonymous namespace)::handleMessage(QtMsgType, QMessageLogContext const&, QString const&) (type=<optimized out>, context=<optimized out>, input=<optimized out>) at src/util/logging.cpp:355 #5 0x00007f175cf47128 in () at /usr/lib/libQt5Core.so.5 #6 0x00007f175cf3fd8a in () at /usr/lib/libQt5Core.so.5 #7 0x00007f175cf06526 in QMessageLogger::critical(char const*, ...) const () at /usr/lib/libQt5Core.so.5 #8 0x000055deb2e5c720 in mixxx_debug_assert(char const*, char const*, int, char const*) (assertion=assertion@entry=0x55deb39bd0db "isValid()", file=file@entry=0x55deb39bbf30 "src/track/bpm.h", line=line@entry=53, function=function@entry=0x55deb39bbf08 "double mixxx::Bpm::value() const") at gsrc/util/assert.h:9 #9 0x000055deb2ee7e7e in mixxx_debug_assert_return_true(char const*, char const*, int, char const*) (function=0x55deb39bbf08 "double mixxx::Bpm::value() const", line=53, file=0x55deb39bbf30 "src/track/bpm.h", assertion=0x55deb39bd0db "isValid()") at gsrc/util/assert.h:18 #10 mixxx::Bpm::value() const (this=<synthetic pointer>) at src/track/bpm.h:53 #11 mixxx::operator*(mixxx::Bpm, double) (multiple=1, bpm=...) at src/track/bpm.h:160 #12 SyncControl::setLocalBpm(mixxx::Bpm) (this=<optimized out>, localBpm=...) at src/engine/sync/synccontrol.cpp:567 #13 0x000055deb34c7ba3 in EngineBuffer::postProcess(int) (this=0x55deb56eb060, iBufferSize=2048) at src/engine/enginebuffer.cpp:1318 #14 0x000055deb3139023 in EngineMaster::processChannels(int) (this=0x55deb5449440, iBufferSize=<optimized out>) at src/engine/enginemaster.cpp:383 #15 0x000055deb31394f7 in EngineMaster::process(int) (this=0x55deb5449440, iBufferSize=iBufferSize@entry=2048) at src/engine/enginemaster.cpp:410 #16 0x000055deb2f91d0b in SoundManager::onDeviceOutputCallback(long) (this=<optimized out>, iFramesPerBuffer=iFramesPerBuffer@entry=1024) at src/soundio/soundmanager.cpp:596 #17 0x000055deb32dd794 in SoundDevicePortAudio::callbackProcessClkRef(long, float*, float const*, PaStreamCallbackTimeInfo const*, unsigned long) (this=0x55deb553e6b0, framesPerBuffer=1024, out=<optimized out>, in=<optimized out>, timeInfo=<optimized out>, statusFlags=<optimized out>) at src/soundio/sounddeviceportaudio.cpp:965 This happens because `newLocalBpm` is invalid when `localBpm` is invalid. Trying to do sync decks while no tempo information is available does not make sense, so we only synchronize decks if the local BPM is available.
No description provided.