Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@

[versions]
polar = "2.3.0"

[libraries]
polar = { module = "top.polar:api", version.ref = "polar" }

[bundles]

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
package dev.slne.surf.queue.api

import dev.slne.surf.surfapi.shared.api.annotation.InternalAPIMarker

/**
* Opt-in annotation marking internal surf-queue APIs that are not intended for
* external use.
*
* Any declaration annotated with this marker requires an explicit `@OptIn(InternalSurfQueueApi::class)`
* at the call site, ensuring consumers acknowledge they are using an unsupported,
* internal API that may change without notice.
*/
@RequiresOptIn(
level = RequiresOptIn.Level.ERROR,
message = "This API is internal and should not be used outside of the library"
)
@InternalAPIMarker
annotation class InternalSurfQueueApi
Original file line number Diff line number Diff line change
@@ -1,34 +1,107 @@
package dev.slne.surf.queue.api

import dev.slne.surf.core.api.common.SurfCoreApi
import dev.slne.surf.core.api.common.server.SurfServer
import dev.slne.surf.core.api.common.surfCoreApi
import dev.slne.surf.queue.api.service.SurfQueueService
import it.unimi.dsi.fastutil.objects.Object2IntMap
import it.unimi.dsi.fastutil.objects.ObjectList
import java.util.*

/**
* Public API interface for a server-specific player queue.
*
* Each instance represents the queue for a single target server, identified by [serverName].
* All queue operations are suspend functions and safe to call from any coroutine context.
*/
interface SurfQueue {

/** The name of the target server this queue belongs to. */
val serverName: String

fun server() = surfCoreApi.getServerByName(serverName)
/**
* Returns the [SurfServer] instance for this queue's target server.
*/
fun server() = SurfCoreApi.getServerByName(serverName)

/**
* Enqueues [uuid] using the priority resolved from LuckPerms.
*
* @return `true` if the player was newly added, `false` if already queued.
*/
suspend fun enqueue(uuid: UUID): Boolean

/**
* Enqueues [uuid] with an explicit [priority].
* Priorities above the maximum representable value are capped automatically.
*
* @return `true` if the player was newly added, `false` if already queued.
*/
suspend fun enqueue(uuid: UUID, priority: Int): Boolean

/**
* Removes [uuid] from the queue.
*
* @return `true` if the player was removed, `false` if they were not queued.
*/
suspend fun dequeue(uuid: UUID): Boolean

/**
* Returns `true` if [uuid] is currently in the queue.
*/
suspend fun isQueued(uuid: UUID): Boolean

/**
* Returns the 0-based position of [uuid] in the queue, or `null` if not queued.
*/
suspend fun getPosition(uuid: UUID): Int?

/**
* Returns the total number of players currently in the queue.
*/
suspend fun size(): Int

/**
* Returns `true` if the queue is paused. A paused queue stops processing transfers.
*/
suspend fun isPaused(): Boolean

/** Pauses the queue. */
suspend fun pause()

/** Resumes the queue. */
suspend fun resume()

suspend fun getAllUuidsWithPosition(): Collection<Object2IntMap.Entry<UUID>>
/**
* Returns all queued UUIDs together with their 1-based position.
*
* @deprecated Use [getAllUuidsOrderedByPosition] for better performance.
*/
@Deprecated(
"Use getAllUuidsOrderedByPosition for better performance",
ReplaceWith("getAllUuidsOrderedByPosition()")
)
suspend fun getAllUuidsWithPosition(): ObjectList<Object2IntMap.Entry<UUID>>

/**
* Returns all queued UUIDs in ascending position order (position 1 first).
*/
suspend fun getAllUuidsOrderedByPosition(): ObjectList<UUID>

@OptIn(InternalSurfQueueApi::class)
companion object {
fun byServer(serverName: String) = SurfQueueService.instance.get(serverName)
/**
* Returns the [SurfQueue] for the given [serverName], or creates a new one if it doesn't exist.
*/
fun byServer(serverName: String) = SurfQueueService.instance.getQueueByName(serverName)

/**
* Returns the [SurfQueue] for the given [server], or creates a new one if it doesn't exist.
*/
fun byServer(server: SurfServer) = byServer(server.name)
}
}

/**
* Convenience extension to retrieve the queue for this server.
*/
fun SurfServer.queue() = SurfQueue.byServer(this)
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,24 @@ import dev.slne.surf.queue.api.InternalSurfQueueApi
import dev.slne.surf.queue.api.SurfQueue
import dev.slne.surf.surfapi.core.api.util.requiredService

/**
* Internal service interface responsible for creating and caching [SurfQueue] instances.
*
* The concrete implementation is
* loaded via `@AutoService` and accessed through the [instance] companion property.
* This interface is marked as [InternalSurfQueueApi] and should not be used directly;
* prefer [SurfQueue.byServer] instead.
*/
@InternalSurfQueueApi
interface SurfQueueService {
fun get(serverName: String): SurfQueue
/**
* Returns the [SurfQueue] for the given [serverName], creating a new one if it
* does not already exist in the cache.
*
* @param serverName the name of the target server
* @return the queue instance for that server
*/
fun getQueueByName(serverName: String): SurfQueue

companion object {
val instance = requiredService<SurfQueueService>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package dev.slne.surf.queue.common

import dev.slne.surf.queue.common.queue.AbstractQueue
import dev.slne.surf.queue.common.queue.tick.QueueTicker
import dev.slne.surf.queue.common.queue.RedisQueueService
import dev.slne.surf.queue.common.redis.RedisInstance
import dev.slne.surf.surfapi.core.api.component.SurfComponentApi
import dev.slne.surf.surfapi.core.api.util.requiredService
import org.jetbrains.annotations.MustBeInvokedByOverriders

/**
* Abstract base class that bootstraps the queue system.
*
* Responsible for connecting to Redis, loading queue state, managing
* [SurfComponentApi] lifecycle, and tearing down the [QueueTicker].
* Platform-specific subclasses (Paper, Velocity) provide the [componentOwner]
* and implement [createQueue] to return the appropriate [AbstractQueue] variant.
*
* Implementations are responsible for starting the [QueueTicker] at the
* appropriate time during their platform's lifecycle.
*/
abstract class QueueInstance { // Implementations are responsible for starting the queue ticker task
/** The platform-specific owner object used with [SurfComponentApi]. */
protected abstract val componentOwner: Any

/**
* Connects to Redis, fetches existing queues, and loads all [dev.slne.surf.surfapi.shared.api.component.SurfComponent]s.
* Subclasses **must** call `super.load()`.
*/
@MustBeInvokedByOverriders
open suspend fun load() {
RedisInstance.get().connect()
RedisQueueService.get().fetchFromRedis()
SurfComponentApi.load(componentOwner)
}

/**
* Enables all [dev.slne.surf.surfapi.shared.api.component.SurfComponent]s. Subclasses **must** call `super.enable()`.
*/
@MustBeInvokedByOverriders
open suspend fun enable() {
SurfComponentApi.enable(componentOwner)
}

/**
* Stops the [QueueTicker], disables all [dev.slne.surf.surfapi.shared.api.component.SurfComponent]s, and disconnects
* from Redis. Subclasses **must** call `super.disable()`.
*/
@MustBeInvokedByOverriders
open suspend fun disable() {
QueueTicker.dispose()

SurfComponentApi.disable(componentOwner)
RedisInstance.get().disconnect()
}

/**
* Factory method to create a new [AbstractQueue] for the given [serverName].
*
* @param serverName the name of the target server
* @return a platform-specific queue implementation
*/
abstract fun createQueue(serverName: String): AbstractQueue

companion object {
val instance = requiredService<QueueInstance>()
fun get() = instance
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import dev.slne.surf.surfapi.shared.api.component.ComponentMeta
import dev.slne.surf.surfapi.shared.api.component.requirement.ConditionalOnMissingComponent
import java.util.UUID

/**
* Default [PriorityHook] implementation that always returns priority `0`.
*
* Activated automatically by `@ConditionalOnMissingComponent` when no other
* [PriorityHook] (e.g., [LuckpermsPriorityHook]) is available.
*/
@ComponentMeta
@ConditionalOnMissingComponent(PriorityHook::class)
class FallbackPriority : PriorityHook {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@ import net.luckperms.api.LuckPermsProvider
import net.luckperms.api.node.NodeType
import java.util.*

/**
* [PriorityHook] implementation that resolves queue priority from a LuckPerms
* meta key.
*
* Reads the `queue-priority` meta value from the player's inherited nodes.
* If the meta key is absent or not a valid integer, the priority defaults to `0`.
*/
//@ComponentMeta
//@DependsOnClass(LuckPerms::class)
object LuckpermsPriorityHook : PriorityHook {
// companion object {
/** The LuckPerms meta key used to store a player's queue priority. */
const val KEY = "queue-priority"
// }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,25 @@ import dev.slne.surf.surfapi.core.api.component.SurfComponentApi
import dev.slne.surf.surfapi.shared.api.component.SurfComponent
import java.util.*

/**
* Hook interface for resolving a player's queue priority.
*
* Implementations are registered as [SurfComponent]s. The active implementation
* is determined by the component system — [LuckpermsPriorityHook] when LuckPerms
* is present, otherwise [FallbackPriority].
*/
interface PriorityHook : SurfComponent {

/**
* Returns the queue priority for the player identified by [uuid].
*
* @param uuid the player's unique identifier
* @return a non-negative priority value; higher values mean higher priority
*/
suspend fun getPriority(uuid: UUID): Int

companion object {
/** Returns the first loaded [PriorityHook] implementation. */
fun get() = SurfComponentApi.componentsOfTypeLoaded(PriorityHook::class.java).first()
}
}
Expand Down
Loading