-
Notifications
You must be signed in to change notification settings - Fork 225
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
Refactor Connect and Disconnect functionality out to CClient #3372
base: main
Are you sure you want to change the base?
Refactor Connect and Disconnect functionality out to CClient #3372
Conversation
src/client.cpp
Outdated
if ( !IsRunning() ) | ||
{ | ||
// Set server address and connect if valid address was supplied | ||
if ( SetServerAddr ( strServerAddress ) ) |
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 don't like this style at all. If there's some validation to happen, it should have happened before we get this far. We shouldn't try to connect to an address that's not invalid.
Perhaps this whole routine is redundant and SetServerAddr
should be called Connect
and do the emits?
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.
SetServerAddr
Should just do exactly what the name implies IMO - Otherwise we could introduce mixing of functionality.
Connect() would then emit the signals. That's cleaner and closer to what we do now.
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 think that nevertheless Connect(serverAddress) should exist. It simplifies needing to set and then start the client.
@@ -1193,65 +1193,36 @@ void CClientDlg::OnCLPingTimeWithNumClientsReceived ( CHostAddress InetAddr, int | |||
ConnectDlg.SetPingTimeAndNumClientsResult ( InetAddr, iPingTime, iNumClients ); | |||
} | |||
|
|||
void CClientDlg::Connect ( const QString& strSelectedAddress, const QString& strMixerBoardLabel ) | |||
void CClientDlg::OnConnect ( const QString& strMixerBoardLabel ) |
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.
Ideally, this signal would get issued only on successful connection.
As we're going cross-thread here, we need Client to supply any listener -- ClientDlg or the Client JSON-RPC -- with full details of the newly established connection.
We should follow this rule for all state changes in the Client -- that should be the only way for ClientDlg and the Client JSON-RPC to acquire knowledge of Client state, no calls to functions across threads.
(The same is true on the server side, too.)
Similarly, to change the state of the Client, the Client should expose slots and ClientDlg or Client JSON-RPC should signal those slots to effect the change in state. Not call methods across threads.
This does demand a major restructure but I think this could be a good place to start. Set out what state changes when the Client successfully establishes a connection to a server, create an object instance containing that information, and issue a signal passing that object. This gives a clearly defined API to that change in state.
Either the message on the signal could be minimal, covering only the state that changes on any connect, or extensive, supplying the whole Client state. In the latter case, every signal from the Client would pass a new state instance with the full current Client state, making the listener's job much easier. (I don't like it as much as the minimal approach but it's likely much easier to explain and maintain. It smacks of "global variables" a bit too much, though...)
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.
The global approach could also be wasteful while the small approach could be more difficult. If we restructure correctly, we should aim at the small approach.
|
8bacce4
to
4ec9c68
Compare
4ec9c68
to
a1d98aa
Compare
69246f5
to
57825bf
Compare
57825bf
to
8bc81d7
Compare
e27569f
to
ae31a65
Compare
Please recheck if the important parts are in the two issues I opened and comment there. |
8b0b44d
to
8e48c03
Compare
{ | ||
throw CGenErr ( tr ( "Received invalid server address. Please check for typos in the provided server address." ) ); | ||
} | ||
} |
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.
Should this throw some indication it ignored the request?
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.
It emits the error message further down
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 do not understand why clang format complains about the list style here. |
8e48c03
to
9f93619
Compare
src/clientdlg.cpp
Outdated
// first check if we are already connected, if this is the case we have to | ||
// disconnect the old server first | ||
if ( pClient->IsRunning() ) | ||
{ | ||
Disconnect(); | ||
} | ||
|
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 don't think this code should be removed. Or else equivalent code should be added somewhere according to the new structure (which I don't yet fully understand).
It is possible, while connected to a server, to go File
->Connection Setup...
, or Ctrl-C
, to bring up the connect dialog, and then select a new server to connect to. In existing versions of Jamulus, this disconnects from the connected server, and then immediately connects to the new server. This was used to great effect during WorldJam to quickly move between rooms.
In the version built from this PR, this doesn't work. The existing connection continues instead of connecting to the new server.
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.
We could probably call something like pClient->Disconnect()/Stop() and then pClient->Connect().
The main idea of this PR is to:
- Emit signals if the client disconnects or connects
- Introduce convenient methods for programmatic (JSON-RPC) disconnection and connection.
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. Fixed. Not sure if we need the if is running then stop.
I'd like to have this if in the client then.
4642e40
to
b996df2
Compare
b996df2
to
7b420ff
Compare
This is an extract from jamulussoftware#2550 Co-authored-by: ann0see <[email protected]>
7b420ff
to
a77dcfe
Compare
/// @method | ||
/// @brief Stops the client if the client is running | ||
/// @emit Disconnected | ||
void CClient::Disconnect() |
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.
Introduced again. Mainly an alias. But now also with the check
@@ -122,7 +121,7 @@ CClient::CClient ( const quint16 iPortNumber, | |||
QObject::connect ( &Channel, &CChannel::ConClientListMesReceived, this, &CClient::OnConClientListMesReceived ); | |||
QObject::connect ( &Channel, &CChannel::ConClientListMesReceived, this, &CClient::ConClientListMesReceived ); | |||
|
|||
QObject::connect ( &Channel, &CChannel::Disconnected, this, &CClient::Disconnected ); | |||
QObject::connect ( &Channel, &CChannel::Disconnected, this, &CClient::Stop ); |
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 happy with Stop
as a name. What does it do? It doesn't exit the application...
It's used (at least) here and in the SIGINT
/SIGTERM
handler. The latter makes me wonder if it's also used in the OnAboutToQuit
handling (which should be invoked by the SIGINT
/SIGTERM
handler...).
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.
Stop() means it stops the client.
But this concept is not well defined IMO.
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 guess I want to know what "Stop" stops the client doing. About the only thing I can think is it should trigger emitting a signal that stops audio processing. If that is all it's doing, this would be better as a lambda.
I think I'd like SIGINT
/SIGTERM
to simply exit the application and have the AboutToQuit handling clean up, if that's a sane approach. Currently it feels like there's random paths through the code for no real reason.
OK... so what I'm expecting to see...
I think most of the Connect stuff is there. I'm less clear what's happening around Disconnect. And the exiting the client currently appears very messy - at least, it's confusing me... |
Yes, I agree. |
I would like to define the methods as follows (not sure if this is everything) CClient is connected iff all of those hold
CClient will disconnect if at least one of these conditions holds
CClient is running iff
This means, there may be a sound card error while CClient is running and the client may not be connected to a server To transition from CClient being running to CClient being connected, we need to call Connect() and the connection to the server must stand (within the timeout).
A SIGINT would
To transition from CClient being connected to cclient being disconnected, We'd just call disconnect. I do not think there's a way to have a failed disconnection: This means:
Does this make sense? |
Background(Apologies for writing this out in a PR where you know it as well as I do but I'm trying to make sure my thoughts are in order...) We need states:
"Disconnected" means no connection has been requested or established Now, these states should apply equally to the Client's view of it's single connection to a Server and Server's view of each of its connections to a Client. This is important (and a core concept to Jamulus). The Client Channel owns that state. The Client has one Channel but the Server has many Channels. So any changes here must not break the Server. A Channel is considered disconnected when no more audio data has been exchanged for a certain period (or something, I don't remember this bit) or an explicit disconnect is received (maybe, I'm not sure on that, either). Again, that goes equally for both ends of the Channel. (And finally... The Channel carries Protocol messages that can either be audio or other messages. The audio messages go into the jitter buffer, triggering audio processing. The other protocol messages get processed, quite often by the Channel itself but sometimes needing either Client or Server (as they know more about the world) to get involved. The main difference between Client and Server, of course, is audio processing -- the Client exchanges CSound audio data with the jitter buffer but the Server pushes the audio through the mixer.)
OK, let's start with "CClient has a CChannel" and it's "CChannel" that holds state regarding whether an active connection has been established. Given this is the case (and it has to be for the Client and Server to work using the same code): CClient is "connected" or "running" or "started" or whatever, if and only if
- its CChannel.ConnectionState() is Connected (or "CChannel.IsConnected()")
- its CSound.IsRunning() is true Neither of those alone is useful for components outside CClient (other than CChannel and CSound maintaining their own state). Nothing else needs worrying about.
Maybe we rename CClient.Connect(...) to CClient.RequestConnection(QString server) because that's where all the Client-specific logic lives and it doesn't end up by connecting. It would:
CChannel.Connect() is Client-only. It would
(The Client is responsible for putting audio from CSound into the channel jitter buffer, which should trigger CChannel to take audio from the jitter buffer and send it to the Server, and for reading the jitter buffer and passing the audio to CSound. I don't clearly remember the structure here. There's a timer that expires if processing doesn't start. There's a timer that expires for each audio cycle, too. I think both are in CChannel as it's very similar in Client and Server.) CChannel will handle audio being received by marking the CChannel connection state as ConnectionEstablished (IsConnected() is true). Again, same at Client and Server for this. It also emits a ConnectionEstablished signal.
The first two should cause CClient.Disconnect() to be called and nothing else - lambda slot handlers in their respective classes calling the client method for signals. It'll get called during CClient.OnAboutToQuit, I expect. Actually, I'm proposing CClient.EndConnection() in place of CClient.Disconnect() to make it clear it's a non-trivial operation. It should also always, without condition:
We need two things:
We could also check the wiring up of the signals from those two classes to the slots in the Client are in place so the sound goes to/from the channel... because CChannel and CSound do not know CClient is handling their signals. However, that would be wired up once during creation of CClient, so can be taken as a given. Nothing else matters because, so long as we have sound going in and out and packets going in and out - that means we set up okay and we're running fine.
Yes - but by CChannel changing state from ConnectionEstablished to Disconnected and emitting a ConnectionEnded signal or by CSound changing state from Running to Stopped and emitting an SoundStopped signal, where both are handled by CClient. In either case, asking CClient whether it's "connected" or "running" or "started" or whatever, it'll say false. In fact, CChannel::ConnectionEnded and CSound::SoundStopped would get handled the same way -- calling CClient::EndConnection. (Or StopConnection or something... it needs a better name...)
OK, I don't understand your concept of "just being running". The Client just being there isn't the Client being in any particular state in and of itself. It's just there, handling signals. That doesn't need a state in and of itself.
Neither of the above changes the state of the Client itself -- it would change the result of the method that combines the result of CChannel.ConnectionState() and CSound.IsRunning(), though. But that's not a state, it's a computation. If CChannel gets a server disconnect protocol message, it'll need to pass that to CClient to deal with - not simply disconnect, as that would be seen as an error happening. Any request to Disconnect would be handled the same way -- call CClient.EndConnection(). Exiting the client can happen multiple ways -- CLI through SIGINT/SIGTERM, GUI through menu emitting a signal, or a JSON-RPC command. These should all trigger the Qt OnAboutToQuit by asking Qt to exit the application. The Window Manager can also trigger Qt OnAboutToQuit by asking Qt to exit the application. In all cases, when the client terminates, part of handling OnAboutToQuit in CClient would be to call CClient.EndConnection(). Or maybe I'm missing something? |
This is an extract from #2550
Short description of changes
Refactor Connect() and disconnect functionality.
Introduces a new Connect() method for quick connection and adds signals to Start() and Stop() of client.
CHANGELOG: Refactoring: Move Connect() functionality to CClient.
Context: Fixes an issue?
Fixes: #3367
Does this change need documentation? What needs to be documented and how?
No
Status of this Pull Request
Ready for review. Start for refactoring. Most likely overlaps and to a large extent relates somehow to #3364
What is missing until this pull request can be merged?
Review, clarification, probably documentation and refactoring.
Checklist