NetworkedElement, Json RPC and ClientContextUpdate fixes
This commit is contained in:
parent
d37bad79c6
commit
66aa99acc2
@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
|
|||||||
|
|
||||||
kotlinVersion=1.9.10
|
kotlinVersion=1.9.10
|
||||||
kotlinCoroutinesVersion=1.8.0
|
kotlinCoroutinesVersion=1.8.0
|
||||||
kommonsVersion=2.9.20
|
kommonsVersion=2.9.21
|
||||||
|
|
||||||
ffiVersion=2.2.13
|
ffiVersion=2.2.13
|
||||||
lwjglVersion=3.3.0
|
lwjglVersion=3.3.0
|
||||||
|
@ -10,6 +10,7 @@ import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
|
|||||||
import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldsConfig
|
import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldsConfig
|
||||||
import ru.dbotthepony.kstarbound.defs.world.AsteroidWorldsConfig
|
import ru.dbotthepony.kstarbound.defs.world.AsteroidWorldsConfig
|
||||||
import ru.dbotthepony.kstarbound.defs.world.DungeonWorldsConfig
|
import ru.dbotthepony.kstarbound.defs.world.DungeonWorldsConfig
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.SkyGlobalConfig
|
||||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplateConfig
|
import ru.dbotthepony.kstarbound.defs.world.WorldTemplateConfig
|
||||||
import ru.dbotthepony.kstarbound.json.mapAdapter
|
import ru.dbotthepony.kstarbound.json.mapAdapter
|
||||||
import ru.dbotthepony.kstarbound.json.pairSetAdapter
|
import ru.dbotthepony.kstarbound.json.pairSetAdapter
|
||||||
@ -53,6 +54,9 @@ object GlobalDefaults {
|
|||||||
var bushDamage by Delegates.notNull<TileDamageConfig>()
|
var bushDamage by Delegates.notNull<TileDamageConfig>()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var sky by Delegates.notNull<SkyGlobalConfig>()
|
||||||
|
private set
|
||||||
|
|
||||||
private object EmptyTask : ForkJoinTask<Unit>() {
|
private object EmptyTask : ForkJoinTask<Unit>() {
|
||||||
private fun readResolve(): Any = EmptyTask
|
private fun readResolve(): Any = EmptyTask
|
||||||
override fun getRawResult() {
|
override fun getRawResult() {
|
||||||
@ -99,12 +103,14 @@ object GlobalDefaults {
|
|||||||
tasks.add(load("/terrestrial_worlds.config", ::terrestrialWorlds))
|
tasks.add(load("/terrestrial_worlds.config", ::terrestrialWorlds))
|
||||||
tasks.add(load("/asteroids_worlds.config", ::asteroidWorlds))
|
tasks.add(load("/asteroids_worlds.config", ::asteroidWorlds))
|
||||||
tasks.add(load("/world_template.config", ::worldTemplate))
|
tasks.add(load("/world_template.config", ::worldTemplate))
|
||||||
tasks.add(load("/dungeon_worlds.config", ::dungeonWorlds, Starbound.gson.mapAdapter()))
|
tasks.add(load("/sky.config", ::sky))
|
||||||
|
|
||||||
tasks.add(load("/plants/grassDamage.config", ::grassDamage))
|
tasks.add(load("/plants/grassDamage.config", ::grassDamage))
|
||||||
tasks.add(load("/plants/treeDamage.config", ::treeDamage))
|
tasks.add(load("/plants/treeDamage.config", ::treeDamage))
|
||||||
tasks.add(load("/plants/bushDamage.config", ::bushDamage))
|
tasks.add(load("/plants/bushDamage.config", ::bushDamage))
|
||||||
|
|
||||||
|
tasks.add(load("/dungeon_worlds.config", ::dungeonWorlds, Starbound.gson.mapAdapter()))
|
||||||
|
|
||||||
return tasks
|
return tasks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs.item.api
|
package ru.dbotthepony.kstarbound.defs.item.api
|
||||||
|
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
import ru.dbotthepony.kstarbound.Registry
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
import ru.dbotthepony.kstarbound.defs.AssetPath
|
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||||
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
||||||
@ -37,7 +38,7 @@ interface IItemDefinition : IThingWithDescription {
|
|||||||
/**
|
/**
|
||||||
* Иконка в инвентаре, относительный и абсолютный пути
|
* Иконка в инвентаре, относительный и абсолютный пути
|
||||||
*/
|
*/
|
||||||
val inventoryIcon: List<IInventoryIcon>?
|
val inventoryIcon: Either<out IInventoryIcon, out List<IInventoryIcon>>?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Теги предмета
|
* Теги предмета
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs.item.impl
|
package ru.dbotthepony.kstarbound.defs.item.impl
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
import ru.dbotthepony.kstarbound.Registry
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
import ru.dbotthepony.kstarbound.defs.AssetPath
|
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||||
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
||||||
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
||||||
import ru.dbotthepony.kstarbound.defs.item.IInventoryIcon
|
import ru.dbotthepony.kstarbound.defs.item.IInventoryIcon
|
||||||
|
import ru.dbotthepony.kstarbound.defs.item.InventoryIcon
|
||||||
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.ItemRarity
|
import ru.dbotthepony.kstarbound.defs.item.ItemRarity
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
@ -20,7 +22,7 @@ data class ItemDefinition(
|
|||||||
override val price: Long = 0,
|
override val price: Long = 0,
|
||||||
override val rarity: ItemRarity = ItemRarity.COMMON,
|
override val rarity: ItemRarity = ItemRarity.COMMON,
|
||||||
override val category: String? = null,
|
override val category: String? = null,
|
||||||
override val inventoryIcon: ImmutableList<IInventoryIcon>? = null,
|
override val inventoryIcon: Either<InventoryIcon, ImmutableList<IInventoryIcon>>? = null,
|
||||||
override val itemTags: ImmutableList<String> = ImmutableList.of(),
|
override val itemTags: ImmutableList<String> = ImmutableList.of(),
|
||||||
override val learnBlueprintsOnPickup: ImmutableList<Registry.Ref<IItemDefinition>> = ImmutableList.of(),
|
override val learnBlueprintsOnPickup: ImmutableList<Registry.Ref<IItemDefinition>> = ImmutableList.of(),
|
||||||
override val maxStack: Long = 9999L,
|
override val maxStack: Long = 9999L,
|
||||||
|
@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.defs.tile
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import ru.dbotthepony.kommons.math.RGBAColor
|
import ru.dbotthepony.kommons.math.RGBAColor
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||||
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
||||||
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
||||||
@ -16,8 +17,8 @@ data class TileDefinition(
|
|||||||
val materialName: String,
|
val materialName: String,
|
||||||
val particleColor: RGBAColor? = null,
|
val particleColor: RGBAColor? = null,
|
||||||
val itemDrop: String? = null,
|
val itemDrop: String? = null,
|
||||||
val footstepSound: ImmutableList<String> = ImmutableList.of(),
|
val footstepSound: Either<ImmutableList<String>, String> = Either.left(ImmutableList.of()),
|
||||||
val miningSounds: ImmutableList<String> = ImmutableList.of(),
|
val miningSounds: Either<ImmutableList<String>, String> = Either.left(ImmutableList.of()),
|
||||||
|
|
||||||
val blocksLiquidFlow: Boolean = true,
|
val blocksLiquidFlow: Boolean = true,
|
||||||
val soil: Boolean = false,
|
val soil: Boolean = false,
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs.world
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kommons.io.StreamCodec
|
||||||
import ru.dbotthepony.kommons.math.RGBAColor
|
import ru.dbotthepony.kommons.math.RGBAColor
|
||||||
import ru.dbotthepony.kommons.util.Either
|
import ru.dbotthepony.kommons.util.Either
|
||||||
import ru.dbotthepony.kommons.vector.Vector2d
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||||
import ru.dbotthepony.kstarbound.io.readColor
|
import ru.dbotthepony.kstarbound.io.readColor
|
||||||
import ru.dbotthepony.kstarbound.io.writeColor
|
import ru.dbotthepony.kstarbound.io.writeColor
|
||||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
@ -20,14 +23,22 @@ enum class SkyType {
|
|||||||
ATMOSPHERELESS,
|
ATMOSPHERELESS,
|
||||||
ORBITAL,
|
ORBITAL,
|
||||||
WARP,
|
WARP,
|
||||||
SPACE
|
SPACE;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val CODEC = StreamCodec.Enum(SkyType::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class FlyingType {
|
enum class FlyingType {
|
||||||
NONE,
|
NONE,
|
||||||
DISEMBARKING,
|
DISEMBARKING,
|
||||||
WARP,
|
WARP,
|
||||||
ARRIVING
|
ARRIVING;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val CODEC = StreamCodec.Enum(FlyingType::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class WarpPhase(val stupidassbitch: Int) {
|
enum class WarpPhase(val stupidassbitch: Int) {
|
||||||
@ -119,16 +130,17 @@ data class SkyColoring(
|
|||||||
|
|
||||||
data class SkyWorldHorizon(val center: Vector2d, val scale: Double, val rotation: Double, val layers: List<Pair<String, String>>)
|
data class SkyWorldHorizon(val center: Vector2d, val scale: Double, val rotation: Double, val layers: List<Pair<String, String>>)
|
||||||
|
|
||||||
class SkyParameters() {
|
@JsonFactory
|
||||||
var skyType = SkyType.BARREN
|
data class SkyParameters(
|
||||||
var seed = 0L
|
var skyType: SkyType = SkyType.BARREN,
|
||||||
var dayLength: Double? = null
|
var seed: Long = 0L,
|
||||||
var horizonClouds = false
|
var dayLength: Double? = null,
|
||||||
var skyColoring: Either<SkyColoring, RGBAColor> = Either.right(RGBAColor.BLACK)
|
var horizonClouds: Boolean = false,
|
||||||
var spaceLevel: Double? = null
|
var skyColoring: Either<SkyColoring, RGBAColor> = Either.left(SkyColoring()),
|
||||||
var surfaceLevel: Double? = null
|
var spaceLevel: Double? = null,
|
||||||
var nearbyPlanet: Pair<List<Pair<String, Double>>, Vector2d>? = null
|
var surfaceLevel: Double? = null,
|
||||||
|
var nearbyPlanet: Pair<List<Pair<String, Double>>, Vector2d>? = null,
|
||||||
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
suspend fun create(coordinate: UniversePos, universe: Universe): SkyParameters {
|
suspend fun create(coordinate: UniversePos, universe: Universe): SkyParameters {
|
||||||
if (coordinate.isSystem)
|
if (coordinate.isSystem)
|
||||||
@ -163,3 +175,13 @@ class SkyParameters() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class SkyGlobalConfig(
|
||||||
|
val stars: Stars,
|
||||||
|
) {
|
||||||
|
data class Stars(
|
||||||
|
val frames: Int,
|
||||||
|
val list: ImmutableList<AssetPath>,
|
||||||
|
val hyperlist: ImmutableList<AssetPath>,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -215,13 +215,13 @@ private fun materialMiningSound(context: ExecutionContext, arguments: ArgumentIt
|
|||||||
val tile = lookup(Registries.tiles, arguments.nextAny())
|
val tile = lookup(Registries.tiles, arguments.nextAny())
|
||||||
val mod = lookup(Registries.tiles, arguments.nextOptionalAny(null))
|
val mod = lookup(Registries.tiles, arguments.nextOptionalAny(null))
|
||||||
|
|
||||||
if (mod != null && mod.value.miningSounds.isNotEmpty()) {
|
if (mod != null && mod.value.miningSounds.map({ it.isNotEmpty() }, { true })) {
|
||||||
context.returnBuffer.setTo(mod.value.miningSounds.random())
|
context.returnBuffer.setTo(mod.value.miningSounds.map({ it.random() }, { it }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tile != null && tile.value.miningSounds.isNotEmpty()) {
|
if (tile != null && tile.value.miningSounds.map({ it.isNotEmpty() }, { true })) {
|
||||||
context.returnBuffer.setTo(tile.value.miningSounds.random())
|
context.returnBuffer.setTo(tile.value.miningSounds.map({ it.random() }, { it }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,13 +233,13 @@ private fun materialFootstepSound(context: ExecutionContext, arguments: Argument
|
|||||||
val tile = lookup(Registries.tiles, arguments.nextAny())
|
val tile = lookup(Registries.tiles, arguments.nextAny())
|
||||||
val mod = lookup(Registries.tiles, arguments.nextOptionalAny(null))
|
val mod = lookup(Registries.tiles, arguments.nextOptionalAny(null))
|
||||||
|
|
||||||
if (mod != null && mod.value.footstepSound.isNotEmpty()) {
|
if (mod != null && mod.value.footstepSound.map({ it.isNotEmpty() }, { true })) {
|
||||||
context.returnBuffer.setTo(mod.value.footstepSound.random())
|
context.returnBuffer.setTo(mod.value.footstepSound.map({ it.random() }, { it }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tile != null && tile.value.footstepSound.isNotEmpty()) {
|
if (tile != null && tile.value.footstepSound.map({ it.isNotEmpty() }, { true })) {
|
||||||
context.returnBuffer.setTo(tile.value.footstepSound.random())
|
context.returnBuffer.setTo(tile.value.footstepSound.map({ it.random() }, { it }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.math
|
||||||
|
|
||||||
|
import ru.dbotthepony.kommons.math.linearInterpolation
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
fun interface Interpolator {
|
||||||
|
fun interpolate(t: Double, a: Double, b: Double): Double
|
||||||
|
|
||||||
|
object Sin : Interpolator {
|
||||||
|
override fun interpolate(t: Double, a: Double, b: Double): Double {
|
||||||
|
return linearInterpolation((sin(t * PI - PI / 2.0) + 1.0) / 2.0, a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Linear : Interpolator {
|
||||||
|
override fun interpolate(t: Double, a: Double, b: Double): Double {
|
||||||
|
// custom to allow extrapolation
|
||||||
|
return a + (b - a) * t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object NearestMiddle : Interpolator {
|
||||||
|
override fun interpolate(t: Double, a: Double, b: Double): Double {
|
||||||
|
if (t >= 0.5)
|
||||||
|
return b
|
||||||
|
else
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,16 +12,6 @@ data class PeriodicFunction(
|
|||||||
val periodVariance: Double = 0.0,
|
val periodVariance: Double = 0.0,
|
||||||
val magnitudeVariance: Double = 0.0
|
val magnitudeVariance: Double = 0.0
|
||||||
) {
|
) {
|
||||||
fun interface Interpolator {
|
|
||||||
fun interpolate(t: Double, a: Double, b: Double): Double
|
|
||||||
}
|
|
||||||
|
|
||||||
object Sin : Interpolator {
|
|
||||||
override fun interpolate(t: Double, a: Double, b: Double): Double {
|
|
||||||
return linearInterpolation((sin(t * PI - PI / 2.0) + 1.0) / 2.0, a, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var timer = 0.0
|
private var timer = 0.0
|
||||||
private var timerMax = 1.0
|
private var timerMax = 1.0
|
||||||
|
|
||||||
@ -60,6 +50,6 @@ data class PeriodicFunction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun sinValue(): Double {
|
fun sinValue(): Double {
|
||||||
return value(Sin)
|
return value(Interpolator.Sin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,8 +75,16 @@ enum class ConnectionType {
|
|||||||
MEMORY;
|
MEMORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
fun interface IPacketReader<T : IPacket> {
|
fun interface IPacketReaderDetailed<T : IPacket> {
|
||||||
|
fun read(stream: DataInputStream, isLegacy: Boolean, side: ConnectionSide): T
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interface IPacketReader<T : IPacket> : IPacketReaderDetailed<T> {
|
||||||
fun read(stream: DataInputStream, isLegacy: Boolean): T
|
fun read(stream: DataInputStream, isLegacy: Boolean): T
|
||||||
|
|
||||||
|
override fun read(stream: DataInputStream, isLegacy: Boolean, side: ConnectionSide): T {
|
||||||
|
return read(stream, isLegacy)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IPacket {
|
interface IPacket {
|
||||||
|
@ -73,6 +73,8 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType) :
|
|||||||
|
|
||||||
isLegacy = false
|
isLegacy = false
|
||||||
isConnected = true
|
isConnected = true
|
||||||
|
|
||||||
|
inGame()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun onChannelClosed() {
|
protected open fun onChannelClosed() {
|
||||||
|
@ -25,13 +25,15 @@ import kotlin.concurrent.withLock
|
|||||||
class JsonRPC {
|
class JsonRPC {
|
||||||
enum class Command {
|
enum class Command {
|
||||||
REQUEST, RESPONSE, FAIL;
|
REQUEST, RESPONSE, FAIL;
|
||||||
|
|
||||||
|
val jsonName = name.lowercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Entry(val command: Command, val id: Int, val handler: KOptional<String>, val arguments: KOptional<JsonElement>) {
|
data class Entry(val command: Command, val id: Int, val handler: KOptional<String>, val arguments: KOptional<JsonElement>) {
|
||||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
if (isLegacy) {
|
if (isLegacy) {
|
||||||
stream.writeJsonElement(JsonObject().also {
|
stream.writeJsonElement(JsonObject().also {
|
||||||
it["command"] = command.name.lowercase()
|
it["command"] = command.jsonName
|
||||||
it["id"] = id
|
it["id"] = id
|
||||||
handler.ifPresent { v -> it["handler"] = v }
|
handler.ifPresent { v -> it["handler"] = v }
|
||||||
arguments.ifPresent { v -> if (command == Command.RESPONSE) it["result"] = v else it["arguments"] = v }
|
arguments.ifPresent { v -> if (command == Command.RESPONSE) it["result"] = v else it["arguments"] = v }
|
||||||
@ -52,11 +54,11 @@ class JsonRPC {
|
|||||||
fun legacy(stream: DataInputStream): Entry {
|
fun legacy(stream: DataInputStream): Entry {
|
||||||
val data = stream.readJsonElement()
|
val data = stream.readJsonElement()
|
||||||
check(data is JsonObject) { "Expected JsonObject, got ${data::class}" }
|
check(data is JsonObject) { "Expected JsonObject, got ${data::class}" }
|
||||||
val command = data["command"]?.asString?.uppercase() ?: throw JsonSyntaxException("Missing 'command' in RPC data")
|
val command = data["command"]?.asString?.lowercase() ?: throw JsonSyntaxException("Missing 'command' in RPC data")
|
||||||
val id = data["id"]?.asInt ?: throw JsonSyntaxException("Missing 'id' in RPC data")
|
val id = data["id"]?.asInt ?: throw JsonSyntaxException("Missing 'id' in RPC data")
|
||||||
val handler = KOptional.ofNullable(data["handler"]?.asString)
|
val handler = KOptional.ofNullable(data["handler"]?.asString)
|
||||||
val arguments = KOptional.ofNullable(data["arguments"])
|
val arguments = KOptional.ofNullable(data["arguments"])
|
||||||
return Entry(Command.entries.firstOrNull { it.name == command } ?: throw JsonSyntaxException("Invalid 'command': $command"), id, handler, arguments)
|
return Entry(Command.entries.firstOrNull { it.jsonName == command } ?: throw JsonSyntaxException("Invalid 'command': $command"), id, handler, arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun read(stream: DataInputStream, isLegacy: Boolean): Entry {
|
fun read(stream: DataInputStream, isLegacy: Boolean): Entry {
|
||||||
@ -111,8 +113,13 @@ class JsonRPC {
|
|||||||
try {
|
try {
|
||||||
when (entry.command) {
|
when (entry.command) {
|
||||||
Command.REQUEST -> {
|
Command.REQUEST -> {
|
||||||
val handler = handlers[entry.handler.value] ?: throw IllegalArgumentException("No such handler ${entry.handler.value}")
|
val handler = handlers[entry.handler.value]
|
||||||
pendingWrite.add(Entry(Command.RESPONSE, entry.id, KOptional(), KOptional(handler(entry.arguments.value))))
|
|
||||||
|
if (handler == null) {
|
||||||
|
pendingWrite.add(Entry(Command.FAIL, entry.id, KOptional(), KOptional()))
|
||||||
|
} else {
|
||||||
|
pendingWrite.add(Entry(Command.RESPONSE, entry.id, KOptional(), KOptional(handler(entry.arguments.value))))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Command.RESPONSE -> {
|
Command.RESPONSE -> {
|
||||||
|
@ -50,12 +50,12 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
private val missingNames = Int2ObjectArrayMap<String>()
|
private val missingNames = Int2ObjectArrayMap<String>()
|
||||||
private val clazz2Type = Reference2ObjectOpenHashMap<KClass<*>, Type<*>>()
|
private val clazz2Type = Reference2ObjectOpenHashMap<KClass<*>, Type<*>>()
|
||||||
|
|
||||||
private data class Type<T : IPacket>(val id: Int, val type: KClass<T>, val factory: IPacketReader<T>, val direction: PacketDirection)
|
private data class Type<T : IPacket>(val id: Int, val type: KClass<T>, val factory: IPacketReaderDetailed<T>, val direction: PacketDirection)
|
||||||
|
|
||||||
val size: Int
|
val size: Int
|
||||||
get() = packets.size
|
get() = packets.size
|
||||||
|
|
||||||
private fun <T : IPacket> add(type: KClass<T>, reader: IPacketReader<T>, direction: PacketDirection = PacketDirection.get(type)): PacketRegistry {
|
private fun <T : IPacket> add(type: KClass<T>, reader: IPacketReaderDetailed<T>, direction: PacketDirection = PacketDirection.get(type)): PacketRegistry {
|
||||||
if (packets.size >= 255)
|
if (packets.size >= 255)
|
||||||
throw IndexOutOfBoundsException("Unable to add any more packet types! 255 is the max")
|
throw IndexOutOfBoundsException("Unable to add any more packet types! 255 is the max")
|
||||||
|
|
||||||
@ -72,8 +72,12 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
return add(T::class, reader, direction)
|
return add(T::class, reader, direction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T : IPacket> add(reader: IPacketReaderDetailed<T>, direction: PacketDirection = PacketDirection.get(T::class)): PacketRegistry {
|
||||||
|
return add(T::class, reader, direction)
|
||||||
|
}
|
||||||
|
|
||||||
private inline fun <reified T : IPacket> add(value: T, direction: PacketDirection = PacketDirection.get(T::class)): PacketRegistry {
|
private inline fun <reified T : IPacket> add(value: T, direction: PacketDirection = PacketDirection.get(T::class)): PacketRegistry {
|
||||||
return add(T::class, { _, _ -> value }, direction)
|
return add(T::class, { _, _, _ -> value }, direction)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun skip(amount: Int = 1) {
|
private fun skip(amount: Int = 1) {
|
||||||
@ -154,12 +158,16 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
|
|
||||||
// legacy protocol allows to stitch multiple packets of same type together without
|
// legacy protocol allows to stitch multiple packets of same type together without
|
||||||
// separate headers for each
|
// separate headers for each
|
||||||
while (stream.available() > 0) {
|
// Due to nature of netty pipeline, we can't do the same on native protocol;
|
||||||
|
// so don't do that when on native protocol
|
||||||
|
while (stream.available() > 0 || !isLegacy) {
|
||||||
try {
|
try {
|
||||||
ctx.fireChannelRead(readingType!!.factory.read(DataInputStream(stream), isLegacy))
|
ctx.fireChannelRead(readingType!!.factory.read(DataInputStream(stream), isLegacy, side))
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
LOGGER.error("Error while reading incoming packet from network (type ${readingType!!.id}; ${readingType!!.type})", err)
|
LOGGER.error("Error while reading incoming packet from network (type ${readingType!!.id}; ${readingType!!.type})", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isLegacy) break
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.close()
|
stream.close()
|
||||||
@ -219,8 +227,8 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
val stream = FastByteArrayOutputStream()
|
val stream = FastByteArrayOutputStream()
|
||||||
(msg as IPacket).write(DataOutputStream(stream), isLegacy)
|
(msg as IPacket).write(DataOutputStream(stream), isLegacy)
|
||||||
|
|
||||||
if (isLegacy)
|
if (isLegacy && stream.length == 0)
|
||||||
check(stream.length > 0) { "Packet $msg didn't write any data to network, this is not allowed by legacy protocol" }
|
throw IllegalStateException("Packet $msg didn't write any data to network, this is not allowed by legacy protocol")
|
||||||
|
|
||||||
if (stream.length >= 512) {
|
if (stream.length >= 512) {
|
||||||
// compress
|
// compress
|
||||||
@ -228,6 +236,7 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
val buffers = ByteArrayList(1024)
|
val buffers = ByteArrayList(1024)
|
||||||
val buffer = ByteArray(1024)
|
val buffer = ByteArray(1024)
|
||||||
deflater.setInput(stream.array, 0, stream.length)
|
deflater.setInput(stream.array, 0, stream.length)
|
||||||
|
deflater.finish()
|
||||||
|
|
||||||
while (!deflater.needsInput()) {
|
while (!deflater.needsInput()) {
|
||||||
val deflated = deflater.deflate(buffer)
|
val deflated = deflater.deflate(buffer)
|
||||||
@ -369,7 +378,7 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
LEGACY.skip("SetDungeonBreathable")
|
LEGACY.skip("SetDungeonBreathable")
|
||||||
LEGACY.skip("SetPlayerStart")
|
LEGACY.skip("SetPlayerStart")
|
||||||
LEGACY.skip("FindUniqueEntityResponse")
|
LEGACY.skip("FindUniqueEntityResponse")
|
||||||
LEGACY.add(PongPacket)
|
LEGACY.add(PongPacket::read)
|
||||||
|
|
||||||
// Packets sent world client -> world server
|
// Packets sent world client -> world server
|
||||||
LEGACY.skip("ModifyTileList")
|
LEGACY.skip("ModifyTileList")
|
||||||
@ -382,7 +391,7 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
LEGACY.skip("WorldClientStateUpdate")
|
LEGACY.skip("WorldClientStateUpdate")
|
||||||
LEGACY.skip("FindUniqueEntity")
|
LEGACY.skip("FindUniqueEntity")
|
||||||
LEGACY.skip("WorldStartAcknowledge")
|
LEGACY.skip("WorldStartAcknowledge")
|
||||||
LEGACY.add(PingPacket)
|
LEGACY.add(PingPacket::read)
|
||||||
|
|
||||||
// Packets sent bidirectionally between world client and world server
|
// Packets sent bidirectionally between world client and world server
|
||||||
LEGACY.skip("EntityCreate")
|
LEGACY.skip("EntityCreate")
|
||||||
|
@ -17,6 +17,8 @@ import ru.dbotthepony.kommons.io.writeMap
|
|||||||
import ru.dbotthepony.kommons.io.writeVarInt
|
import ru.dbotthepony.kommons.io.writeVarInt
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kstarbound.client.ClientConnection
|
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||||
|
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||||
|
import ru.dbotthepony.kstarbound.network.ConnectionSide
|
||||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||||
import ru.dbotthepony.kstarbound.network.IServerPacket
|
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||||
import ru.dbotthepony.kstarbound.network.JsonRPC
|
import ru.dbotthepony.kstarbound.network.JsonRPC
|
||||||
@ -34,23 +36,31 @@ class ClientContextUpdatePacket(
|
|||||||
) : IClientPacket, IServerPacket {
|
) : IClientPacket, IServerPacket {
|
||||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
if (isLegacy) {
|
if (isLegacy) {
|
||||||
// this is stupid
|
if (!shipChunks.isPresent && !networkedVars.isPresent) {
|
||||||
run {
|
// client to server
|
||||||
val wrap = FastByteArrayOutputStream()
|
stream.writeCollection(rpcEntries) { it.write(this, true) }
|
||||||
DataOutputStream(wrap).writeCollection(rpcEntries) { it.write(this, true) }
|
} else {
|
||||||
stream.writeVarInt(wrap.length)
|
// server to client
|
||||||
stream.write(wrap.array, 0, wrap.length)
|
// this is so dumb
|
||||||
}
|
val wrap2 = FastByteArrayOutputStream()
|
||||||
|
|
||||||
shipChunks.ifPresent {
|
run {
|
||||||
val wrap = FastByteArrayOutputStream()
|
val wrap = FastByteArrayOutputStream()
|
||||||
DataOutputStream(wrap).writeMap(it, { it.write(this) }, { writeKOptional(it) { writeByteArray(it) } })
|
DataOutputStream(wrap).writeCollection(rpcEntries) { it.write(this, true) }
|
||||||
stream.writeByteArray(wrap.array, 0, wrap.length)
|
wrap2.writeByteArray(wrap.array, 0, wrap.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
networkedVars.ifPresent {
|
shipChunks.ifPresent {
|
||||||
stream.writeVarInt(it.size)
|
val wrap = FastByteArrayOutputStream()
|
||||||
stream.write(it.elements(), 0, it.size)
|
DataOutputStream(wrap).writeMap(it, { it.write(this) }, { writeKOptional(it) { writeByteArray(it) } })
|
||||||
|
wrap2.writeByteArray(wrap.array, 0, wrap.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
networkedVars.ifPresent {
|
||||||
|
wrap2.writeByteArray(it.elements(), 0, it.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.writeByteArray(wrap2.array, 0, wrap2.length)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
stream.writeCollection(rpcEntries) { it.write(this, false) }
|
stream.writeCollection(rpcEntries) { it.write(this, false) }
|
||||||
@ -75,16 +85,25 @@ class ClientContextUpdatePacket(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun read(stream: DataInputStream, isLegacy: Boolean): ClientContextUpdatePacket {
|
fun read(stream: DataInputStream, isLegacy: Boolean, side: ConnectionSide): ClientContextUpdatePacket {
|
||||||
if (isLegacy) {
|
if (isLegacy) {
|
||||||
// beyond stupid
|
// beyond stupid
|
||||||
val rpc = stream.readByteArray()
|
if (side == ConnectionSide.SERVER) {
|
||||||
|
return ClientContextUpdatePacket(
|
||||||
|
DataInputStream(FastByteArrayInputStream(stream.readByteArray())).readCollection { JsonRPC.Entry.legacy(this) },
|
||||||
|
KOptional(),
|
||||||
|
KOptional()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val wrap = DataInputStream(FastByteArrayInputStream(stream.readByteArray()))
|
||||||
|
val rpc = wrap.readByteArray()
|
||||||
|
|
||||||
return ClientContextUpdatePacket(
|
return ClientContextUpdatePacket(
|
||||||
DataInputStream(FastByteArrayInputStream(rpc)).readCollection { JsonRPC.Entry.legacy(this) },
|
DataInputStream(FastByteArrayInputStream(rpc)).readCollection { JsonRPC.Entry.legacy(this) },
|
||||||
if (stream.available() > 0) KOptional(stream.readMap({ readByteKey() }, { readKOptional { readByteArray() } })) else KOptional(),
|
if (wrap.available() > 0) KOptional(wrap.readMap({ readByteKey() }, { readKOptional { readByteArray() } })) else KOptional(),
|
||||||
if (stream.available() > 0) KOptional(ByteArrayList.wrap(stream.readByteArray())) else KOptional(),
|
if (wrap.available() > 0) KOptional(ByteArrayList.wrap(wrap.readByteArray())) else KOptional(),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return ClientContextUpdatePacket(
|
return ClientContextUpdatePacket(
|
||||||
stream.readCollection { JsonRPC.Entry.native(this) },
|
stream.readCollection { JsonRPC.Entry.native(this) },
|
||||||
|
@ -4,6 +4,7 @@ import ru.dbotthepony.kstarbound.client.ClientConnection
|
|||||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||||
import ru.dbotthepony.kstarbound.network.IServerPacket
|
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||||
|
import java.io.DataInputStream
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
object PongPacket : IClientPacket {
|
object PongPacket : IClientPacket {
|
||||||
@ -14,6 +15,11 @@ object PongPacket : IClientPacket {
|
|||||||
override fun play(connection: ClientConnection) {
|
override fun play(connection: ClientConnection) {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun read(stream: DataInputStream, isLegacy: Boolean): PongPacket {
|
||||||
|
if (isLegacy) stream.readBoolean()
|
||||||
|
return PongPacket
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object PingPacket : IServerPacket {
|
object PingPacket : IServerPacket {
|
||||||
@ -22,6 +28,11 @@ object PingPacket : IServerPacket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun play(connection: ServerConnection) {
|
override fun play(connection: ServerConnection) {
|
||||||
connection.send(PongPacket)
|
connection.sendAndFlush(PongPacket)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun read(stream: DataInputStream, isLegacy: Boolean): PingPacket {
|
||||||
|
if (isLegacy) stream.readBoolean()
|
||||||
|
return PingPacket
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,10 +24,16 @@ import java.io.DataInputStream
|
|||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
class WorldStartPacket(
|
class WorldStartPacket(
|
||||||
val templateData: JsonElement, val skyData: ByteArray, val weatherData: ByteArray,
|
val templateData: JsonElement,
|
||||||
val playerStart: Vector2d, val playerRespawn: Vector2d, val respawnInWorld: Boolean,
|
val skyData: ByteArray,
|
||||||
val dungeonGravity: Map<Int, Vector2d>, val dungeonBreathable: Map<Int, Boolean>,
|
val weatherData: ByteArray,
|
||||||
val protectedDungeonIDs: Set<Int>, val worldProperties: JsonElement, val connectionID: Int,
|
val playerStart: Vector2d,
|
||||||
|
val playerRespawn: Vector2d,
|
||||||
|
val respawnInWorld: Boolean,
|
||||||
|
val worldProperties: JsonElement,
|
||||||
|
val dungeonGravity: Map<Int, Vector2d>,
|
||||||
|
val dungeonBreathable: Map<Int, Boolean>,
|
||||||
|
val protectedDungeonIDs: Set<Int>, val connectionID: Int,
|
||||||
val localInterpolationMode: Boolean,
|
val localInterpolationMode: Boolean,
|
||||||
) : IClientPacket {
|
) : IClientPacket {
|
||||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||||
@ -37,10 +43,10 @@ class WorldStartPacket(
|
|||||||
if (isLegacy) stream.readVector2f().toDoubleVector() else stream.readVector2d(),
|
if (isLegacy) stream.readVector2f().toDoubleVector() else stream.readVector2d(),
|
||||||
if (isLegacy) stream.readVector2f().toDoubleVector() else stream.readVector2d(),
|
if (isLegacy) stream.readVector2f().toDoubleVector() else stream.readVector2d(),
|
||||||
stream.readBoolean(),
|
stream.readBoolean(),
|
||||||
|
stream.readJsonElement(),
|
||||||
if (isLegacy) stream.readMap({ readUnsignedShort() }, { Vector2d(0.0, readFloat().toDouble()) }, ::Int2ObjectOpenHashMap) else stream.readMap({ readInt() }, { readVector2d() }, { Int2ObjectAVLTreeMap() }),
|
if (isLegacy) stream.readMap({ readUnsignedShort() }, { Vector2d(0.0, readFloat().toDouble()) }, ::Int2ObjectOpenHashMap) else stream.readMap({ readInt() }, { readVector2d() }, { Int2ObjectAVLTreeMap() }),
|
||||||
if (isLegacy) stream.readMap({ readUnsignedShort() }, { readBoolean() }, ::Int2ObjectOpenHashMap) else stream.readMap({ readInt() }, { readBoolean() }, { Int2BooleanAVLTreeMap() }),
|
if (isLegacy) stream.readMap({ readUnsignedShort() }, { readBoolean() }, ::Int2ObjectOpenHashMap) else stream.readMap({ readInt() }, { readBoolean() }, { Int2BooleanAVLTreeMap() }),
|
||||||
if (isLegacy) stream.readCollection({ readUnsignedShort() }, { IntAVLTreeSet() }) else stream.readCollection({ readInt() }, { IntAVLTreeSet() }),
|
if (isLegacy) stream.readCollection({ readUnsignedShort() }, { IntAVLTreeSet() }) else stream.readCollection({ readInt() }, { IntAVLTreeSet() }),
|
||||||
stream.readJsonElement(),
|
|
||||||
stream.readUnsignedShort(),
|
stream.readUnsignedShort(),
|
||||||
stream.readBoolean()
|
stream.readBoolean()
|
||||||
)
|
)
|
||||||
@ -54,6 +60,7 @@ class WorldStartPacket(
|
|||||||
stream.writeStruct2f(playerStart.toFloatVector())
|
stream.writeStruct2f(playerStart.toFloatVector())
|
||||||
stream.writeStruct2f(playerRespawn.toFloatVector())
|
stream.writeStruct2f(playerRespawn.toFloatVector())
|
||||||
stream.writeBoolean(respawnInWorld)
|
stream.writeBoolean(respawnInWorld)
|
||||||
|
stream.writeJsonElement(worldProperties)
|
||||||
stream.writeMap(dungeonGravity, { writeShort(it) }, { writeFloat(it.y.toFloat()) })
|
stream.writeMap(dungeonGravity, { writeShort(it) }, { writeFloat(it.y.toFloat()) })
|
||||||
stream.writeMap(dungeonBreathable, { writeShort(it) }, { writeBoolean(it) })
|
stream.writeMap(dungeonBreathable, { writeShort(it) }, { writeBoolean(it) })
|
||||||
stream.writeCollection(protectedDungeonIDs) { writeShort(it) }
|
stream.writeCollection(protectedDungeonIDs) { writeShort(it) }
|
||||||
@ -61,12 +68,12 @@ class WorldStartPacket(
|
|||||||
stream.writeStruct2d(playerStart)
|
stream.writeStruct2d(playerStart)
|
||||||
stream.writeStruct2d(playerRespawn)
|
stream.writeStruct2d(playerRespawn)
|
||||||
stream.writeBoolean(respawnInWorld)
|
stream.writeBoolean(respawnInWorld)
|
||||||
|
stream.writeJsonElement(worldProperties)
|
||||||
stream.writeMap(dungeonGravity, { writeInt(it) }, { writeStruct2d(it) })
|
stream.writeMap(dungeonGravity, { writeInt(it) }, { writeStruct2d(it) })
|
||||||
stream.writeMap(dungeonBreathable, { writeInt(it) }, { writeBoolean(it) })
|
stream.writeMap(dungeonBreathable, { writeInt(it) }, { writeBoolean(it) })
|
||||||
stream.writeCollection(protectedDungeonIDs) { writeInt(it) }
|
stream.writeCollection(protectedDungeonIDs) { writeInt(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.writeJsonElement(worldProperties)
|
|
||||||
stream.writeShort(connectionID)
|
stream.writeShort(connectionID)
|
||||||
stream.writeBoolean(localInterpolationMode)
|
stream.writeBoolean(localInterpolationMode)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.syncher
|
||||||
|
|
||||||
|
import ru.dbotthepony.kommons.io.StreamCodec
|
||||||
|
import ru.dbotthepony.kommons.util.Listenable
|
||||||
|
import ru.dbotthepony.kommons.util.ListenableDelegate
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
import java.util.*
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
open class BasicNetworkedElement<TYPE, LEGACY>(private var value: TYPE, val codec: StreamCodec<TYPE>, val legacyCodec: StreamCodec<LEGACY>, val toLegacy: (TYPE) -> LEGACY, val fromLegacy: (LEGACY) -> TYPE) : NetworkedElement(), ListenableDelegate<TYPE> {
|
||||||
|
protected val valueListeners = Listenable.Impl<TYPE>()
|
||||||
|
protected val queue = LinkedList<Pair<Double, TYPE>>()
|
||||||
|
protected var isInterpolating = false
|
||||||
|
protected var currentTime = 0.0
|
||||||
|
|
||||||
|
override fun accept(t: TYPE) {
|
||||||
|
if (t != value) {
|
||||||
|
value = t
|
||||||
|
queue.clear()
|
||||||
|
bumpVersion()
|
||||||
|
valueListeners.accept(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addListener(listener: Consumer<TYPE>): Listenable.L {
|
||||||
|
return valueListeners.addListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(): TYPE {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {
|
||||||
|
val old = value
|
||||||
|
value = if (isLegacy) fromLegacy(legacyCodec.read(data)) else codec.read(data)
|
||||||
|
queue.clear()
|
||||||
|
bumpVersion()
|
||||||
|
|
||||||
|
if (value != old) {
|
||||||
|
valueListeners.accept(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
if (isLegacy) {
|
||||||
|
if (queue.isNotEmpty()) {
|
||||||
|
legacyCodec.write(data, toLegacy(queue.last.second))
|
||||||
|
} else {
|
||||||
|
legacyCodec.write(data, toLegacy(value))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (queue.isNotEmpty()) {
|
||||||
|
codec.write(data, queue.last.second)
|
||||||
|
} else {
|
||||||
|
codec.write(data, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean) {
|
||||||
|
val read = if (isLegacy) fromLegacy(legacyCodec.read(data)) else codec.read(data)
|
||||||
|
bumpVersion()
|
||||||
|
|
||||||
|
if (isInterpolating) {
|
||||||
|
// Only append an incoming delta to our pending value list if the incoming
|
||||||
|
// step is forward in time of every other pending value. In any other
|
||||||
|
// case, this is an error or the step tracking is wildly off, so just clear
|
||||||
|
// any other incoming values.
|
||||||
|
val actualDelay = interpolationDelay + currentTime
|
||||||
|
|
||||||
|
if (interpolationDelay > 0.0 && (queue.isEmpty() || queue.last.first <= actualDelay)) {
|
||||||
|
queue.add(actualDelay to read)
|
||||||
|
} else {
|
||||||
|
value = read
|
||||||
|
queue.clear()
|
||||||
|
valueListeners.accept(read)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = read
|
||||||
|
valueListeners.accept(read)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeDelta(data: DataOutputStream, remoteVersion: Long, isLegacy: Boolean) {
|
||||||
|
writeInitial(data, isLegacy)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun readBlankDelta(interpolationDelay: Double) {
|
||||||
|
// TODO: original engine doesn't override this, is this intentional?
|
||||||
|
// tickInterpolation(interpolationDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enableInterpolation(extrapolation: Double) {
|
||||||
|
if (!isInterpolating) {
|
||||||
|
isInterpolating = true
|
||||||
|
queue.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun disableInterpolation() {
|
||||||
|
if (isInterpolating) {
|
||||||
|
isInterpolating = false
|
||||||
|
|
||||||
|
if (queue.isNotEmpty()) {
|
||||||
|
value = queue.last.second
|
||||||
|
valueListeners.accept(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun tickInterpolation(delta: Double) {
|
||||||
|
require(delta >= 0.0) { "Negative interpolation delta: $delta" }
|
||||||
|
currentTime += delta
|
||||||
|
|
||||||
|
while (queue.isNotEmpty() && queue.first.first <= currentTime) {
|
||||||
|
value = queue.removeFirst().second
|
||||||
|
valueListeners.accept(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.syncher
|
||||||
|
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
class EventCounterElement : BasicNetworkedElement<Long, Long>(0L, UnsignedVarLongCodec, UnsignedVarLongCodec, { it }, { it }) {
|
||||||
|
var pulled = 0L
|
||||||
|
private set
|
||||||
|
|
||||||
|
var ignoreOccurrencesOnLoad = false
|
||||||
|
|
||||||
|
fun trigger() {
|
||||||
|
accept(get() + 1L)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pullOccurred(): Boolean {
|
||||||
|
return pullOccurrences() != 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pullOccurrences(): Long {
|
||||||
|
val value = get()
|
||||||
|
val delta = value - pulled
|
||||||
|
require(delta >= 0L) { "Event counter turned back in time" }
|
||||||
|
pulled = value
|
||||||
|
return delta
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ignoreOccurrences() {
|
||||||
|
pulled = get()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {
|
||||||
|
super.readInitial(data, isLegacy)
|
||||||
|
|
||||||
|
if (ignoreOccurrencesOnLoad)
|
||||||
|
pulled = get()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
valueListeners.addListener(Consumer {
|
||||||
|
if (it < pulled)
|
||||||
|
pulled = it
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.syncher
|
||||||
|
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import ru.dbotthepony.kommons.io.BooleanValueCodec
|
||||||
|
import ru.dbotthepony.kommons.io.StreamCodec
|
||||||
|
import ru.dbotthepony.kommons.io.VarIntValueCodec
|
||||||
|
import ru.dbotthepony.kommons.io.VarLongValueCodec
|
||||||
|
import ru.dbotthepony.kommons.io.Vector2dCodec
|
||||||
|
import ru.dbotthepony.kommons.io.Vector2fCodec
|
||||||
|
import ru.dbotthepony.kommons.io.readByteArray
|
||||||
|
import ru.dbotthepony.kommons.io.readVarInt
|
||||||
|
import ru.dbotthepony.kommons.io.readVarLong
|
||||||
|
import ru.dbotthepony.kommons.io.writeByteArray
|
||||||
|
import ru.dbotthepony.kommons.io.writeVarInt
|
||||||
|
import ru.dbotthepony.kommons.io.writeVarLong
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.SkyType
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
|
fun <TYPE> BasicNetworkedElement(value: TYPE, codec: StreamCodec<TYPE>): BasicNetworkedElement<TYPE, TYPE> {
|
||||||
|
return BasicNetworkedElement(value, codec, codec, { it }, { it })
|
||||||
|
}
|
||||||
|
|
||||||
|
val UnsignedVarLongCodec = StreamCodec.Impl(DataInputStream::readVarLong, DataOutputStream::writeVarLong)
|
||||||
|
val UnsignedVarIntCodec = StreamCodec.Impl(DataInputStream::readVarInt, DataOutputStream::writeVarInt)
|
||||||
|
|
||||||
|
val ByteArrayCodec = StreamCodec.Impl(DataInputStream::readByteArray, DataOutputStream::writeByteArray)
|
||||||
|
|
||||||
|
// networking size_t...
|
||||||
|
// god help us all
|
||||||
|
val SizeTCodec = StreamCodec.Impl({ stream -> stream.readVarLong().let { if (it == 0L) -1L else it - 1L } }, { stream, value -> if (value in 0L..<Long.MAX_VALUE) stream.writeVarLong(value + 1L) else stream.writeVarLong(0L) })
|
||||||
|
|
||||||
|
fun networkedFloat(value: Double = 0.0) = FloatingNetworkedElement.float(value)
|
||||||
|
fun networkedDouble(value: Double = 0.0) = FloatingNetworkedElement.double(value)
|
||||||
|
fun networkedFixedPoint(base: Double, value: Double = 0.0) = FloatingNetworkedElement.fixed(base, value)
|
||||||
|
fun networkedSignedInt(value: Int = 0) = BasicNetworkedElement(value, VarIntValueCodec)
|
||||||
|
fun networkedUnsignedInt(value: Int = 0) = BasicNetworkedElement(value, UnsignedVarIntCodec)
|
||||||
|
fun networkedSignedLong(value: Long = 0L) = BasicNetworkedElement(value, VarLongValueCodec)
|
||||||
|
fun networkedUnsignedLong(value: Long = 0L) = BasicNetworkedElement(value, UnsignedVarLongCodec)
|
||||||
|
fun networkedBoolean(value: Boolean = false) = BasicNetworkedElement(value, BooleanValueCodec)
|
||||||
|
fun networkedPointer(value: Long = 0L) = BasicNetworkedElement(value, SizeTCodec)
|
||||||
|
fun networkedVec2f(value: Vector2d = Vector2d.ZERO) = BasicNetworkedElement(value, Vector2dCodec, Vector2fCodec, { it.toFloatVector() }, { it.toDoubleVector() })
|
||||||
|
fun networkedBytes(value: ByteArray = ByteArray(0)) = BasicNetworkedElement(value, ByteArrayCodec)
|
||||||
|
fun <E : Enum<E>> networkedEnum(value: E) = BasicNetworkedElement(value, StreamCodec.Enum(value::class.java))
|
||||||
|
|
||||||
|
inline fun <reified T> networkedJson(value: T, adapter: TypeAdapter<T> = Starbound.gson.getAdapter(T::class.java), legacyIsArray: Boolean = true): BasicNetworkedElement<T, *> {
|
||||||
|
if (legacyIsArray) {
|
||||||
|
return BasicNetworkedElement(value, JsonCodec(adapter), JsonCodec(adapter, true), { it }, { it })
|
||||||
|
} else {
|
||||||
|
return BasicNetworkedElement(value, JsonCodec(adapter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* properly networks an enum on native protocol;
|
||||||
|
* networks a signed variable length integer on legacy protocol.
|
||||||
|
* this is way too dumb beyond my comprehension
|
||||||
|
*/
|
||||||
|
fun <E : Enum<E>> networkedEnumStupid(value: E): BasicNetworkedElement<E, Int> {
|
||||||
|
val codec = StreamCodec.Enum(value::class.java)
|
||||||
|
return BasicNetworkedElement(value, codec, VarIntValueCodec, { it.ordinal.shl(1) }, { codec.values[it.ushr(1)] })
|
||||||
|
}
|
@ -0,0 +1,257 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.syncher
|
||||||
|
|
||||||
|
import ru.dbotthepony.kommons.io.readSignedVarLong
|
||||||
|
import ru.dbotthepony.kommons.io.readVarLong
|
||||||
|
import ru.dbotthepony.kommons.io.writeSignedVarLong
|
||||||
|
import ru.dbotthepony.kommons.io.writeVarLong
|
||||||
|
import ru.dbotthepony.kommons.util.Listenable
|
||||||
|
import ru.dbotthepony.kommons.util.ListenableDelegate
|
||||||
|
import ru.dbotthepony.kstarbound.math.Interpolator
|
||||||
|
import ru.dbotthepony.kstarbound.util.FloatSupplier
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
import java.util.function.Consumer
|
||||||
|
import java.util.function.DoubleSupplier
|
||||||
|
import kotlin.math.roundToLong
|
||||||
|
|
||||||
|
// works solely with doubles, but networks as either float, double or fixed point
|
||||||
|
class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, val legacyOps: Ops = ops, val interpolator: Interpolator = Interpolator.NearestMiddle) : NetworkedElement(), ListenableDelegate<Double>, DoubleSupplier {
|
||||||
|
interface Ops {
|
||||||
|
fun write(data: DataOutputStream, value: Double)
|
||||||
|
fun read(data: DataInputStream): Double
|
||||||
|
|
||||||
|
fun areDifferent(a: Double, b: Double): Boolean {
|
||||||
|
return a != b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object DoubleOps : Ops {
|
||||||
|
override fun write(data: DataOutputStream, value: Double) {
|
||||||
|
data.writeDouble(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(data: DataInputStream): Double {
|
||||||
|
return data.readDouble()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object FloatOps : Ops {
|
||||||
|
override fun write(data: DataOutputStream, value: Double) {
|
||||||
|
data.writeFloat(value.toFloat())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(data: DataInputStream): Double {
|
||||||
|
return data.readFloat().toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areDifferent(a: Double, b: Double): Boolean {
|
||||||
|
return a.toFloat() != b.toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class FixedPointOps(val base: Double) : Ops {
|
||||||
|
override fun write(data: DataOutputStream, value: Double) {
|
||||||
|
data.writeSignedVarLong((value / base).roundToLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(data: DataInputStream): Double {
|
||||||
|
return data.readSignedVarLong() * base
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areDifferent(a: Double, b: Double): Boolean {
|
||||||
|
return (a / base).roundToLong() != (b / base).roundToLong()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val queue = ArrayDeque<Pair<Double, Double>>()
|
||||||
|
|
||||||
|
var currentTime = 0.0
|
||||||
|
private set
|
||||||
|
var isInterpolating = false
|
||||||
|
private set
|
||||||
|
var extrapolation = 0.0
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val valueListeners = Listenable.Impl<Double>()
|
||||||
|
|
||||||
|
override fun accept(t: Double) {
|
||||||
|
if (t != value) {
|
||||||
|
val old = value
|
||||||
|
value = t
|
||||||
|
|
||||||
|
if (ops.areDifferent(old, t))
|
||||||
|
bumpVersion()
|
||||||
|
|
||||||
|
if (isInterpolating) {
|
||||||
|
queue.clear()
|
||||||
|
queue.add(currentTime to t)
|
||||||
|
}
|
||||||
|
|
||||||
|
valueListeners.accept(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addListener(listener: Consumer<Double>): Listenable.L {
|
||||||
|
return valueListeners.addListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(): Double {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAsDouble(): Double {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAsFloat(): Float {
|
||||||
|
return value.toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
val float = FloatSupplier { getAsFloat() }
|
||||||
|
|
||||||
|
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {
|
||||||
|
if (isLegacy) {
|
||||||
|
value = legacyOps.read(data)
|
||||||
|
} else {
|
||||||
|
value = ops.read(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
if (queue.isNotEmpty())
|
||||||
|
(if (isLegacy) legacyOps else ops).write(data, queue.last().second)
|
||||||
|
else
|
||||||
|
(if (isLegacy) legacyOps else ops).write(data, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean) {
|
||||||
|
val read = if (isLegacy) legacyOps.read(data) else ops.read(data)
|
||||||
|
|
||||||
|
if (isInterpolating) {
|
||||||
|
val realDelay = interpolationDelay + currentTime
|
||||||
|
if (queue.last().first > realDelay)
|
||||||
|
queue.clear()
|
||||||
|
|
||||||
|
queue.add(realDelay to read)
|
||||||
|
value = interpolated()
|
||||||
|
valueListeners.accept(value)
|
||||||
|
} else {
|
||||||
|
value = read
|
||||||
|
valueListeners.accept(read)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeDelta(data: DataOutputStream, remoteVersion: Long, isLegacy: Boolean) {
|
||||||
|
writeInitial(data, isLegacy)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun readBlankDelta(interpolationDelay: Double) {
|
||||||
|
if (isInterpolating) {
|
||||||
|
val actual = interpolationDelay + currentTime
|
||||||
|
val (last, lastPoint) = queue.last()
|
||||||
|
|
||||||
|
if (actual < last)
|
||||||
|
queue.clear()
|
||||||
|
|
||||||
|
queue.add(actual to lastPoint)
|
||||||
|
|
||||||
|
val old = value
|
||||||
|
value = interpolated()
|
||||||
|
|
||||||
|
if (old != value) {
|
||||||
|
valueListeners.accept(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enableInterpolation(extrapolation: Double) {
|
||||||
|
if (!isInterpolating) {
|
||||||
|
isInterpolating = true
|
||||||
|
queue.clear()
|
||||||
|
queue.add(currentTime to value)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.extrapolation = extrapolation
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun disableInterpolation() {
|
||||||
|
if (isInterpolating) {
|
||||||
|
isInterpolating = false
|
||||||
|
|
||||||
|
if (queue.isNotEmpty()) {
|
||||||
|
value = queue.last().second
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun tickInterpolation(delta: Double) {
|
||||||
|
currentTime += delta
|
||||||
|
|
||||||
|
if (isInterpolating) {
|
||||||
|
while (queue.size > 2 && queue[1].first <= currentTime) {
|
||||||
|
queue.removeFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
val old = value
|
||||||
|
value = interpolated()
|
||||||
|
|
||||||
|
if (value != old) {
|
||||||
|
valueListeners.accept(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interpolated(time: Double = 0.0): Double {
|
||||||
|
check(isInterpolating) { "Not interpolating" }
|
||||||
|
check(queue.size >= 2) { "Interpolation queue is degenerate (only ${queue.size} points)" }
|
||||||
|
|
||||||
|
val actualTime = time + currentTime
|
||||||
|
|
||||||
|
if (actualTime < queue.first().first) {
|
||||||
|
// extrapolate into past?
|
||||||
|
val (time0, value0) = queue[0]
|
||||||
|
val (time1, value1) = queue[1]
|
||||||
|
|
||||||
|
return interpolator.interpolate(((actualTime - time0) / (time1 - time0)).coerceAtLeast(-extrapolation), value0, value1)
|
||||||
|
} else if (actualTime > queue.last().first) {
|
||||||
|
// extrapolate into future
|
||||||
|
val (time0, value0) = queue[queue.size - 2]
|
||||||
|
val (time1, value1) = queue[queue.size - 1]
|
||||||
|
|
||||||
|
return interpolator.interpolate(((actualTime - time1) / (time1 - time0)).coerceAtMost(extrapolation + 1.0), value0, value1)
|
||||||
|
} else {
|
||||||
|
// normal interpolation
|
||||||
|
for (i in 0 until queue.size - 1) {
|
||||||
|
val (time0, value0) = queue[i]
|
||||||
|
val (time1, value1) = queue[i + 1]
|
||||||
|
|
||||||
|
if (actualTime in time0 .. time1) {
|
||||||
|
return interpolator.interpolate((actualTime - time0) / (time1 - time0), value0, value1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw RuntimeException("unreachable code")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Uses floats when asked to network for legacy protocol
|
||||||
|
*/
|
||||||
|
fun float(value: Double = 0.0): FloatingNetworkedElement {
|
||||||
|
return FloatingNetworkedElement(value, DoubleOps, FloatOps)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun double(value: Double = 0.0): FloatingNetworkedElement {
|
||||||
|
return FloatingNetworkedElement(value, DoubleOps)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fixed(base: Double, value: Double = 0.0): FloatingNetworkedElement {
|
||||||
|
return FloatingNetworkedElement(value, FixedPointOps(base))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,154 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.syncher
|
||||||
|
|
||||||
|
import ru.dbotthepony.kommons.io.readVarInt
|
||||||
|
import ru.dbotthepony.kommons.io.writeVarInt
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
import java.util.function.Consumer
|
||||||
|
import java.util.function.LongSupplier
|
||||||
|
|
||||||
|
class GroupElement() : NetworkedElement() {
|
||||||
|
constructor(element: NetworkedElement, vararg rest: NetworkedElement) : this() {
|
||||||
|
add(element)
|
||||||
|
rest.forEach { add(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// element -> propagateInterpolation
|
||||||
|
private val elements = ArrayList<Pair<NetworkedElement, Boolean>>()
|
||||||
|
|
||||||
|
var isInterpolating = false
|
||||||
|
private set
|
||||||
|
var extrapolation = 0.0
|
||||||
|
private set
|
||||||
|
|
||||||
|
override fun specifyVersioner(versionCounter: LongSupplier) {
|
||||||
|
super.specifyVersioner(versionCounter)
|
||||||
|
elements.forEach { it.first.specifyVersioner(versionCounter) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enableInterpolation(extrapolation: Double) {
|
||||||
|
isInterpolating = true
|
||||||
|
this.extrapolation = extrapolation
|
||||||
|
elements.forEach { it.first.enableInterpolation(extrapolation) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun disableInterpolation() {
|
||||||
|
isInterpolating = false
|
||||||
|
extrapolation = 0.0
|
||||||
|
elements.forEach { it.first.disableInterpolation() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun tickInterpolation(delta: Double) {
|
||||||
|
elements.forEach { it.first.tickInterpolation(delta) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(element: NetworkedElement, propagateInterpolation: Boolean = true): GroupElement {
|
||||||
|
require(elements.none { it.first == element }) { "Already has element $element in $this" }
|
||||||
|
elements.add(element to propagateInterpolation)
|
||||||
|
|
||||||
|
if (propagateInterpolation) {
|
||||||
|
if (isInterpolating)
|
||||||
|
element.enableInterpolation(extrapolation)
|
||||||
|
else
|
||||||
|
element.disableInterpolation()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (versionCounter != null) {
|
||||||
|
element.specifyVersioner(versionCounter!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
element.listeners.addListener(Consumer {
|
||||||
|
this.version = this.version.coerceAtLeast(it)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.version = this.version.coerceAtLeast(element.version)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {
|
||||||
|
elements.forEach { it.first.readInitial(data, isLegacy) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
elements.forEach { it.first.writeInitial(data, isLegacy) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean) {
|
||||||
|
check(elements.isNotEmpty()) { "No networked elements in this group" }
|
||||||
|
|
||||||
|
if (elements.size == 1) {
|
||||||
|
elements[0].first.readDelta(data, interpolationDelay, isLegacy)
|
||||||
|
} else {
|
||||||
|
var nextIndex = data.readVarInt()
|
||||||
|
|
||||||
|
// here goes more funk - when interpolating,
|
||||||
|
// elements missing from network message are interpolated
|
||||||
|
|
||||||
|
if (isInterpolating) {
|
||||||
|
// when interpolating, we can't just sparsingly read elements,
|
||||||
|
// since elements which are absent from delta must be updated too
|
||||||
|
// Things get ugly
|
||||||
|
|
||||||
|
for ((i, element) in elements.withIndex()) {
|
||||||
|
if (nextIndex == 0 || i < nextIndex - 1) {
|
||||||
|
element.first.readBlankDelta(interpolationDelay)
|
||||||
|
} else if (i == nextIndex - 1) {
|
||||||
|
element.first.readDelta(data, interpolationDelay, isLegacy)
|
||||||
|
nextIndex = data.readVarInt()
|
||||||
|
} else {
|
||||||
|
throw IllegalStateException("Networked element group indexes were written out of order")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while (nextIndex != 0) {
|
||||||
|
val element = elements.getOrNull(nextIndex - 1) ?: throw NoSuchElementException("Unknown networked element with index ${nextIndex - 1}!")
|
||||||
|
element.first.readDelta(data, interpolationDelay, isLegacy)
|
||||||
|
nextIndex = data.readVarInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun readBlankDelta(interpolationDelay: Double) {
|
||||||
|
if (isInterpolating) {
|
||||||
|
elements.forEach { it.first.readBlankDelta(interpolationDelay) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeDelta(data: DataOutputStream, remoteVersion: Long, isLegacy: Boolean) {
|
||||||
|
check(elements.isNotEmpty()) { "No networked elements in this group" }
|
||||||
|
|
||||||
|
// here is where it gets funky, data structure
|
||||||
|
// is different for when there is only one element in group
|
||||||
|
// or multiple
|
||||||
|
|
||||||
|
if (elements.size == 1) {
|
||||||
|
// one element - pass through
|
||||||
|
elements[0].first.writeDelta(data, remoteVersion, isLegacy)
|
||||||
|
} else {
|
||||||
|
// otherwise, sequentially scan elements for updates
|
||||||
|
// and if element needs updating, write element's index as variable length integer;
|
||||||
|
// then write element itself
|
||||||
|
|
||||||
|
for ((i, element) in elements.withIndex()) {
|
||||||
|
if (element.first.hasChangedSince(remoteVersion)) {
|
||||||
|
data.writeVarInt(i + 1)
|
||||||
|
element.first.writeDelta(data, remoteVersion, isLegacy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.writeVarInt(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasChangedSince(version: Long): Boolean {
|
||||||
|
if (elements.isEmpty())
|
||||||
|
return false
|
||||||
|
|
||||||
|
return super.hasChangedSince(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bumpVersion() {
|
||||||
|
elements.forEach { it.first.bumpVersion() }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.syncher
|
||||||
|
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||||
|
import ru.dbotthepony.kommons.io.StreamCodec
|
||||||
|
import ru.dbotthepony.kommons.io.readByteArray
|
||||||
|
import ru.dbotthepony.kommons.io.writeByteArray
|
||||||
|
import ru.dbotthepony.kstarbound.json.BinaryJsonReader
|
||||||
|
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
|
class JsonCodec<V>(val adapter: TypeAdapter<V>, val wrapIntoArray: Boolean = false) : StreamCodec<V> {
|
||||||
|
override fun copy(value: V): V {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(stream: DataInputStream): V {
|
||||||
|
if (wrapIntoArray) {
|
||||||
|
return adapter.read(BinaryJsonReader(FastByteArrayInputStream(stream.readByteArray())))
|
||||||
|
} else {
|
||||||
|
return adapter.read(BinaryJsonReader(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(stream: DataOutputStream, value: V) {
|
||||||
|
if (wrapIntoArray) {
|
||||||
|
val output = FastByteArrayOutputStream()
|
||||||
|
DataOutputStream(output).writeJsonElement(adapter.toJsonTree(value))
|
||||||
|
stream.writeByteArray(output.array, 0, output.length)
|
||||||
|
} else {
|
||||||
|
stream.writeJsonElement(adapter.toJsonTree(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.syncher
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||||
|
import ru.dbotthepony.kommons.io.writeByteArray
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.util.Objects
|
||||||
|
import java.util.function.LongSupplier
|
||||||
|
|
||||||
|
class MasterElement<E : NetworkedElement>(val upstream: E) : LongSupplier {
|
||||||
|
var version: Long = 0L
|
||||||
|
private set
|
||||||
|
|
||||||
|
override fun getAsLong(): Long {
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
upstream.specifyVersioner(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(remoteVersion: Long = 0L, isLegacy: Boolean = false): Pair<ByteArrayList, Long> {
|
||||||
|
if (remoteVersion < 0L)
|
||||||
|
throw IllegalArgumentException("remote version is negative")
|
||||||
|
else if (remoteVersion == 0L) {
|
||||||
|
val output = FastByteArrayOutputStream()
|
||||||
|
val stream = DataOutputStream(output)
|
||||||
|
stream.write(1)
|
||||||
|
upstream.writeInitial(stream, isLegacy)
|
||||||
|
return ByteArrayList.wrap(output.array, output.length) to ++version
|
||||||
|
} else {
|
||||||
|
val output = FastByteArrayOutputStream()
|
||||||
|
val stream = DataOutputStream(output)
|
||||||
|
|
||||||
|
if (upstream.hasChangedSince(remoteVersion)) {
|
||||||
|
stream.write(0)
|
||||||
|
upstream.writeDelta(stream, remoteVersion, isLegacy)
|
||||||
|
return ByteArrayList.wrap(output.array, output.length) to ++version
|
||||||
|
}
|
||||||
|
|
||||||
|
return ByteArrayList() to version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(stream: OutputStream, remoteVersion: Long = 0L, isLegacy: Boolean = false): Long {
|
||||||
|
val (data, version) = write(remoteVersion, isLegacy)
|
||||||
|
stream.writeByteArray(data.elements(), 0, data.size)
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
fun read(data: InputStream, interpolationTime: Double = 0.0, isLegacy: Boolean = false) {
|
||||||
|
if (data.available() == 0) {
|
||||||
|
upstream.readBlankDelta(interpolationTime)
|
||||||
|
} else {
|
||||||
|
val stream = DataInputStream(data)
|
||||||
|
|
||||||
|
if (stream.readBoolean()) {
|
||||||
|
upstream.readInitial(stream, isLegacy)
|
||||||
|
} else {
|
||||||
|
upstream.readDelta(stream, interpolationTime, isLegacy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun read(data: ByteArray, interpolationTime: Double = 0.0, isLegacy: Boolean = false) {
|
||||||
|
return read(FastByteArrayInputStream(data), interpolationTime, isLegacy)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun read(data: ByteArray, offset: Int, length: Int, interpolationTime: Double = 0.0, isLegacy: Boolean = false) {
|
||||||
|
Objects.checkFromIndexSize(offset, length, data.size)
|
||||||
|
return read(FastByteArrayInputStream(data, offset, length), interpolationTime, isLegacy)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun read(data: ByteArrayList, interpolationTime: Double = 0.0, isLegacy: Boolean = false) {
|
||||||
|
return read(data.elements(), 0, data.size, interpolationTime, isLegacy)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.syncher
|
||||||
|
|
||||||
|
import ru.dbotthepony.kommons.util.Listenable
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
import java.util.function.LongSupplier
|
||||||
|
|
||||||
|
// While I already have created my own syncher as part of Kommons (for minecraft mods),
|
||||||
|
// Starbound networking is done a bit differently.
|
||||||
|
// Most notably, networked variables employ interpolation and extrapolation,
|
||||||
|
// have different network data format when networking for first time and require
|
||||||
|
// strict field order when receiving them.
|
||||||
|
|
||||||
|
// Also, due to structure of networked delta data, most "lazy" optimizations can not
|
||||||
|
// be applied, such as dirty lists. If interpolation is enabled, all elements must either network their
|
||||||
|
// delta, or network "nothing changed, interpolate"
|
||||||
|
|
||||||
|
// But on second looks, versioning vs event has its own advantages, namely,
|
||||||
|
// IF data is updated way more frequent than it is sent to clients, then
|
||||||
|
// versioning approach is faster than event approach (because on each data update
|
||||||
|
// we don't need to notify all remote networkers about this)
|
||||||
|
|
||||||
|
// infrequent updates: Event
|
||||||
|
// very frequent updates: Polling with versioning
|
||||||
|
abstract class NetworkedElement {
|
||||||
|
// Full store / load of the entire element.
|
||||||
|
abstract fun readInitial(data: DataInputStream, isLegacy: Boolean)
|
||||||
|
abstract fun writeInitial(data: DataOutputStream, isLegacy: Boolean)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a delta written by writeNetDelta. 'interpolationTime' is the time in
|
||||||
|
* the future that data from this delta should be delayed and smoothed into,
|
||||||
|
* if interpolation is enabled.
|
||||||
|
*/
|
||||||
|
abstract fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write all the state changes that have happened since (and including)
|
||||||
|
* fromVersion. The normal way to use this would be to call writeDelta with
|
||||||
|
* the version at the time of the *last* call to writeDelta, + 1. If
|
||||||
|
* fromVersion is 0, this will always write the full state. Should return
|
||||||
|
* true if a delta was needed and was written to DataStream, false otherwise.
|
||||||
|
*/
|
||||||
|
abstract fun writeDelta(data: DataOutputStream, remoteVersion: Long, isLegacy: Boolean)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When extrapolating, it is important to notify when a delta WOULD have been
|
||||||
|
* received even if no deltas are produced, so no extrapolation takes place.
|
||||||
|
*/
|
||||||
|
abstract fun readBlankDelta(interpolationDelay: Double = 0.0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables interpolation mode. If interpolation mode is enabled, then
|
||||||
|
* NetElements will delay presenting incoming delta data for the
|
||||||
|
* 'interpolationTime' parameter given in readNetDelta, and smooth between
|
||||||
|
* received values. When interpolation is enabled, tickNetInterpolation must
|
||||||
|
* be periodically called to smooth values forward in time. If
|
||||||
|
* extrapolationHint is given, this may be used as a hint for the amount of
|
||||||
|
* time to extrapolate forward if no deltas are received.
|
||||||
|
*/
|
||||||
|
abstract fun enableInterpolation(extrapolation: Double = 0.0)
|
||||||
|
abstract fun disableInterpolation()
|
||||||
|
abstract fun tickInterpolation(delta: Double)
|
||||||
|
|
||||||
|
// A network of NetElements will have a shared monotinically increasing
|
||||||
|
// NetElementVersion. When elements are updated, they will mark the version
|
||||||
|
// number at the time they are updated so that a delta can be constructed
|
||||||
|
// that contains only changes since any past version.
|
||||||
|
var version: Long = 0L
|
||||||
|
protected set(value) {
|
||||||
|
if (field != value) {
|
||||||
|
require(value > field) { "Downgrading element version from $field to $value" }
|
||||||
|
field = value
|
||||||
|
listeners.accept(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun hasChangedSince(version: Long): Boolean {
|
||||||
|
return this.version >= version
|
||||||
|
}
|
||||||
|
|
||||||
|
val listeners = Listenable.Impl<Long>()
|
||||||
|
|
||||||
|
var versionCounter: LongSupplier? = null
|
||||||
|
protected set
|
||||||
|
|
||||||
|
open fun specifyVersioner(versionCounter: LongSupplier) {
|
||||||
|
this.versionCounter = versionCounter
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks this networked element dirty
|
||||||
|
*/
|
||||||
|
open fun bumpVersion() {
|
||||||
|
version = versionCounter?.asLong ?: version
|
||||||
|
}
|
||||||
|
}
|
@ -37,6 +37,8 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
lateinit var shipWorld: ServerWorld
|
lateinit var shipWorld: ServerWorld
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var skyVersion = 0L
|
||||||
|
|
||||||
init {
|
init {
|
||||||
connectionID = server.nextConnectionID.incrementAndGet()
|
connectionID = server.nextConnectionID.incrementAndGet()
|
||||||
}
|
}
|
||||||
@ -86,6 +88,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
|
|
||||||
override fun setupNative() {
|
override fun setupNative() {
|
||||||
super.setupNative()
|
super.setupNative()
|
||||||
|
shipChunkSource = IChunkSource.Void
|
||||||
}
|
}
|
||||||
|
|
||||||
fun receiveShipChunks(chunks: Map<ByteKey, KOptional<ByteArray>>) {
|
fun receiveShipChunks(chunks: Map<ByteKey, KOptional<ByteArray>>) {
|
||||||
@ -219,13 +222,15 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun inGame() {
|
override fun inGame() {
|
||||||
// server.playerInGame(this)
|
if (!isLegacy) {
|
||||||
|
server.playerInGame(this)
|
||||||
LOGGER.info("Initializing ship world for $this")
|
} else {
|
||||||
shipWorld = ServerWorld(server, WorldGeometry(Vector2i(2048, 2048), false, false))
|
LOGGER.info("Initializing ship world for $this")
|
||||||
shipWorld.addChunkSource(shipChunkSource)
|
shipWorld = ServerWorld(server, WorldGeometry(Vector2i(2048, 2048), false, false))
|
||||||
shipWorld.thread.start()
|
shipWorld.addChunkSource(shipChunkSource)
|
||||||
shipWorld.acceptPlayer(this)
|
shipWorld.thread.start()
|
||||||
|
shipWorld.acceptPlayer(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -49,9 +49,12 @@ class ServerWorld(
|
|||||||
player.world = this
|
player.world = this
|
||||||
|
|
||||||
if (player.isLegacy) {
|
if (player.isLegacy) {
|
||||||
|
val (skyData, skyVersion) = sky.networkedGroup.write(isLegacy = true)
|
||||||
|
player.skyVersion = skyVersion
|
||||||
|
|
||||||
player.sendAndFlush(WorldStartPacket(
|
player.sendAndFlush(WorldStartPacket(
|
||||||
templateData = WorldTemplate(geometry).toJson(true),
|
templateData = WorldTemplate(geometry).toJson(true),
|
||||||
skyData = ByteArray(0),
|
skyData = skyData.toByteArray(),
|
||||||
weatherData = ByteArray(0),
|
weatherData = ByteArray(0),
|
||||||
playerStart = playerSpawnPosition,
|
playerStart = playerSpawnPosition,
|
||||||
playerRespawn = playerSpawnPosition,
|
playerRespawn = playerSpawnPosition,
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.util
|
||||||
|
|
||||||
|
import java.util.function.Supplier
|
||||||
|
|
||||||
|
fun interface FloatSupplier : Supplier<Float> {
|
||||||
|
@Deprecated("Use type specific method instead", replaceWith = ReplaceWith("this.getAsFloat()"))
|
||||||
|
override fun get(): Float {
|
||||||
|
return getAsFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAsFloat(): Float
|
||||||
|
}
|
79
src/main/kotlin/ru/dbotthepony/kstarbound/world/Sky.kt
Normal file
79
src/main/kotlin/ru/dbotthepony/kstarbound/world/Sky.kt
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||||
|
import ru.dbotthepony.kommons.io.VarIntValueCodec
|
||||||
|
import ru.dbotthepony.kommons.util.getValue
|
||||||
|
import ru.dbotthepony.kommons.util.setValue
|
||||||
|
import ru.dbotthepony.kommons.util.value
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.SkyParameters
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.SkyType
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.WarpPhase
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.BasicNetworkedElement
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.GroupElement
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.NetworkedElement
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.networkedDouble
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.networkedEnumStupid
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.networkedFloat
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.networkedJson
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.networkedSignedInt
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.networkedUnsignedInt
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.networkedVec2f
|
||||||
|
|
||||||
|
class Sky() {
|
||||||
|
private val skyParametersNetState = networkedJson(SkyParameters())
|
||||||
|
|
||||||
|
private val skyTypeNetState = networkedEnumStupid(SkyType.ORBITAL)
|
||||||
|
private val timeNetState = networkedDouble()
|
||||||
|
private val flyingTypeNetState = networkedUnsignedInt()
|
||||||
|
private val enterHyperspaceNetState = networkedBoolean()
|
||||||
|
private val startInWarpNetState = networkedBoolean()
|
||||||
|
private val worldMoveNetState = networkedEnumStupid(WarpPhase.MAINTAIN)
|
||||||
|
private val starMoveNetState = networkedVec2f()
|
||||||
|
private val warpPhaseNetState = networkedVec2f()
|
||||||
|
private val flyingTimerNetState = networkedFloat()
|
||||||
|
|
||||||
|
var skyType by skyTypeNetState
|
||||||
|
private set
|
||||||
|
var time by timeNetState
|
||||||
|
private set
|
||||||
|
var flyingType by flyingTypeNetState
|
||||||
|
private set
|
||||||
|
var enterHyperspace by enterHyperspaceNetState
|
||||||
|
private set
|
||||||
|
var startInWarp by startInWarpNetState
|
||||||
|
private set
|
||||||
|
var worldMove by worldMoveNetState
|
||||||
|
private set
|
||||||
|
var starMove by starMoveNetState
|
||||||
|
private set
|
||||||
|
var warpPhase by warpPhaseNetState
|
||||||
|
private set
|
||||||
|
var flyingTimer by flyingTimerNetState
|
||||||
|
private set
|
||||||
|
|
||||||
|
val networkedGroup = MasterElement(GroupElement(
|
||||||
|
skyParametersNetState,
|
||||||
|
skyTypeNetState,
|
||||||
|
timeNetState,
|
||||||
|
flyingTypeNetState,
|
||||||
|
enterHyperspaceNetState,
|
||||||
|
startInWarpNetState,
|
||||||
|
worldMoveNetState,
|
||||||
|
starMoveNetState,
|
||||||
|
warpPhaseNetState,
|
||||||
|
flyingTimerNetState,
|
||||||
|
))
|
||||||
|
|
||||||
|
constructor(parameters: SkyParameters, inOrbit: Boolean) : this() {
|
||||||
|
skyParametersNetState.value = parameters.copy()
|
||||||
|
|
||||||
|
if (inOrbit) {
|
||||||
|
skyType = SkyType.ORBITAL
|
||||||
|
} else {
|
||||||
|
skyType = parameters.skyType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -34,6 +34,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
val background = TileView.Background(this)
|
val background = TileView.Background(this)
|
||||||
val foreground = TileView.Foreground(this)
|
val foreground = TileView.Foreground(this)
|
||||||
val mailbox = MailboxExecutorService()
|
val mailbox = MailboxExecutorService()
|
||||||
|
val sky = Sky()
|
||||||
|
|
||||||
override fun getCellDirect(x: Int, y: Int): AbstractCell {
|
override fun getCellDirect(x: Int, y: Int): AbstractCell {
|
||||||
if (!geometry.x.inBoundsCell(x) || !geometry.y.inBoundsCell(y)) return AbstractCell.NULL
|
if (!geometry.x.inBoundsCell(x) || !geometry.y.inBoundsCell(y)) return AbstractCell.NULL
|
||||||
|
@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.test
|
|||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import ru.dbotthepony.kstarbound.util.HashTableInterner
|
import ru.dbotthepony.kstarbound.util.HashTableInterner
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.MWCRandom
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
|
|
||||||
@ -16,7 +17,7 @@ object InternerTest {
|
|||||||
|
|
||||||
for (i in 0 until 8) {
|
for (i in 0 until 8) {
|
||||||
threads.add(Thread {
|
threads.add(Thread {
|
||||||
val rand = RandomGenerator.of("Xoroshiro128PlusPlus")
|
val rand = MWCRandom()
|
||||||
|
|
||||||
for (i2 in 0 until 100_000) {
|
for (i2 in 0 until 100_000) {
|
||||||
val v = rand.nextInt()
|
val v = rand.nextInt()
|
||||||
|
@ -0,0 +1,126 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.test
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import ru.dbotthepony.kommons.io.Vector2fCodec
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2f
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.BasicNetworkedElement
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.EventCounterElement
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.FloatingNetworkedElement
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.GroupElement
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.networkedDouble
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.networkedEnum
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.networkedFloat
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.networkedPointer
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.networkedSignedInt
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.networkedUnsignedInt
|
||||||
|
|
||||||
|
// reflects tests defined in original sources
|
||||||
|
// because they are quite comprehensive
|
||||||
|
object NetworkedElementTests {
|
||||||
|
enum class TestEnum {
|
||||||
|
VALUE1,
|
||||||
|
VALUE2,
|
||||||
|
VALUE3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("NetworkedElement")
|
||||||
|
fun testAllTypes() {
|
||||||
|
val masterField1 = networkedSignedInt()
|
||||||
|
val masterField2 = networkedUnsignedInt()
|
||||||
|
val masterField3 = networkedPointer()
|
||||||
|
val masterField4 = networkedFloat()
|
||||||
|
val masterField5 = networkedDouble()
|
||||||
|
val masterField6 = networkedFixedPoint(0.01)
|
||||||
|
val masterField7 = networkedFixedPoint(0.01) // no difference
|
||||||
|
val masterField8 = networkedBoolean()
|
||||||
|
val masterField9 = networkedEnum(TestEnum.VALUE1)
|
||||||
|
val masterField10 = EventCounterElement()
|
||||||
|
val masterField11 = BasicNetworkedElement(Vector2f.ZERO, Vector2fCodec)
|
||||||
|
|
||||||
|
val master = MasterElement(GroupElement())
|
||||||
|
master.upstream.add(masterField1)
|
||||||
|
master.upstream.add(masterField2)
|
||||||
|
master.upstream.add(masterField3)
|
||||||
|
master.upstream.add(masterField4)
|
||||||
|
master.upstream.add(masterField5)
|
||||||
|
master.upstream.add(masterField6)
|
||||||
|
master.upstream.add(masterField7)
|
||||||
|
master.upstream.add(masterField8)
|
||||||
|
master.upstream.add(masterField9)
|
||||||
|
master.upstream.add(masterField10)
|
||||||
|
master.upstream.add(masterField11)
|
||||||
|
|
||||||
|
masterField1.accept(567)
|
||||||
|
masterField2.accept(17000)
|
||||||
|
masterField3.accept(22222)
|
||||||
|
masterField4.accept(1.55)
|
||||||
|
masterField5.accept(1.12345678910111213)
|
||||||
|
masterField6.accept(2000.62)
|
||||||
|
masterField7.accept(2000.62)
|
||||||
|
masterField8.accept(true)
|
||||||
|
masterField9.accept(TestEnum.VALUE2)
|
||||||
|
masterField10.trigger()
|
||||||
|
masterField11.accept(Vector2f(2.0f, 2.0f))
|
||||||
|
|
||||||
|
assertEquals(567, masterField1.get())
|
||||||
|
assertEquals(17000, masterField2.get())
|
||||||
|
assertEquals(22222, masterField3.get())
|
||||||
|
assertEquals(1.55, masterField4.get())
|
||||||
|
assertEquals(1.12345678910111213, masterField5.get())
|
||||||
|
assertEquals(2000.62, masterField6.get())
|
||||||
|
assertEquals(2000.62, masterField7.get())
|
||||||
|
assertEquals(true, masterField8.get())
|
||||||
|
assertEquals(TestEnum.VALUE2, masterField9.get())
|
||||||
|
assertTrue(masterField10.pullOccurred())
|
||||||
|
assertEquals(Vector2f(2.0f, 2.0f), masterField11.get())
|
||||||
|
|
||||||
|
val slaveField1 = networkedSignedInt()
|
||||||
|
val slaveField2 = networkedUnsignedInt()
|
||||||
|
val slaveField3 = networkedPointer()
|
||||||
|
val slaveField4 = networkedFloat()
|
||||||
|
val slaveField5 = networkedDouble()
|
||||||
|
val slaveField6 = networkedFixedPoint(0.01)
|
||||||
|
val slaveField7 = networkedFixedPoint(0.01) // no difference
|
||||||
|
val slaveField8 = networkedBoolean()
|
||||||
|
val slaveField9 = networkedEnum(TestEnum.VALUE1)
|
||||||
|
val slaveField10 = EventCounterElement()
|
||||||
|
val slaveField11 = BasicNetworkedElement(Vector2f.ZERO, Vector2fCodec)
|
||||||
|
|
||||||
|
val slave = MasterElement(GroupElement())
|
||||||
|
slave.upstream.add(slaveField1)
|
||||||
|
slave.upstream.add(slaveField2)
|
||||||
|
slave.upstream.add(slaveField3)
|
||||||
|
slave.upstream.add(slaveField4)
|
||||||
|
slave.upstream.add(slaveField5)
|
||||||
|
slave.upstream.add(slaveField6)
|
||||||
|
slave.upstream.add(slaveField7)
|
||||||
|
slave.upstream.add(slaveField8)
|
||||||
|
slave.upstream.add(slaveField9)
|
||||||
|
slave.upstream.add(slaveField10)
|
||||||
|
slave.upstream.add(slaveField11)
|
||||||
|
|
||||||
|
val result = master.write().first
|
||||||
|
|
||||||
|
slave.read(FastByteArrayInputStream(result.array, 0, result.length))
|
||||||
|
|
||||||
|
assertEquals(567, slaveField1.get())
|
||||||
|
assertEquals(17000, slaveField2.get())
|
||||||
|
assertEquals(22222, slaveField3.get())
|
||||||
|
assertEquals(1.55f, slaveField4.getAsFloat())
|
||||||
|
assertEquals(1.12345678910111213, slaveField5.get())
|
||||||
|
assertEquals(2000.62f, slaveField6.getAsFloat())
|
||||||
|
assertEquals(2000.62f, slaveField7.getAsFloat()) // due to precision fuckery, 2000.62 turns into 2000.620000001
|
||||||
|
assertEquals(true, slaveField8.get())
|
||||||
|
assertEquals(TestEnum.VALUE2, slaveField9.get())
|
||||||
|
assertTrue(slaveField10.pullOccurred())
|
||||||
|
assertEquals(Vector2f(2.0f, 2.0f), slaveField11.get())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user