Skip to content
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

Closed
wants to merge 7 commits into from
Closed

Conversation

ywwg
Copy link
Member

@ywwg ywwg commented Jun 8, 2013

No description provided.

@@ -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;
Copy link
Member

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.

Copy link
Member

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 ;).

Copy link
Member Author

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!

Copy link
Member

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

Copy link
Member

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

Copy link
Member

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.

@daschuer
Copy link
Member

daschuer commented Jun 9, 2013

I wander how much flexibility of the control object we really need (vs. simplicity and performance).
This affects the behavior and the validator. I think in general if it is good to have them exactly there but with this NULL pointer approach we have to deal with updates for the objects when they are not fully setup.

I am not sure if this is possible, but I would really like to have an owner/user concept for the control concept.
The owner is responsible to setup the control object and once it is finished, it can be released for usage.

What is the difference between validator and behaviour? Is it possible to join them?

@rryan
Copy link
Member

rryan commented Jun 11, 2013

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.

@ywwg
Copy link
Member Author

ywwg commented Jun 11, 2013

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;
Copy link
Member

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.

@rryan
Copy link
Member

rryan commented Jun 11, 2013

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.

@ywwg
Copy link
Member Author

ywwg commented Jun 11, 2013

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() { }
Copy link
Member Author

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

Copy link
Member

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.

@daschuer
Copy link
Member

Hi Owen,

I had time to think a little bit about your validation concept ... and I think it will not work in all cases.
Sorry about this late concerns.

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

@rryan
Copy link
Member

rryan commented Jun 11, 2013

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.

@rryan
Copy link
Member

rryan commented Jun 11, 2013

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.

@daschuer
Copy link
Member

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"

@ywwg
Copy link
Member Author

ywwg commented Jun 11, 2013

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.

@daschuer
Copy link
Member

As I pointed out, there might be a problem with interrupting after changing m_Enabled.
If we offer a general solution for this, it opens the door for race conditions later on other controls as well.

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.
But I like a clean "play indicator" solution more.

@ywwg
Copy link
Member Author

ywwg commented Jun 11, 2013

I would like to have enable/disable for:
Places I think we should use enable/disable:
play, cue, ff, rew, ie all playback controls (track not loaded)
all EQ controls (eq bypass)
vinyl controls (vinyl not active)
talk button (no mic connected)

@daschuer
Copy link
Member

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.
If the CO is enabled again, there is no way to set it to the requested value. The user has turn the button again to receive midi updates.

I have made some drawings with the free yEd, shared with you at
https://docs.google.com/a/mixxx.org/file/d/0B487gWGL6DsXV2FJU2tfbnhkOVU/edit?usp=sharing
https://docs.google.com/a/mixxx.org/file/d/0B487gWGL6DsXeWNMUVVNZW84N0E/edit?usp=sharing

If we finally fix the race condition, all solutions would work.
Here some comments:

  • Enabled solution.
    ** It is hard to make it thread save and non blocking
    ** Fully compatible the current scripts
    ** It is not clear what Play = 1.0 is, because Engine Buffer may playing or a play command may requested
  • Feedback solution
    ** No race condition
    ** Fully compatible the current scripts
    ** Can distinguish between play request and playing
    ** Overhead due to a additional CO
  • Tree State Solution
    ** No race condition
    ** Native atomic writes on 32 bit CPUs
    ** Not compatible the current scripts (Adapter Required)
    ** Can distinguish between play request and playing
    ** New API: Request() / Confirm() / GetCurrent() / GetRequest()
    ** Requested value is stored

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.

@ywwg
Copy link
Member Author

ywwg commented Jun 12, 2013

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.

@daschuer
Copy link
Member

Has any one read:
https://docs.google.com/a/mixxx.org/file/d/0B487gWGL6DsXeWNMUVVNZW84N0E/edit?usp=sharing

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.

@daschuer daschuer mentioned this pull request Aug 6, 2013
@daschuer
Copy link
Member

can we close this one?

@ywwg
Copy link
Member Author

ywwg commented Aug 21, 2013

oh yeah

@ywwg ywwg closed this Aug 21, 2013
@daschuer daschuer mentioned this pull request Dec 28, 2014
daschuer referenced this pull request in daschuer/mixxx Oct 19, 2015
There is no need for signal upstream shoutcast because user can't change options when shoutcast sending is on-air.
daschuer pushed a commit that referenced this pull request May 25, 2017
Some beat size box improvements
@daschuer daschuer mentioned this pull request Aug 13, 2017
19 tasks
@daschuer daschuer mentioned this pull request Jun 3, 2018
@daschuer daschuer mentioned this pull request Jun 11, 2018
11 tasks
Be-ing pushed a commit that referenced this pull request Dec 30, 2018
uklotzde pushed a commit that referenced this pull request Dec 17, 2020
ColumnCache: Verify consistency of all column maps
ywwg pushed a commit that referenced this pull request Mar 7, 2022
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants