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

Commit ed42f71

Browse files
committed
feat: add playtime tracking entities and implementation for player analytics
1 parent fc40aa4 commit ed42f71

File tree

4 files changed

+426
-1
lines changed
  • surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/player/playtime
  • surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/playtime
  • surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/db/exposed

4 files changed

+426
-1
lines changed
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package dev.slne.surf.cloud.api.common.player.playtime
2+
3+
import dev.slne.surf.cloud.api.common.server.CloudServer
4+
import it.unimi.dsi.fastutil.objects.Object2ObjectMap
5+
import it.unimi.dsi.fastutil.objects.ObjectList
6+
import it.unimi.dsi.fastutil.objects.ObjectSet
7+
import java.time.ZonedDateTime
8+
import kotlin.time.Duration
9+
10+
/**
11+
* Provides a comprehensive analytical view of playtime data, allowing various queries and analyses
12+
* based on servers, categories, and timeframes.
13+
*
14+
* This interface is immutable and provides methods to perform detailed aggregations and analytics
15+
* without altering the underlying data.
16+
*/
17+
interface Playtime {
18+
19+
/**
20+
* Returns the total playtime across all servers and categories.
21+
*
22+
* @param since Optional start time. If provided, only playtime after this timestamp is considered.
23+
* @return The summed total playtime as a [Duration].
24+
*/
25+
fun sumPlaytimes(since: ZonedDateTime? = null): Duration
26+
27+
/**
28+
* Returns the total playtime for a specific category.
29+
*
30+
* @param category The category to filter by.
31+
* @param since Optional start time to filter playtime.
32+
* @return The summed total playtime for the category as a [Duration].
33+
*/
34+
fun sumByCategory(category: String, since: ZonedDateTime? = null): Duration
35+
36+
/**
37+
* Returns the total playtime on a specific server identified by its name.
38+
*
39+
* @param server The server name to filter by.
40+
* @param since Optional start time to filter playtime.
41+
* @return The summed total playtime for the specified server as a [Duration].
42+
*/
43+
fun sumByServer(server: String, since: ZonedDateTime? = null): Duration
44+
45+
/**
46+
* Returns the total playtime on a specific [CloudServer].
47+
*
48+
* @param server The [CloudServer] to filter by.
49+
* @param since Optional start time to filter playtime.
50+
* @return The summed total playtime for the specified server as a [Duration].
51+
*/
52+
fun sumByServer(server: CloudServer, since: ZonedDateTime? = null): Duration {
53+
return sumByServer(server.name, since)
54+
}
55+
56+
/**
57+
* Returns a set of all distinct categories present in the playtime data.
58+
*
59+
* @return An [ObjectSet] of unique category names.
60+
*/
61+
fun getCategories(): ObjectSet<String>
62+
63+
/**
64+
* Returns a set of all distinct server names present in the playtime data.
65+
*
66+
* @return An [ObjectSet] of unique server names.
67+
*/
68+
fun getServers(): ObjectSet<String>
69+
70+
/**
71+
* Returns the total playtime for a specific server and optionally a specific category.
72+
*
73+
* @param server The server name.
74+
* @param category Optional category to further filter results.
75+
* @param since Optional start time to filter playtime.
76+
* @return The summed total playtime matching the specified filters as a [Duration].
77+
*/
78+
fun playtimeFor(
79+
server: String,
80+
category: String? = null,
81+
since: ZonedDateTime? = null
82+
): Duration
83+
84+
/**
85+
* Returns a mapping of servers to their respective total playtime durations.
86+
*
87+
* @param since Optional start time to filter playtime.
88+
* @return An [Object2ObjectMap] where keys are server names and values are durations.
89+
*/
90+
fun playtimesPerServer(since: ZonedDateTime? = null): Object2ObjectMap<String, Duration>
91+
92+
/**
93+
* Returns a mapping of categories to their respective total playtime durations.
94+
*
95+
* @param since Optional start time to filter playtime.
96+
* @return An [Object2ObjectMap] where keys are category names and values are durations.
97+
*/
98+
fun playtimesPerCategory(since: ZonedDateTime? = null): Object2ObjectMap<String, Duration>
99+
100+
/**
101+
* Returns the average playtime per server, optionally filtered by category and start time.
102+
*
103+
* @param category Optional category to filter by.
104+
* @param since Optional start time to filter playtime.
105+
* @return The average playtime across servers as a [Duration].
106+
*/
107+
fun averagePlaytimePerServer(category: String? = null, since: ZonedDateTime? = null): Duration
108+
109+
/**
110+
* Generates a timeline mapping timestamps to accumulated playtime durations, grouped by specified intervals.
111+
* Each interval represents a bucket of time starting at the beginning of the interval and includes the total
112+
* playtime that occurred within that interval. This is particularly useful for analyzing player activity trends,
113+
* identifying peak playing times, or generating visualizations such as heatmaps and activity charts.
114+
*
115+
* For example, if you choose an hourly interval, each timestamp in the resulting map will correspond
116+
* precisely to the start of that hour, with its associated duration representing the sum of all playtime
117+
* recorded between that hour and the start of the next hour.
118+
*
119+
* ### Example use case:
120+
*
121+
* Suppose you want to analyze player activity throughout the last day to determine peak gaming hours on
122+
* the server named "PvP-Arena" within the "competitive" category. You could use:
123+
*
124+
* ```kotlin
125+
* val hourlyTimeline = playtime.timeline(
126+
* interval = 1.hours,
127+
* category = "competitive",
128+
* server = "PvP-Arena"
129+
* )
130+
*
131+
* hourlyTimeline.forEach { (hour, duration) ->
132+
* println("Playtime from $hour to ${hour.plusHours(1)}: $duration")
133+
* }
134+
* ```
135+
*
136+
* The resulting output might look like:
137+
*
138+
* ```
139+
* Playtime from 2025-04-08T14:00Z to 2025-04-08T15:00Z: 30m
140+
* Playtime from 2025-04-08T15:00Z to 2025-04-08T16:00Z: 45m
141+
* Playtime from 2025-04-08T16:00Z to 2025-04-08T17:00Z: 1h
142+
* ...
143+
* ```
144+
*
145+
* This clearly illustrates player activity peaks, enabling targeted actions such as scheduling server events,
146+
* balancing loads, or informing community engagement strategies.
147+
*
148+
* @param interval The duration of each interval (e.g., hourly, daily, weekly).
149+
* @param category Optional category filter. If specified, only playtime matching this category is considered.
150+
* @param server Optional server filter. If specified, only playtime on this particular server is included.
151+
* @return An [Object2ObjectMap] mapping interval-start timestamps to the total accumulated playtime durations
152+
* within each interval.
153+
*/
154+
fun timeline(
155+
interval: Duration,
156+
category: String? = null,
157+
server: String? = null
158+
): Object2ObjectMap<ZonedDateTime, Duration>
159+
160+
/**
161+
* Retrieves a ranked list of servers sorted by total playtime in descending order.
162+
*
163+
* @param limit Maximum number of results to return.
164+
* @param since Optional start time to filter playtime.
165+
* @return An [ObjectList] of pairs, each containing a server name and its corresponding playtime duration.
166+
*/
167+
fun topServers(limit: Int = 5, since: ZonedDateTime? = null): ObjectList<Pair<String, Duration>>
168+
169+
/**
170+
* Retrieves a ranked list of categories sorted by total playtime in descending order.
171+
*
172+
* @param limit Maximum number of results to return.
173+
* @param since Optional start time to filter playtime.
174+
* @return An [ObjectList] of pairs, each containing a category name and its corresponding playtime duration.
175+
*/
176+
fun topCategories(limit: Int = 5, since: ZonedDateTime? = null): ObjectList<Pair<String, Duration>>
177+
}

0 commit comments

Comments
 (0)