From 56eaf000a477c609e05504e613dacbe829fbe641 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@musicpd.org>
Date: Mon, 20 Jan 2020 13:16:13 +0100
Subject: [PATCH] Partition: add client list

For efficient traversal within one partition, e.g. for distributing
partition-local idle events.
---
 src/Partition.cxx               |  1 +
 src/Partition.hxx               |  8 ++++++++
 src/client/Client.cxx           |  2 ++
 src/client/Client.hxx           |  2 ++
 src/client/New.cxx              |  2 ++
 src/command/MessageCommands.cxx | 14 ++++----------
 6 files changed, 19 insertions(+), 10 deletions(-)

diff --git a/src/Partition.cxx b/src/Partition.cxx
index 5c31e9ecd..2e8525a75 100644
--- a/src/Partition.cxx
+++ b/src/Partition.cxx
@@ -25,6 +25,7 @@
 #include "mixer/Volume.hxx"
 #include "IdleFlags.hxx"
 #include "client/Listener.hxx"
+#include "client/Client.hxx"
 #include "input/cache/Manager.hxx"
 #include "util/Domain.hxx"
 
diff --git a/src/Partition.hxx b/src/Partition.hxx
index 186ab39e7..3ee2702ca 100644
--- a/src/Partition.hxx
+++ b/src/Partition.hxx
@@ -32,6 +32,8 @@
 #include "Chrono.hxx"
 #include "config.h"
 
+#include <boost/intrusive/list.hpp>
+
 #include <string>
 #include <memory>
 
@@ -39,6 +41,7 @@ struct Instance;
 class MultipleOutputs;
 class SongLoader;
 class ClientListener;
+class Client;
 
 /**
  * A partition of the Music Player Daemon.  It is a separate unit with
@@ -55,6 +58,11 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
 
 	std::unique_ptr<ClientListener> listener;
 
+	boost::intrusive::list<Client,
+			       boost::intrusive::base_hook<boost::intrusive::list_base_hook<boost::intrusive::tag<Partition>,
+											    boost::intrusive::link_mode<boost::intrusive::normal_link>>>,
+			       boost::intrusive::constant_time_size<false>> clients;
+
 	MaskMonitor global_events;
 
 	struct playlist playlist;
diff --git a/src/client/Client.cxx b/src/client/Client.cxx
index 6c47a0822..21bc83cdb 100644
--- a/src/client/Client.cxx
+++ b/src/client/Client.cxx
@@ -68,7 +68,9 @@ Client::SetPartition(Partition &new_partition) noexcept
 	if (partition == &new_partition)
 		return;
 
+	partition->clients.erase(partition->clients.iterator_to(*this));
 	partition = &new_partition;
+	partition->clients.push_back(*this);
 
 	/* set idle flags for those subsystems which are specific to
 	   the current partition to force the client to reload its
diff --git a/src/client/Client.hxx b/src/client/Client.hxx
index e52a7a18a..c94350b51 100644
--- a/src/client/Client.hxx
+++ b/src/client/Client.hxx
@@ -52,6 +52,8 @@ class BackgroundCommand;
 
 class Client final
 	: FullyBufferedSocket,
+	  public boost::intrusive::list_base_hook<boost::intrusive::tag<Partition>,
+						  boost::intrusive::link_mode<boost::intrusive::normal_link>>,
 	  public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>> {
 	TimerEvent timeout_event;
 
diff --git a/src/client/New.cxx b/src/client/New.cxx
index b1affda4c..1f92c63c3 100644
--- a/src/client/New.cxx
+++ b/src/client/New.cxx
@@ -72,6 +72,7 @@ client_new(EventLoop &loop, Partition &partition,
 				    num);
 
 	client_list.Add(*client);
+	partition.clients.push_back(*client);
 
 	FormatInfo(client_domain, "[%u] opened from %s",
 		   num, remote.c_str());
@@ -81,6 +82,7 @@ void
 Client::Close() noexcept
 {
 	partition->instance.client_list->Remove(*this);
+	partition->clients.erase(partition->clients.iterator_to(*this));
 
 	if (FullyBufferedSocket::IsDefined())
 		FullyBufferedSocket::Close();
diff --git a/src/command/MessageCommands.cxx b/src/command/MessageCommands.cxx
index c38bc4050..62cf6bb36 100644
--- a/src/command/MessageCommands.cxx
+++ b/src/command/MessageCommands.cxx
@@ -22,8 +22,8 @@
 #include "client/Client.hxx"
 #include "client/List.hxx"
 #include "client/Response.hxx"
-#include "Instance.hxx"
 #include "util/ConstBuffer.hxx"
+#include "Partition.hxx"
 
 #include <set>
 #include <string>
@@ -79,11 +79,7 @@ handle_channels(Client &client, gcc_unused Request args, Response &r)
 
 	std::set<std::string> channels;
 
-	const auto &partition = client.GetPartition();
-	for (const auto &c : *client.GetInstance().client_list) {
-		if (&c.GetPartition() != &partition)
-			continue;
-
+	for (const auto &c : client.GetPartition().clients) {
 		const auto &subscriptions = c.GetSubscriptions();
 		channels.insert(subscriptions.begin(),
 				subscriptions.end());
@@ -125,10 +121,8 @@ handle_send_message(Client &client, Request args, Response &r)
 	bool sent = false;
 	const ClientMessage msg(channel_name, message_text);
 
-	const auto &partition = client.GetPartition();
-	for (auto &c : *client.GetInstance().client_list)
-		if (&c.GetPartition() == &partition &&
-		    c.PushMessage(msg))
+	for (auto &c : client.GetPartition().clients)
+		if (c.PushMessage(msg))
 			sent = true;
 
 	if (sent)