diff options
author | Joel Kronqvist <joel.kronqvist@iki.fi> | 2024-11-04 21:38:50 +0200 |
---|---|---|
committer | Joel Kronqvist <joel.kronqvist@iki.fi> | 2024-11-04 22:03:40 +0200 |
commit | ae82027a9bd4e75582f9499d4006b18c29a4129c (patch) | |
tree | 92d23012158647d31fbdc0e6cdae7854ee773cbc /src/main/scala/Server/Client.scala | |
download | scalevalapokalypsi-ae82027a9bd4e75582f9499d4006b18c29a4129c.tar.gz scalevalapokalypsi-ae82027a9bd4e75582f9499d4006b18c29a4129c.zip |
Added basic TCP networking for the server.
The client's networking is still very experimental and
the actual protocol is not yet specified for either side.
Diffstat (limited to 'src/main/scala/Server/Client.scala')
-rw-r--r-- | src/main/scala/Server/Client.scala | 80 |
1 files changed, 80 insertions, 0 deletions
diff --git a/src/main/scala/Server/Client.scala b/src/main/scala/Server/Client.scala new file mode 100644 index 0000000..c7ff075 --- /dev/null +++ b/src/main/scala/Server/Client.scala @@ -0,0 +1,80 @@ +package o1game.Server + +import java.net.Socket +import scala.math.min +import o1game.constants.* + +object Client: + def parseClient(data: String, socket: Socket): Client = + Client(socket, Some(GameCharacter(data, Vector()))) + +class Client(val socket: Socket, val character: Option[GameCharacter]): + private var incompleteMessage: Array[Byte] = + Array.fill(MAX_MSG_SIZE)(0.toByte) + private var incompleteMessageIndex = 0 + + /** Calculates the amount of bytes available for future incoming messages + */ + def spaceAvailable: Int = MAX_MSG_SIZE - incompleteMessageIndex + + /** Sets `data` as received for the client. + * + * @return false means there was not enough space to receive the message + */ + def receiveData(data: Vector[Byte]): Boolean = + for i <- 0 until min(data.length, spaceAvailable) do + this.incompleteMessage(this.incompleteMessageIndex + i) = data(i) + this.incompleteMessageIndex += data.length + this.incompleteMessageIndex = + min(this.incompleteMessageIndex, MAX_MSG_SIZE) + data.length < spaceAvailable + + /** Returns one line of data if there are any line breaks. + * Removes the parsed data from the message buffering area. + */ + private def nextLine(): Option[String] = + val nextLF = this.incompleteMessage.indexOf(LF) + if nextLF != -1 then + val message = this.incompleteMessage.take(nextLF) + val rest = this.incompleteMessage.drop(nextLF + 1) + this.incompleteMessage = rest ++ Array.fill(nextLF + 1)(0.toByte) + // TODO: the conversion may probably be exploited to crash the server + Some(String(message)) + else + None + + /** Causes the client to take the actions it has received + */ + def executeActions(): Unit = + LazyList.continually(this.nextLine()) + .takeWhile(_.isDefined) + .flatten + .foreach(s => println(s"`$this` executing `$s`")) + + +class Clients(maxClients: Int): + private val clients: Array[Option[Client]] = Array.fill(maxClients)(None) + + /** Adds `client` to this collection of clients. + * + * @param client the Client to add + * @return true if there was room for the client + * i.e. fewer clients than `maxClients`, false otherwise + */ + def addClient(client: Client): Boolean = + val i = this.clients.indexOf(None) + if i == -1 then + false + else + this.clients(i) = Some(client) + true + + def allClients: Iterable[Client] = clients.toVector.flatten + def foreach(f: Client => Any): Unit = this.clients.flatten.foreach(f) + def removeNonSatisfying(f: Client => Boolean): Unit = + for i <- this.clients.indices do + this.clients(i) match + case Some(c) => + if !f(c) then + this.clients(i) = None + case None =>
\ No newline at end of file |