Skip to content
This repository was archived by the owner on Dec 10, 2025. It is now read-only.

Commit 2e6b4dc

Browse files
committed
refactor: simplify playtime calculations and improve filtering logic in PlaytimeImpl
1 parent 31a2f17 commit 2e6b4dc

File tree

1 file changed

+82
-156
lines changed
  • surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/playtime

1 file changed

+82
-156
lines changed

surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/playtime/PlaytimeImpl.kt

Lines changed: 82 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -7,154 +7,89 @@ import dev.slne.surf.cloud.api.common.util.toObjectList
77
import it.unimi.dsi.fastutil.objects.Object2ObjectMap
88
import it.unimi.dsi.fastutil.objects.ObjectList
99
import it.unimi.dsi.fastutil.objects.ObjectSet
10+
import kotlinx.serialization.Contextual
11+
import kotlinx.serialization.Serializable
12+
import java.time.Instant
1013
import java.time.ZonedDateTime
1114
import kotlin.time.Duration
1215
import kotlin.time.Duration.Companion.seconds
1316

1417
class PlaytimeImpl(private val entries: ObjectList<PlaytimeEntry>) : Playtime {
15-
override fun sumPlaytimes(since: ZonedDateTime?): Duration {
16-
if (since == null) {
17-
return entries.sumOf { it.durationSeconds }.seconds
18-
}
19-
20-
return entries
21-
.filter { it.createdAt.isAfter(since) }
22-
.sumOf { it.durationSeconds }
23-
.seconds
24-
}
18+
override fun sumPlaytimes(since: ZonedDateTime?): Duration = entries
19+
.filter { since == null || it.createdAt.isAfter(since) }
20+
.sumOf { it.durationSeconds }
21+
.seconds
2522

2623
override fun sumByCategory(
2724
category: String,
2825
since: ZonedDateTime?
29-
): Duration {
30-
if (since == null) {
31-
return entries
32-
.filter { it.category.equals(category, true) }
33-
.sumOf { it.durationSeconds }
34-
.seconds
26+
): Duration = entries
27+
.filter {
28+
it.category.equals(category, ignoreCase = true)
29+
&& (since == null || it.createdAt.isAfter(since))
3530
}
36-
37-
return entries
38-
.filter { it.category.equals(category, true) && it.createdAt.isAfter(since) }
39-
.sumOf { it.durationSeconds }
40-
.seconds
41-
}
31+
.sumOf { it.durationSeconds }
32+
.seconds
4233

4334
override fun sumByServer(
4435
server: String,
4536
since: ZonedDateTime?
46-
): Duration {
47-
if (since == null) {
48-
return entries
49-
.filter { it.server.equals(server, true) }
50-
.sumOf { it.durationSeconds }
51-
.seconds
37+
): Duration = entries
38+
.filter {
39+
it.server.equals(server, ignoreCase = true)
40+
&& (since == null || it.createdAt.isAfter(since))
5241
}
42+
.sumOf { it.durationSeconds }
43+
.seconds
5344

54-
return entries
55-
.filter { it.server.equals(server, true) && it.createdAt.isAfter(since) }
56-
.sumOf { it.durationSeconds }
57-
.seconds
58-
}
59-
60-
override fun getCategories(): ObjectSet<String> {
61-
return entries.mapTo(mutableObjectSetOf()) { it.category }
62-
}
45+
override fun getCategories(): ObjectSet<String> =
46+
entries.mapTo(mutableObjectSetOf()) { it.category }
6347

64-
override fun getServers(): ObjectSet<String> {
65-
return entries.mapTo(mutableObjectSetOf()) { it.server }
66-
}
48+
override fun getServers(): ObjectSet<String> =
49+
entries.mapTo(mutableObjectSetOf()) { it.server }
6750

6851
override fun playtimeFor(
6952
server: String,
7053
category: String?,
7154
since: ZonedDateTime?
72-
): Duration {
73-
if (since == null) {
74-
return entries
75-
.filter {
76-
it.server.equals(server, true) && (category == null || it.category.equals(
77-
category,
78-
true
79-
))
80-
}
81-
.sumOf { it.durationSeconds }
82-
.seconds
83-
}
84-
85-
return entries
86-
.filter {
87-
it.server.equals(server, true) && (category == null || it.category.equals(
88-
category,
89-
true
90-
)) && it.createdAt.isAfter(since)
91-
}
92-
.sumOf { it.durationSeconds }
93-
.seconds
94-
}
95-
96-
override fun playtimesPerServer(since: ZonedDateTime?): Object2ObjectMap<String, Duration> {
97-
if (since == null) {
98-
return entries.groupBy { it.server }
99-
.mapValuesTo(mutableObject2ObjectMapOf()) { (_, entry) -> entry.sumOf { it.durationSeconds }.seconds }
55+
): Duration = entries
56+
.filter {
57+
it.server.equals(server, ignoreCase = true)
58+
&& (category == null || it.category.equals(category, ignoreCase = true))
59+
&& (since == null || it.createdAt.isAfter(since))
10060
}
61+
.sumOf { it.durationSeconds }
62+
.seconds
10163

102-
return entries
103-
.filter { it.createdAt.isAfter(since) }
64+
override fun playtimesPerServer(since: ZonedDateTime?): Object2ObjectMap<String, Duration> =
65+
entries.filter { since == null || it.createdAt.isAfter(since) }
10466
.groupBy { it.server }
105-
.mapValuesTo(mutableObject2ObjectMapOf()) { (_, entry) -> entry.sumOf { it.durationSeconds }.seconds }
106-
}
107-
108-
override fun playtimesPerCategory(since: ZonedDateTime?): Object2ObjectMap<String, Duration> {
109-
if (since == null) {
110-
return entries.groupBy { it.category }
111-
.mapValuesTo(mutableObject2ObjectMapOf()) { (_, entry) -> entry.sumOf { it.durationSeconds }.seconds }
112-
}
67+
.mapValuesTo(mutableObject2ObjectMapOf()) { (_, group) ->
68+
group.sumOf { it.durationSeconds }.seconds
69+
}
11370

114-
return entries
115-
.filter { it.createdAt.isAfter(since) }
71+
override fun playtimesPerCategory(since: ZonedDateTime?): Object2ObjectMap<String, Duration> =
72+
entries.filter { since == null || it.createdAt.isAfter(since) }
11673
.groupBy { it.category }
117-
.mapValuesTo(mutableObject2ObjectMapOf()) { (_, entry) -> entry.sumOf { it.durationSeconds }.seconds }
118-
}
74+
.mapValuesTo(mutableObject2ObjectMapOf()) { (_, group) ->
75+
group.sumOf { it.durationSeconds }.seconds
76+
}
11977

12078
override fun averagePlaytimePerServer(
12179
category: String?,
12280
since: ZonedDateTime?
12381
): Duration {
124-
if (since == null) {
125-
val values = entries
126-
.filter { category == null || it.category.equals(category, true) }
127-
.groupBy { it.server }
128-
.mapValuesTo(mutableObject2ObjectMapOf()) { (_, entry) -> entry.sumOf { it.durationSeconds } }
129-
.values
130-
131-
if (values.isEmpty()) {
132-
return Duration.ZERO
133-
}
134-
135-
return values
136-
.average()
137-
.seconds
138-
}
139-
140-
val values = entries
82+
val sumsPerServer = entries
14183
.filter {
142-
(category == null || it.category.equals(
143-
category,
144-
true
145-
)) && it.createdAt.isAfter(since)
84+
(category == null || it.category.equals(category, ignoreCase = true)) &&
85+
(since == null || it.createdAt.isAfter(since))
14686
}
14787
.groupBy { it.server }
148-
.mapValuesTo(mutableObject2ObjectMapOf()) { (_, entry) -> entry.sumOf { it.durationSeconds } }
88+
.mapValues { (_, group) -> group.sumOf { it.durationSeconds } }
14989
.values
15090

151-
if (values.isEmpty()) {
152-
return Duration.ZERO
153-
}
154-
155-
return values
156-
.average()
157-
.seconds
91+
return if (sumsPerServer.isEmpty()) Duration.ZERO
92+
else sumsPerServer.average().seconds
15893
}
15994

16095
override fun timeline(
@@ -165,8 +100,8 @@ class PlaytimeImpl(private val entries: ObjectList<PlaytimeEntry>) : Playtime {
165100
val map = mutableObject2ObjectMapOf<ZonedDateTime, Duration>()
166101

167102
for (entry in entries) {
168-
if (category != null && !entry.category.equals(category, true)) continue
169-
if (server != null && !entry.server.equals(server, true)) continue
103+
if (category != null && !entry.category.equals(category, ignoreCase = true)) continue
104+
if (server != null && !entry.server.equals(server, ignoreCase = true)) continue
170105

171106
val bucket = floorToInterval(entry.createdAt, interval)
172107
map.merge(bucket, entry.durationSeconds.seconds) { a, b -> a + b }
@@ -178,63 +113,54 @@ class PlaytimeImpl(private val entries: ObjectList<PlaytimeEntry>) : Playtime {
178113
override fun topServers(
179114
limit: Int,
180115
since: ZonedDateTime?
181-
): ObjectList<Pair<String, Duration>> {
182-
if (since == null) {
183-
return entries
184-
.groupBy { it.server }
185-
.mapValues { (_, entry) -> entry.sumOf { it.durationSeconds }.seconds }
186-
.toList()
187-
.sortedByDescending { it.second }
188-
.take(limit)
189-
.toObjectList()
190-
}
191-
192-
return entries
193-
.filter { it.createdAt.isAfter(since) }
194-
.groupBy { it.server }
195-
.mapValues { (_, entry) -> entry.sumOf { it.durationSeconds }.seconds }
196-
.toList()
197-
.sortedByDescending { it.second }
198-
.take(limit)
199-
.toObjectList()
200-
}
116+
): ObjectList<Pair<String, Duration>> = entries
117+
.filter { since == null || it.createdAt.isAfter(since) }
118+
.groupBy { it.server }
119+
.mapValues { (_, group) -> group.sumOf { it.durationSeconds }.seconds }
120+
.toList()
121+
.sortedByDescending { it.second }
122+
.take(limit)
123+
.toObjectList()
201124

202125
override fun topCategories(
203126
limit: Int,
204127
since: ZonedDateTime?
205-
): ObjectList<Pair<String, Duration>> {
206-
if (since == null) {
207-
return entries
208-
.groupBy { it.category }
209-
.mapValues { (_, entry) -> entry.sumOf { it.durationSeconds }.seconds }
210-
.toList()
211-
.sortedByDescending { it.second }
212-
.take(limit)
213-
.toObjectList()
214-
}
215-
216-
return entries
217-
.filter { it.createdAt.isAfter(since) }
218-
.groupBy { it.category }
219-
.mapValues { (_, entry) -> entry.sumOf { it.durationSeconds }.seconds }
220-
.toList()
221-
.sortedByDescending { it.second }
222-
.take(limit)
223-
.toObjectList()
224-
}
128+
): ObjectList<Pair<String, Duration>> = entries
129+
.filter { since == null || it.createdAt.isAfter(since) }
130+
.groupBy { it.category }
131+
.mapValues { (_, group) -> group.sumOf { it.durationSeconds }.seconds }
132+
.toList()
133+
.sortedByDescending { it.second }
134+
.take(limit)
135+
.toObjectList()
225136
}
226137

138+
/**
139+
* Floors the given [ZonedDateTime] to the nearest interval defined by the [Duration].
140+
*
141+
* @param time The [ZonedDateTime] to be floored.
142+
* @param interval The [Duration] representing the interval to floor to.
143+
* @return A new [ZonedDateTime] floored to the nearest interval.
144+
*/
227145
private fun floorToInterval(time: ZonedDateTime, interval: Duration): ZonedDateTime {
228146
val seconds = interval.inWholeSeconds
229147
val epochSeconds = time.toEpochSecond()
230148
val floored = (epochSeconds / seconds) * seconds
231-
return ZonedDateTime.ofInstant(java.time.Instant.ofEpochSecond(floored), time.zone)
149+
return ZonedDateTime.ofInstant(Instant.ofEpochSecond(floored), time.zone)
232150
}
233151

234-
152+
/**
153+
* Represents a single entry in the playtime data.
154+
*
155+
* @property category The category of the playtime entry.
156+
* @property server The server associated with the playtime entry.
157+
* @property durationSeconds The duration of playtime in seconds.
158+
* @property createdAt The timestamp when the playtime entry was created.
159+
*/
160+
@Serializable
235161
data class PlaytimeEntry(
236162
val category: String,
237163
val server: String,
238164
val durationSeconds: Long,
239-
val createdAt: ZonedDateTime
165+
val createdAt: @Contextual ZonedDateTime,
240166
)

0 commit comments

Comments
 (0)