From 51769c40d8515f92c1c825f9ea65ff729332db5f Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Sun, 13 Nov 2022 08:51:18 +0100
Subject: [PATCH] util/IntrusiveSortedList: new class

---
 src/event/TimerList.cxx          |  8 ----
 src/event/TimerList.hxx          |  7 ++--
 src/util/IntrusiveSortedList.hxx | 65 ++++++++++++++++++++++++++++++++
 test/util/TestIntrusiveList.cxx  | 45 ++++++++++++++++++++++
 4 files changed, 114 insertions(+), 11 deletions(-)
 create mode 100644 src/util/IntrusiveSortedList.hxx

diff --git a/src/event/TimerList.cxx b/src/event/TimerList.cxx
index 5110b4970..300e8fbf8 100644
--- a/src/event/TimerList.cxx
+++ b/src/event/TimerList.cxx
@@ -54,15 +54,7 @@ TimerList::~TimerList() noexcept
 void
 TimerList::Insert(FineTimerEvent &t) noexcept
 {
-#ifdef NO_BOOST
-	auto i = std::find_if(timers.begin(), timers.end(), [due = t.GetDue()](const auto &other){
-		return other.GetDue() >= due;
-	});
-
-	timers.insert(i, t);
-#else
 	timers.insert(t);
-#endif
 }
 
 Event::Duration
diff --git a/src/event/TimerList.hxx b/src/event/TimerList.hxx
index 120ea9e08..a0708add1 100644
--- a/src/event/TimerList.hxx
+++ b/src/event/TimerList.hxx
@@ -34,9 +34,10 @@
 
 #include "Chrono.hxx"
 #include "event/Features.h"
-#include "util/IntrusiveList.hxx"
 
-#ifndef NO_BOOST
+#ifdef NO_BOOST
+#include "util/IntrusiveSortedList.hxx"
+#else
 #include <boost/intrusive/set.hpp>
 #endif
 
@@ -55,7 +56,7 @@ class TimerList final {
 	/* when building without Boost, then this is just a sorted
 	   doubly-linked list - this doesn't scale well, but is good
 	   enough for most programs */
-	IntrusiveList<FineTimerEvent> timers;
+	IntrusiveSortedList<FineTimerEvent, Compare> timers;
 #else
 	boost::intrusive::multiset<FineTimerEvent,
 				   boost::intrusive::base_hook<boost::intrusive::set_base_hook<boost::intrusive::link_mode<boost::intrusive::auto_unlink>>>,
diff --git a/src/util/IntrusiveSortedList.hxx b/src/util/IntrusiveSortedList.hxx
new file mode 100644
index 000000000..27e654384
--- /dev/null
+++ b/src/util/IntrusiveSortedList.hxx
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2020-2022 Max Kellermann <max.kellermann@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "IntrusiveList.hxx"
+
+/**
+ * A variant of #IntrusiveList which is sorted automatically.  There
+ * are obvious scalability problems with this approach, so use with
+ * care.
+ */
+template<typename T, typename Compare=typename T::Compare,
+	 typename HookTraits=IntrusiveListBaseHookTraits<T>,
+	 bool constant_time_size=false>
+class IntrusiveSortedList
+	: public IntrusiveList<T, HookTraits, constant_time_size>
+{
+	using Base = IntrusiveList<T, HookTraits, constant_time_size>;
+
+	[[no_unique_address]]
+	Compare compare;
+
+public:
+	constexpr IntrusiveSortedList() noexcept = default;
+	IntrusiveSortedList(IntrusiveSortedList &&src) noexcept = default;
+
+	using typename Base::reference;
+	using Base::begin;
+	using Base::end;
+
+	void insert(reference item) noexcept {
+		auto position = std::find_if(begin(), end(), [this, &item](const auto &other){
+			return !compare(other, item);
+		});
+
+		Base::insert(position, item);
+	}
+};
diff --git a/test/util/TestIntrusiveList.cxx b/test/util/TestIntrusiveList.cxx
index a553c1979..dbe940bb9 100644
--- a/test/util/TestIntrusiveList.cxx
+++ b/test/util/TestIntrusiveList.cxx
@@ -260,3 +260,48 @@ TEST(IntrusiveList, Sort)
 	ASSERT_EQ(&*std::next(list.begin(), 1), &items[2]);
 	ASSERT_EQ(&*std::next(list.begin(), 2), &items[4]);
 }
+
+#include "util/IntrusiveSortedList.hxx"
+
+TEST(IntrusiveSortedList, Basic)
+{
+	using Item = CharItem<IntrusiveHookMode::NORMAL>;
+
+	struct Compare {
+		constexpr bool operator()(const Item &a, const Item &b) noexcept {
+			return a.ch < b.ch;
+		}
+	};
+
+	Item items[]{'z', 'a', 'b', 'q', 'b', 'c', 't', 'm', 'y'};
+
+	IntrusiveSortedList<Item, Compare> list;
+	ASSERT_EQ(ToString(list, list.begin(), 2), "__");
+
+	list.insert(items[0]);
+	ASSERT_EQ(ToString(list, list.begin(), 3), "z_z");
+
+	list.insert(items[1]);
+	ASSERT_EQ(ToString(list, list.begin(), 4), "az_a");
+
+	list.insert(items[2]);
+	ASSERT_EQ(ToString(list, list.begin(), 5), "abz_a");
+
+	list.insert(items[3]);
+	ASSERT_EQ(ToString(list, list.begin(), 6), "abqz_a");
+
+	list.insert(items[4]);
+	ASSERT_EQ(ToString(list, list.begin(), 7), "abbqz_a");
+
+	list.insert(items[5]);
+	ASSERT_EQ(ToString(list, list.begin(), 8), "abbcqz_a");
+
+	list.insert(items[6]);
+	ASSERT_EQ(ToString(list, list.begin(), 9), "abbcqtz_a");
+
+	list.insert(items[7]);
+	ASSERT_EQ(ToString(list, list.begin(), 10), "abbcmqtz_a");
+
+	list.insert(items[8]);
+	ASSERT_EQ(ToString(list, list.begin(), 11), "abbcmqtyz_a");
+}