Refine VNL multi-world with new vector codec

This commit is contained in:
Chord 2019-12-16 13:32:54 -05:00 committed by pschord
parent 37ad423820
commit a54ee2f0b7
5 changed files with 103 additions and 24 deletions

View file

@ -0,0 +1,34 @@
// Copyright (c) 2019 PSForever
package net.psforever.newcodecs
import scodec._
import scodec.bits.BitVector
final class PrefixedVectorCodec[A](firstCodec: Codec[A], codec: Codec[A], limit: Option[Int] = None) extends Codec[Vector[A]] {
def sizeBound = limit match {
case None => SizeBound.unknown
case Some(lim) => codec.sizeBound * lim.toLong
}
def encode(vector: Vector[A]) = Encoder.encodeSeq(firstCodec)(vector.slice(0,1)).map { bits =>
if (vector.length > 1)
bits ++ (Encoder.encodeSeq(codec)(vector.tail) getOrElse BitVector.empty)
else
bits
}
def decode(buffer: BitVector) : scodec.Attempt[scodec.DecodeResult[Vector[A]]] = {
Decoder.decodeCollect[Vector, A](firstCodec, Some(1))(buffer) match {
case Attempt.Successful(firstValue) =>
Decoder.decodeCollect[Vector, A](codec, limit map { _ - 1 })(firstValue.remainder) match {
case Attempt.Successful(secondValue) =>
Attempt.successful(DecodeResult(firstValue.value ++ secondValue.value, secondValue.remainder))
case Attempt.Failure(e) => Attempt.failure(e)
}
case Attempt.Failure(e) => Attempt.failure(e)
}
}
override def toString = s"vector($codec)"
}

View file

@ -3,14 +3,22 @@ package net.psforever.newcodecs
import scodec.Attempt
import scodec.Attempt.{Failure, Successful}
import scodec.Codec
import scodec._
import scodec.bits.BitVector
package object newcodecs {
def q_double(min: Double, max: Double, bits: Int): Codec[Double] = new QuantizedDoubleCodec(min, max, bits)
def q_float(min : Double, max : Double, bits : Int): Codec[Float] = q_double(min, max, bits).narrow(v => Attempt.successful(v.toFloat), _.toDouble)
def binary_choice[A](choice: Boolean, codec_true: => Codec[A], codec_false: => Codec[A]): Codec[A] = new BinaryChoiceCodec(choice, codec_true, codec_false)
def prefixedVectorOfN[A](countCodec: Codec[Int], firstValueCodec: Codec[A], valueCodec: Codec[A]): Codec[Vector[A]] =
countCodec.
flatZip { count => new PrefixedVectorCodec(firstValueCodec, valueCodec, Some(count)) }.
narrow[Vector[A]]({ case (cnt, xs) =>
if (xs.size == cnt) Attempt.successful(xs)
else Attempt.failure(Err(s"Insufficient number of elements: decoded ${xs.size} but should have decoded $cnt"))
}, xs => (xs.size, xs)).
withToString(s"vectorOfN($countCodec, $valueCodec)")
}

View file

@ -5,6 +5,7 @@ import java.net.{InetAddress, InetSocketAddress}
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.PlanetSideEmpire
import net.psforever.newcodecs.newcodecs._
import scodec._
import scodec.bits._
import scodec.codecs._
@ -30,8 +31,7 @@ final case class WorldInformation(name : String, status : WorldStatus.Value,
connections : Vector[WorldConnectionInfo],
empireNeed : PlanetSideEmpire.Value)
final case class VNLWorldStatusMessage(welcomeMessage : String, world_count : Int, world : WorldInformation,
other_worlds : Vector[WorldInformation] = Vector())
final case class VNLWorldStatusMessage(welcomeMessage : String, worlds : Vector[WorldInformation])
extends PlanetSideGamePacket {
type Packet = VNLWorldStatusMessage
def opcode = GamePacketOpcode.VNLWorldStatusMessage
@ -119,9 +119,7 @@ object VNLWorldStatusMessage extends Marshallable[VNLWorldStatusMessage] {
)).as[WorldInformation]
implicit val codec : Codec[VNLWorldStatusMessage] = (
("welcome_message" | PacketHelpers.encodedWideString) ::
(("num_worlds" | uint8L) flatPrepend { num_worlds =>
("primary_world" | world_codec) ::
("extra_worlds" | vectorOfN(provide(num_worlds-1), world_codec_aligned)
)})).as[VNLWorldStatusMessage]
("welcome_message" | PacketHelpers.encodedWideString) ::
("worlds" | prefixedVectorOfN(uint8L, world_codec, world_codec_aligned))
).as[VNLWorldStatusMessage]
}

View file

@ -16,9 +16,12 @@ class VNLWorldStatusMessageTest extends Specification {
"decode" in {
PacketCoding.DecodePacket(string).require match {
case VNLWorldStatusMessage(message, _, world, extra_worlds) =>
extra_worlds.length mustEqual 0
case VNLWorldStatusMessage(message, worlds) =>
message mustEqual "Welcome to PlanetSide! "
worlds.length mustEqual 1
val world = worlds(0)
world.name mustEqual "gemini"
world.empireNeed mustEqual PlanetSideEmpire.NC
world.status mustEqual WorldStatus.Up
@ -36,13 +39,14 @@ class VNLWorldStatusMessageTest extends Specification {
}
"encode" in {
val msg = VNLWorldStatusMessage("Welcome to PlanetSide! ", 1,
WorldInformation("gemini", WorldStatus.Up, ServerType.Released,
val msg = VNLWorldStatusMessage("Welcome to PlanetSide! ",
Vector(WorldInformation("gemini", WorldStatus.Up, ServerType.Released,
Vector(
WorldConnectionInfo(new InetSocketAddress(InetAddress.getByName("64.37.158.69"), 30007))
), PlanetSideEmpire.NC
)
))
)
//0100 04 00 01459e2540377540
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@ -50,18 +54,56 @@ class VNLWorldStatusMessageTest extends Specification {
pkt mustEqual string
}
"encode and decode empty messages" in {
val string = hex"0584410041004100410000"
val empty_msg = VNLWorldStatusMessage("AAAA", Vector())
val empty_pkt = PacketCoding.EncodePacket(empty_msg).require.toByteVector
empty_pkt mustEqual string
PacketCoding.DecodePacket(string).require match {
case VNLWorldStatusMessage(message, worlds) =>
message mustEqual "AAAA"
worlds.length mustEqual 0
case _ =>
ko
}
}
"encode and decode multiple worlds" in {
val msg = VNLWorldStatusMessage("Welcome to PlanetSide! ", 2,
WorldInformation("ABCDABCD1", WorldStatus.Up, ServerType.Released, Vector(), PlanetSideEmpire.NC),
var string = hex" 0597570065006c0063006f006d006500200074006f00200050006c0061006e0065007400530069006400650021002000028941424344414243443101000300006240414243444142434432000002020000"
val msg = VNLWorldStatusMessage("Welcome to PlanetSide! ",
Vector(
WorldInformation("ABCDABCD1", WorldStatus.Up, ServerType.Released, Vector(), PlanetSideEmpire.NC),
WorldInformation("ABCDABCD2", WorldStatus.Down, ServerType.Beta, Vector(), PlanetSideEmpire.TR)
))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
println(pkt)
pkt mustEqual string
// TODO: actually test something
ok
string = hex" 0597570065006c0063006f006d006500200074006f00200050006c0061006e0065007400530069006400650021002000028941424344414243443101000300006240414243444142434432000002020000"
PacketCoding.DecodePacket(string).require match {
case VNLWorldStatusMessage(message, worlds) =>
message mustEqual "Welcome to PlanetSide! "
worlds.length mustEqual 2
worlds(0).name mustEqual "ABCDABCD1"
worlds(0).empireNeed mustEqual PlanetSideEmpire.NC
worlds(0).status mustEqual WorldStatus.Up
worlds(0).serverType mustEqual ServerType.Released
worlds(0).connections.length mustEqual 0
worlds(1).name mustEqual "ABCDABCD2"
worlds(1).empireNeed mustEqual PlanetSideEmpire.TR
worlds(1).status mustEqual WorldStatus.Down
worlds(1).serverType mustEqual ServerType.Beta
worlds(1).connections.length mustEqual 0
case _ =>
ko
}
}
}

View file

@ -164,13 +164,10 @@ class LoginSessionActor extends Actor with MDCContextAware {
}
def updateServerList() = {
val msg = VNLWorldStatusMessage("Welcome to PlanetSide! ", 2,
WorldInformation(
serverName, WorldStatus.Up, ServerType.Beta, Vector(WorldConnectionInfo(serverAddress)), PlanetSideEmpire.VS
),
val msg = VNLWorldStatusMessage("Welcome to PlanetSide! ",
Vector(
WorldInformation(
serverName + "A", WorldStatus.Up, ServerType.Beta, Vector(WorldConnectionInfo(serverAddress)), PlanetSideEmpire.TR
serverName, WorldStatus.Up, ServerType.Beta, Vector(WorldConnectionInfo(serverAddress)), PlanetSideEmpire.VS
)
)
)