1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-04-20 06:37:45 +00:00

Initial support for multiplayer

This is quite the architecture change, and I started working on the
first Tokio actor tutorial I could find. This actually works though,
and you can now chat between two characters on the server.

The next steps are to clean up my mess, and send actors over the wire.
This commit is contained in:
Joshua Goins 2025-03-30 16:41:06 -04:00
parent 7efbc5fd02
commit ae2a75e1af
3 changed files with 520 additions and 369 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,15 @@
use tokio::net::TcpStream; use std::{
net::SocketAddr,
sync::{
Arc, Mutex,
atomic::{AtomicUsize, Ordering},
},
};
use tokio::{net::TcpStream, sync::mpsc::Sender, task::JoinHandle};
use crate::{ use crate::{
common::{ObjectId, Position, timestamp_secs}, common::{GameData, ObjectId, Position, timestamp_secs},
opcodes::ServerZoneIpcType, opcodes::ServerZoneIpcType,
packet::{ packet::{
CompressionType, ConnectionType, PacketSegment, PacketState, SegmentType, parse_packet, CompressionType, ConnectionType, PacketSegment, PacketState, SegmentType, parse_packet,
@ -10,7 +18,7 @@ use crate::{
}; };
use super::{ use super::{
Actor, Event, Inventory, Item, LuaPlayer, StatusEffects, Zone, Actor, Event, Inventory, Item, LuaPlayer, StatusEffects, WorldDatabase, Zone,
ipc::{ ipc::{
ActorControlSelf, ActorSetPos, ClientZoneIpcSegment, ContainerInfo, ContainerType, ActorControlSelf, ActorSetPos, ClientZoneIpcSegment, ContainerInfo, ContainerType,
InitZone, ItemInfo, ServerZoneIpcData, ServerZoneIpcSegment, StatusEffect, InitZone, ItemInfo, ServerZoneIpcData, ServerZoneIpcSegment, StatusEffect,
@ -39,6 +47,68 @@ pub struct PlayerData {
pub zone_id: u16, pub zone_id: u16,
} }
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct ClientId(usize);
pub enum FromServer {
/// A chat message.
Message(String),
}
#[derive(Debug)]
pub struct ClientHandle {
pub id: ClientId,
pub ip: SocketAddr,
pub channel: Sender<FromServer>,
pub kill: JoinHandle<()>,
}
impl ClientHandle {
/// Send a message to this client actor. Will emit an error if sending does
/// not succeed immediately, as this means that forwarding messages to the
/// tcp connection cannot keep up.
pub fn send(&mut self, msg: FromServer) -> Result<(), std::io::Error> {
if self.channel.try_send(msg).is_err() {
Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"Can't keep up or dead",
))
} else {
Ok(())
}
}
/// Kill the actor.
pub fn kill(self) {
// run the destructor
drop(self);
}
}
pub enum ToServer {
NewClient(ClientHandle),
Message(ClientId, String),
FatalError(std::io::Error),
}
#[derive(Clone, Debug)]
pub struct ServerHandle {
pub chan: Sender<ToServer>,
pub next_id: Arc<AtomicUsize>,
}
impl ServerHandle {
pub async fn send(&mut self, msg: ToServer) {
if self.chan.send(msg).await.is_err() {
panic!("Main loop has shut down.");
}
}
pub fn next_id(&self) -> ClientId {
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
ClientId(id)
}
}
/// Represents a single connection between an instance of the client and the world server /// Represents a single connection between an instance of the client and the world server
pub struct ZoneConnection { pub struct ZoneConnection {
pub socket: TcpStream, pub socket: TcpStream,
@ -54,6 +124,14 @@ pub struct ZoneConnection {
pub event: Option<Event>, pub event: Option<Event>,
pub actors: Vec<Actor>, pub actors: Vec<Actor>,
pub ip: SocketAddr,
pub id: ClientId,
pub handle: ServerHandle,
pub database: Arc<WorldDatabase>,
pub lua: Arc<Mutex<mlua::Lua>>,
pub gamedata: Arc<Mutex<GameData>>,
} }
impl ZoneConnection { impl ZoneConnection {

View file

@ -7,7 +7,9 @@ mod chat_handler;
pub use chat_handler::ChatHandler; pub use chat_handler::ChatHandler;
mod connection; mod connection;
pub use connection::{PlayerData, ZoneConnection}; pub use connection::{
ClientHandle, ClientId, FromServer, PlayerData, ServerHandle, ToServer, ZoneConnection,
};
mod database; mod database;
pub use database::{CharacterData, WorldDatabase}; pub use database::{CharacterData, WorldDatabase};