Introduce DataStorage
This commit is contained in:
parent
4e34dbc1e2
commit
818939267a
18 changed files with 220 additions and 45 deletions
|
@ -5,8 +5,8 @@
|
||||||
|
|
||||||
package space.uranos.testplugin
|
package space.uranos.testplugin
|
||||||
|
|
||||||
import space.uranos.Position
|
|
||||||
import space.uranos.Uranos
|
import space.uranos.Uranos
|
||||||
|
import space.uranos.chat.ChatColor
|
||||||
import space.uranos.chat.TextComponent
|
import space.uranos.chat.TextComponent
|
||||||
import space.uranos.net.ServerListInfo
|
import space.uranos.net.ServerListInfo
|
||||||
import space.uranos.net.event.ServerListInfoRequestEvent
|
import space.uranos.net.event.ServerListInfoRequestEvent
|
||||||
|
@ -14,6 +14,7 @@ import space.uranos.net.event.SessionAfterLoginEvent
|
||||||
import space.uranos.player.GameMode
|
import space.uranos.player.GameMode
|
||||||
import space.uranos.plugin.Plugin
|
import space.uranos.plugin.Plugin
|
||||||
import space.uranos.testplugin.anvil.AnvilWorld
|
import space.uranos.testplugin.anvil.AnvilWorld
|
||||||
|
import space.uranos.util.secondsToTicks
|
||||||
import space.uranos.world.Dimension
|
import space.uranos.world.Dimension
|
||||||
import space.uranos.world.VoxelLocation
|
import space.uranos.world.VoxelLocation
|
||||||
import space.uranos.world.block.CraftingTableBlock
|
import space.uranos.world.block.CraftingTableBlock
|
||||||
|
@ -51,6 +52,10 @@ class TestPlugin: Plugin("Test", "1.0.0") {
|
||||||
.atTopCenter()
|
.atTopCenter()
|
||||||
.withRotation(0f, 0f)
|
.withRotation(0f, 0f)
|
||||||
.inside(world)
|
.inside(world)
|
||||||
|
|
||||||
|
Uranos.scheduler.executeAfter(secondsToTicks(10)) {
|
||||||
|
Uranos.players.first().playerListName = TextComponent("Test", true, color = ChatColor.BLUE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,14 +14,14 @@ interface Scheduler {
|
||||||
*
|
*
|
||||||
* If you need the return value of [block], use [runAfter].
|
* If you need the return value of [block], use [runAfter].
|
||||||
*/
|
*/
|
||||||
fun executeAfter(delay: Int, block: suspend () -> Unit): Task
|
fun executeAfter(delay: Long, block: suspend () -> Unit): Task
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes [block] every [interval] ticks.
|
* Executes [block] every [interval] ticks.
|
||||||
*
|
*
|
||||||
* @param delay The ticks to pass before the first execution.
|
* @param delay The ticks to pass before the first execution.
|
||||||
*/
|
*/
|
||||||
fun executeRepeating(interval: Int, delay: Int = interval, block: suspend () -> Any): Task
|
fun executeRepeating(interval: Long, delay: Long = interval, block: suspend () -> Unit): Task
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes [block] when the scheduler (and therefore the server) is shutdown.
|
* Executes [block] when the scheduler (and therefore the server) is shutdown.
|
||||||
|
@ -35,13 +35,13 @@ interface Scheduler {
|
||||||
*
|
*
|
||||||
* If you do not need the return value of [block], you should use [executeAfter] instead.
|
* If you do not need the return value of [block], you should use [executeAfter] instead.
|
||||||
*/
|
*/
|
||||||
suspend fun <R : Any> runAfter(delay: Int, inServerThread: Boolean = false, block: suspend () -> R): R
|
suspend fun <R> runAfter(delay: Long, block: suspend () -> R): R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like [runAfter], but the task is *not* cancelled when the current coroutine scope is cancelled.
|
* Like [runAfter], but the task is *not* cancelled when the current coroutine scope is cancelled.
|
||||||
*/
|
*/
|
||||||
suspend fun <R : Any> runDetachedAfter(delay: Int, inServerThread: Boolean = false, block: suspend () -> R): R =
|
suspend fun <R> runDetachedAfter(delay: Long, block: suspend () -> R): R =
|
||||||
withContext(NonCancellable) { runAfter(delay, inServerThread, block) }
|
withContext(NonCancellable) { runAfter(delay, block) }
|
||||||
|
|
||||||
interface Task {
|
interface Task {
|
||||||
fun cancel()
|
fun cancel()
|
||||||
|
|
|
@ -25,7 +25,7 @@ abstract class Entity internal constructor() {
|
||||||
abstract val type: EntityType
|
abstract val type: EntityType
|
||||||
|
|
||||||
private val worldMutex = Mutex()
|
private val worldMutex = Mutex()
|
||||||
var world: World? = null; private set
|
var world: World? = null; protected set
|
||||||
|
|
||||||
suspend fun setWorld(world: World?) {
|
suspend fun setWorld(world: World?) {
|
||||||
if (world == null && this is PlayerEntity)
|
if (world == null && this is PlayerEntity)
|
||||||
|
|
|
@ -47,7 +47,8 @@ abstract class Session {
|
||||||
/**
|
/**
|
||||||
* The player corresponding to this session.
|
* The player corresponding to this session.
|
||||||
*/
|
*/
|
||||||
val player: Player? get() = (state as? State.WithPlayer)?.player
|
val player: Player? get() = (state as? State.Playing)?.player
|
||||||
|
// val player: Player? get() = (state as? State.WithPlayer)?.player
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current state of this session.
|
* The current state of this session.
|
||||||
|
|
|
@ -8,10 +8,12 @@ package space.uranos.util
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class KClassToInstanceMap<K : Any> : MutableMap<KClass<out K>, K> by HashMap() {
|
class KClassToInstanceMap<K : Any> : MutableMap<KClass<out K>, K> by HashMap() {
|
||||||
|
val instances get() = this.values
|
||||||
|
|
||||||
fun <T : K> getInstance(key: Key<T>): T? = getInstance(key.actualKey)
|
fun <T : K> getInstance(key: Key<T>): T? = getInstance(key.actualKey)
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : K> getInstance(key: KClass<T>): T? = get(key) as T
|
fun <T : K> getInstance(key: KClass<T>): T? = get(key) as T?
|
||||||
|
|
||||||
fun <T : K> putInstance(key: Key<T>, value: T) = this.putInstance(key.actualKey, value)
|
fun <T : K> putInstance(key: Key<T>, value: T) = this.putInstance(key.actualKey, value)
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import space.uranos.server.Server
|
||||||
/**
|
/**
|
||||||
* Suspends for [ticks].
|
* Suspends for [ticks].
|
||||||
*/
|
*/
|
||||||
suspend inline fun delayTicks(ticks: Int) {
|
suspend inline fun delayTicks(ticks: Long) {
|
||||||
Uranos.scheduler.runAfter(ticks) {}
|
Uranos.scheduler.runAfter(ticks) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||||
|
*/
|
||||||
|
|
||||||
|
package space.uranos.util
|
||||||
|
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import space.uranos.Uranos
|
||||||
|
|
||||||
|
suspend fun <T> runInServerThread(block: suspend () -> T): T = withContext(Uranos.coroutineContext) { block() }
|
|
@ -12,7 +12,7 @@ import space.uranos.net.packet.IncomingPacketCodec
|
||||||
object IncomingPlayerPositionPacketCodec :
|
object IncomingPlayerPositionPacketCodec :
|
||||||
IncomingPacketCodec<IncomingPlayerPositionPacket>(0x13, IncomingPlayerPositionPacket::class) {
|
IncomingPacketCodec<IncomingPlayerPositionPacket>(0x13, IncomingPlayerPositionPacket::class) {
|
||||||
override fun decode(msg: ByteBuf): IncomingPlayerPositionPacket = IncomingPlayerPositionPacket(
|
override fun decode(msg: ByteBuf): IncomingPlayerPositionPacket = IncomingPlayerPositionPacket(
|
||||||
Position(msg.readDouble(), msg.readDouble(), msg.readDouble(), 360 - msg.readFloat() % 360, msg.readFloat()),
|
Position(msg.readDouble(), msg.readDouble(), msg.readDouble(), (360 - msg.readFloat()) % 360, msg.readFloat()),
|
||||||
msg.readBoolean()
|
msg.readBoolean()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,21 +5,23 @@
|
||||||
|
|
||||||
package space.uranos
|
package space.uranos
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.CancellableContinuation
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import space.uranos.logging.Logger
|
import space.uranos.logging.Logger
|
||||||
import space.uranos.server.Server
|
import space.uranos.server.Server
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.*
|
||||||
import java.util.concurrent.ScheduledExecutorService
|
|
||||||
import java.util.concurrent.ScheduledFuture
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import kotlin.collections.LinkedHashSet
|
import kotlin.collections.LinkedHashSet
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basically ExecutorService but for coroutines and with ticks.
|
* Basically ExecutorService but for coroutines and with ticks.
|
||||||
*/
|
*/
|
||||||
class UranosScheduler(private val executor: ScheduledExecutorService) : Scheduler {
|
class UranosScheduler : Scheduler {
|
||||||
|
private val executor: ScheduledExecutorService =
|
||||||
|
Executors.newSingleThreadScheduledExecutor { r -> Thread(r, "Scheduler") }
|
||||||
|
|
||||||
private val tasks = ConcurrentHashMap.newKeySet<Task<out Any>>()
|
private val tasks = ConcurrentHashMap.newKeySet<Task<out Any>>()
|
||||||
private val shutdownTasks = Collections.synchronizedSet(LinkedHashSet<suspend () -> Unit>())
|
private val shutdownTasks = Collections.synchronizedSet(LinkedHashSet<suspend () -> Unit>())
|
||||||
|
|
||||||
|
@ -27,8 +29,8 @@ class UranosScheduler(private val executor: ScheduledExecutorService) : Schedule
|
||||||
|
|
||||||
inner class Task<R : Any>(
|
inner class Task<R : Any>(
|
||||||
val fn: suspend () -> R,
|
val fn: suspend () -> R,
|
||||||
val interval: Int?,
|
val interval: Long?,
|
||||||
var ticksUntilExecution: Int
|
var ticksUntilExecution: Long
|
||||||
) : Scheduler.Task {
|
) : Scheduler.Task {
|
||||||
@Volatile
|
@Volatile
|
||||||
var cancelled: Boolean = false
|
var cancelled: Boolean = false
|
||||||
|
@ -85,13 +87,13 @@ class UranosScheduler(private val executor: ScheduledExecutorService) : Schedule
|
||||||
shutdownTasks.forEach { it.invoke() }
|
shutdownTasks.forEach { it.invoke() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun executeAfter(delay: Int, block: suspend () -> Unit): Scheduler.Task {
|
override fun executeAfter(delay: Long, block: suspend () -> Unit): Scheduler.Task {
|
||||||
val task = Task(block, null, delay)
|
val task = Task(block, null, delay)
|
||||||
tasks.add(task)
|
tasks.add(task)
|
||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun executeRepeating(interval: Int, delay: Int, block: suspend () -> Any): Scheduler.Task {
|
override fun executeRepeating(interval: Long, delay: Long, block: suspend () -> Unit): Scheduler.Task {
|
||||||
val task = Task(block, interval, delay)
|
val task = Task(block, interval, delay)
|
||||||
tasks.add(task)
|
tasks.add(task)
|
||||||
return task
|
return task
|
||||||
|
@ -107,15 +109,10 @@ class UranosScheduler(private val executor: ScheduledExecutorService) : Schedule
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use the current coroutine context for the task execution
|
override suspend fun <R> runAfter(delay: Long, block: suspend () -> R): R {
|
||||||
override suspend fun <R : Any> runAfter(delay: Int, inServerThread: Boolean, block: suspend () -> R): R {
|
|
||||||
lateinit var continuation: CancellableContinuation<R>
|
lateinit var continuation: CancellableContinuation<R>
|
||||||
val context = currentCoroutineContext()
|
|
||||||
|
|
||||||
val fn =
|
|
||||||
if (inServerThread) suspend { continuation.resume(block()) }
|
|
||||||
else suspend { withContext(context) { continuation.resume(block()) } }
|
|
||||||
|
|
||||||
|
val fn = suspend { continuation.resume(block()) }
|
||||||
val task = Task(fn, null, delay)
|
val task = Task(fn, null, delay)
|
||||||
|
|
||||||
return suspendCancellableCoroutine {
|
return suspendCancellableCoroutine {
|
||||||
|
|
|
@ -19,17 +19,19 @@ import space.uranos.event.UranosEventHandlerPositionManager
|
||||||
import space.uranos.logging.Logger
|
import space.uranos.logging.Logger
|
||||||
import space.uranos.logging.UranosLoggingOutputProvider
|
import space.uranos.logging.UranosLoggingOutputProvider
|
||||||
import space.uranos.net.UranosSocketServer
|
import space.uranos.net.UranosSocketServer
|
||||||
|
import space.uranos.net.packet.play.PlayerInfoPacket
|
||||||
import space.uranos.player.UranosPlayer
|
import space.uranos.player.UranosPlayer
|
||||||
import space.uranos.plugin.UranosPluginManager
|
import space.uranos.plugin.UranosPluginManager
|
||||||
import space.uranos.recipe.Recipe
|
import space.uranos.recipe.Recipe
|
||||||
import space.uranos.server.Server
|
import space.uranos.server.Server
|
||||||
import space.uranos.util.EncryptionUtils
|
import space.uranos.util.EncryptionUtils
|
||||||
|
import space.uranos.util.msToTicks
|
||||||
import space.uranos.world.BiomeRegistry
|
import space.uranos.world.BiomeRegistry
|
||||||
import space.uranos.world.Dimension
|
import space.uranos.world.Dimension
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.ScheduledExecutorService
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
@ -49,8 +51,8 @@ class UranosServer internal constructor() : Server() {
|
||||||
val x509EncodedPublicKey: ByteArray = EncryptionUtils.generateX509Key(keyPair.public).encoded
|
val x509EncodedPublicKey: ByteArray = EncryptionUtils.generateX509Key(keyPair.public).encoded
|
||||||
|
|
||||||
override lateinit var serverThread: Thread
|
override lateinit var serverThread: Thread
|
||||||
private val scheduledExecutorService: ScheduledExecutorService =
|
private val scheduledExecutorService: ExecutorService =
|
||||||
Executors.newSingleThreadScheduledExecutor { r -> Thread(r, "server").also { serverThread = it } }
|
Executors.newSingleThreadExecutor { r -> Thread(r, "Server").also { serverThread = it } }
|
||||||
|
|
||||||
override val coroutineContext: CoroutineContext =
|
override val coroutineContext: CoroutineContext =
|
||||||
CoroutineName("Server") + SupervisorJob() + scheduledExecutorService.asCoroutineDispatcher()
|
CoroutineName("Server") + SupervisorJob() + scheduledExecutorService.asCoroutineDispatcher()
|
||||||
|
@ -72,7 +74,7 @@ class UranosServer internal constructor() : Server() {
|
||||||
override val biomeRegistry = BiomeRegistry()
|
override val biomeRegistry = BiomeRegistry()
|
||||||
|
|
||||||
override val loggingOutputProvider = UranosLoggingOutputProvider
|
override val loggingOutputProvider = UranosLoggingOutputProvider
|
||||||
override val scheduler = UranosScheduler(scheduledExecutorService)
|
override val scheduler = UranosScheduler()
|
||||||
|
|
||||||
val config = ConfigLoader.Builder()
|
val config = ConfigLoader.Builder()
|
||||||
.addPropertySource(
|
.addPropertySource(
|
||||||
|
@ -116,6 +118,24 @@ class UranosServer internal constructor() : Server() {
|
||||||
logger info "Listening on ${config.host}:${config.port}"
|
logger info "Listening on ${config.host}:${config.port}"
|
||||||
|
|
||||||
scheduler.start()
|
scheduler.start()
|
||||||
|
startDataStorageTicking()
|
||||||
|
startPingSync()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startDataStorageTicking() {
|
||||||
|
scheduler.executeRepeating(1, 0) {
|
||||||
|
players.forEach { it.dataStorage.tick() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startPingSync() {
|
||||||
|
scheduler.executeRepeating(msToTicks(config.pingUpdateInterval.toMillis()), 0) {
|
||||||
|
val packet = PlayerInfoPacket(
|
||||||
|
PlayerInfoPacket.Action.UpdateLatency(players.map { it.uuid to it.session.ping }.toMap())
|
||||||
|
)
|
||||||
|
|
||||||
|
players.forEach { it.session.send(packet) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -17,5 +17,6 @@ data class UranosConfig(
|
||||||
val minLogLevel: Logger.Level,
|
val minLogLevel: Logger.Level,
|
||||||
val packetCompressionThreshold: Int,
|
val packetCompressionThreshold: Int,
|
||||||
val timeout: Duration,
|
val timeout: Duration,
|
||||||
val packetsBufferSize: Int
|
val packetsBufferSize: Int,
|
||||||
|
val pingUpdateInterval: Duration
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||||
|
*/
|
||||||
|
|
||||||
|
package space.uranos.data
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
|
class DataStorage<ContextT : Any>(val context: ContextT) {
|
||||||
|
private val map = HashMap<DataStorageKey<ContextT, *>, Entry<*>>()
|
||||||
|
|
||||||
|
inner class Entry<V>(private val key: DataStorageKey<ContextT, V>, value: V) {
|
||||||
|
private var oldValueRef = WeakReference(value)
|
||||||
|
private var changed = true
|
||||||
|
|
||||||
|
var value: V = value
|
||||||
|
set(value) {
|
||||||
|
val oldValue = oldValueRef.get()
|
||||||
|
if (oldValue == value) return
|
||||||
|
field = value
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun tick(): DataStorageCombinableAction<ContextT, *>? {
|
||||||
|
var action = key.tick(context, value, changed)
|
||||||
|
|
||||||
|
if (changed) action = key.tickIfChanged(context, value)
|
||||||
|
|
||||||
|
oldValueRef = WeakReference(value)
|
||||||
|
changed = false
|
||||||
|
|
||||||
|
return action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <V> set(key: DataStorageKey<ContextT, V>, value: V) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val entry = map[key] as Entry<V>?
|
||||||
|
|
||||||
|
println(key)
|
||||||
|
println(value)
|
||||||
|
|
||||||
|
if (entry == null) map[key] = Entry(key, value)
|
||||||
|
else entry.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun <V> get(key: DataStorageKey<ContextT, V>) = map[key] as V
|
||||||
|
|
||||||
|
suspend fun tick() {
|
||||||
|
val actions = map.values.mapNotNull { it.tick() }
|
||||||
|
|
||||||
|
actions.groupBy { it.key }.forEach { (key, values) ->
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
key as DataStorageCombinableActionKey<ContextT, Any>
|
||||||
|
|
||||||
|
key.tick(context, values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||||
|
*/
|
||||||
|
|
||||||
|
package space.uranos.data
|
||||||
|
|
||||||
|
data class DataStorageCombinableAction<ContextT, V>(val key: DataStorageCombinableActionKey<ContextT, V>, val value: V)
|
||||||
|
|
||||||
|
abstract class DataStorageCombinableActionKey<ContextT, V> {
|
||||||
|
open suspend fun tick(context: ContextT, values: List<V>) {}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||||
|
*/
|
||||||
|
|
||||||
|
package space.uranos.data
|
||||||
|
|
||||||
|
abstract class DataStorageKey<ContextT, V>(val name: String) {
|
||||||
|
open suspend fun tick(context: ContextT, value: V, changed: Boolean): DataStorageCombinableAction<ContextT, *>? =
|
||||||
|
null
|
||||||
|
|
||||||
|
open suspend fun tickIfChanged(context: ContextT, value: V): DataStorageCombinableAction<ContextT, *>? = null
|
||||||
|
|
||||||
|
override fun toString(): String = "DataStorageKey:$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <ContextT : Any, V> createDataStorageKey(
|
||||||
|
name: String,
|
||||||
|
tick: suspend (context: ContextT, value: V, changed: Boolean) -> DataStorageCombinableAction<ContextT, *>? = { _, _, _ -> null },
|
||||||
|
tickIfChanged: suspend (context: ContextT, value: V) -> DataStorageCombinableAction<ContextT, *>? = { _, _ -> null }
|
||||||
|
) =
|
||||||
|
object : DataStorageKey<ContextT, V>(name) {
|
||||||
|
override suspend fun tick(context: ContextT, value: V, changed: Boolean) = tick(context, value, changed)
|
||||||
|
override suspend fun tickIfChanged(context: ContextT, value: V) = tickIfChanged(context, value)
|
||||||
|
}
|
|
@ -93,7 +93,6 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
||||||
session.disconnect(internalReason = "No spawn location set")
|
session.disconnect(internalReason = "No spawn location set")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// TODO: Spawn the player entity
|
|
||||||
session.send(
|
session.send(
|
||||||
JoinGamePacket(
|
JoinGamePacket(
|
||||||
0,
|
0,
|
||||||
|
@ -198,8 +197,7 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
||||||
session.send(
|
session.send(
|
||||||
PlayerInfoPacket(
|
PlayerInfoPacket(
|
||||||
PlayerInfoPacket.Action.UpdateLatency(
|
PlayerInfoPacket.Action.UpdateLatency(
|
||||||
session.server.players
|
session.server.players.map { it.uuid to it.session.ping }.toMap()
|
||||||
.map { it.uuid to it.session.ping }.toMap()
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -207,6 +205,8 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
||||||
session.send(UpdateViewPositionPacket(Chunk.Key.from(player.entity.position.toVoxelLocation())))
|
session.send(UpdateViewPositionPacket(Chunk.Key.from(player.entity.position.toVoxelLocation())))
|
||||||
|
|
||||||
session.scheduleKeepAlivePacket(true)
|
session.scheduleKeepAlivePacket(true)
|
||||||
|
|
||||||
|
player.spawnInitially(state.world)
|
||||||
player.sendChunksAndLight()
|
player.sendChunksAndLight()
|
||||||
|
|
||||||
// WorldBorder
|
// WorldBorder
|
||||||
|
@ -214,5 +214,6 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
||||||
session.send(OutgoingPlayerPositionPacket(state.position))
|
session.send(OutgoingPlayerPositionPacket(state.position))
|
||||||
|
|
||||||
// TODO: Wait for ClientStatus(action=0) packet
|
// TODO: Wait for ClientStatus(action=0) packet
|
||||||
|
session.state = Session.State.Playing(player)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import space.uranos.net.packet.login.LoginProtocol
|
||||||
import space.uranos.net.packet.play.OutgoingKeepAlivePacket
|
import space.uranos.net.packet.play.OutgoingKeepAlivePacket
|
||||||
import space.uranos.net.packet.play.OutgoingPluginMessagePacket
|
import space.uranos.net.packet.play.OutgoingPluginMessagePacket
|
||||||
import space.uranos.net.packet.play.PlayProtocol
|
import space.uranos.net.packet.play.PlayProtocol
|
||||||
|
import space.uranos.net.packet.play.PlayerInfoPacket
|
||||||
import space.uranos.net.packet.status.StatusProtocol
|
import space.uranos.net.packet.status.StatusProtocol
|
||||||
import space.uranos.server.event.SessionInitializedEvent
|
import space.uranos.server.event.SessionInitializedEvent
|
||||||
import space.uranos.util.awaitSuspending
|
import space.uranos.util.awaitSuspending
|
||||||
|
@ -43,10 +44,19 @@ class UranosSession(private val channel: io.netty.channel.Channel, val server: U
|
||||||
val logger = Logger(identifier)
|
val logger = Logger(identifier)
|
||||||
|
|
||||||
override val coroutineContext: CoroutineContext = server.coroutineContext.supervisorChild(identifier)
|
override val coroutineContext: CoroutineContext = server.coroutineContext.supervisorChild(identifier)
|
||||||
val scope = CoroutineScope(coroutineContext)
|
private val scope = CoroutineScope(coroutineContext)
|
||||||
|
|
||||||
override var brand: String? = null
|
override var brand: String? = null
|
||||||
|
|
||||||
override var ping: Int = -1
|
override var ping: Int = -1
|
||||||
|
set(value) {
|
||||||
|
if (field == -1) {
|
||||||
|
val packet = PlayerInfoPacket(PlayerInfoPacket.Action.UpdateLatency(mapOf(player!!.uuid to value)))
|
||||||
|
scope.launch { server.players.forEach { it.session.send(packet) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
override var state: State = State.WaitingForHandshake
|
override var state: State = State.WaitingForHandshake
|
||||||
|
|
||||||
|
|
|
@ -7,18 +7,23 @@ package space.uranos.player
|
||||||
|
|
||||||
import space.uranos.Position
|
import space.uranos.Position
|
||||||
import space.uranos.chat.TextComponent
|
import space.uranos.chat.TextComponent
|
||||||
|
import space.uranos.data.DataStorage
|
||||||
|
import space.uranos.data.createDataStorageKey
|
||||||
import space.uranos.entity.PlayerEntity
|
import space.uranos.entity.PlayerEntity
|
||||||
import space.uranos.net.Session
|
import space.uranos.net.UranosSession
|
||||||
import space.uranos.net.packet.play.ChunkDataPacket
|
import space.uranos.net.packet.play.ChunkDataPacket
|
||||||
import space.uranos.net.packet.play.ChunkLightDataPacket
|
import space.uranos.net.packet.play.ChunkLightDataPacket
|
||||||
|
import space.uranos.net.packet.play.PlayerInfoPacket
|
||||||
|
import space.uranos.net.packet.play.SelectedHotbarSlotPacket
|
||||||
import space.uranos.util.clampArgument
|
import space.uranos.util.clampArgument
|
||||||
import space.uranos.world.Chunk
|
import space.uranos.world.Chunk
|
||||||
import space.uranos.world.VoxelLocation
|
import space.uranos.world.VoxelLocation
|
||||||
|
import space.uranos.world.World
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
class UranosPlayer(
|
class UranosPlayer(
|
||||||
override val session: Session,
|
override val session: UranosSession,
|
||||||
override val name: String,
|
override val name: String,
|
||||||
override val uuid: UUID,
|
override val uuid: UUID,
|
||||||
override var gameMode: GameMode,
|
override var gameMode: GameMode,
|
||||||
|
@ -34,25 +39,48 @@ class UranosPlayer(
|
||||||
override var compassTarget: VoxelLocation,
|
override var compassTarget: VoxelLocation,
|
||||||
selectedHotbarSlot: Int
|
selectedHotbarSlot: Int
|
||||||
) : Player {
|
) : Player {
|
||||||
override var selectedHotbarSlot = 0
|
val dataStorage = DataStorage(this)
|
||||||
|
|
||||||
|
object DataStorageKeys {
|
||||||
|
val selectedHotbarSlot = createDataStorageKey("selectedHotbarSlot") { player: UranosPlayer, value: Int ->
|
||||||
|
player.session.send(SelectedHotbarSlotPacket(value))
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val playerListName = createDataStorageKey("playerListName") { player: UranosPlayer, value: TextComponent? ->
|
||||||
|
player.session.server.players.forEach {
|
||||||
|
// TODO: Make packets like PlayerInfoPacket mergeable
|
||||||
|
it.session.send(PlayerInfoPacket(PlayerInfoPacket.Action.UpdateDisplayName(mapOf(player.uuid to value))))
|
||||||
|
}
|
||||||
|
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var selectedHotbarSlot
|
||||||
|
get() = dataStorage.get(DataStorageKeys.selectedHotbarSlot)
|
||||||
set(value) {
|
set(value) {
|
||||||
clampArgument("selectedHotbarSlot", 0..8, value)
|
clampArgument("selectedHotbarSlot", 0..8, value)
|
||||||
field = value
|
dataStorage.set(DataStorageKeys.selectedHotbarSlot, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override var playerListName
|
||||||
|
get() = dataStorage.get(DataStorageKeys.playerListName)
|
||||||
|
set(value) = dataStorage.set(DataStorageKeys.playerListName, value)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.selectedHotbarSlot = selectedHotbarSlot
|
this.selectedHotbarSlot = selectedHotbarSlot
|
||||||
}
|
}
|
||||||
|
|
||||||
override var playerListName: TextComponent? = null
|
|
||||||
override var currentlyViewedChunks = emptyList<Chunk>()
|
override var currentlyViewedChunks = emptyList<Chunk>()
|
||||||
|
|
||||||
init {
|
override val entity: PlayerEntity = PlayerEntity(position, this, headPitch)
|
||||||
|
|
||||||
|
suspend fun spawnInitially(world: World) {
|
||||||
|
entity.setWorld(world)
|
||||||
updateCurrentlyViewedChunks()
|
updateCurrentlyViewedChunks()
|
||||||
}
|
}
|
||||||
|
|
||||||
override val entity: PlayerEntity = PlayerEntity(position, this, headPitch)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets [currentlyViewedChunks] to all chunks in the view distance.
|
* Sets [currentlyViewedChunks] to all chunks in the view distance.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -7,3 +7,4 @@ minLogLevel: INFO
|
||||||
packetCompressionThreshold: 256
|
packetCompressionThreshold: 256
|
||||||
timeout: 30s
|
timeout: 30s
|
||||||
packetsBufferSize: 20
|
packetsBufferSize: 20
|
||||||
|
pingUpdateInterval: 10s
|
||||||
|
|
Reference in a new issue