/*
wearechat - generic multiplayer game with voip
Copyright (C) 2025 metamuffin
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, version 3 of the License only.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
pub mod network;
use anyhow::Result;
use clap::Parser;
use log::{debug, info, warn};
use network::ServerNetwork;
use std::{
collections::{BTreeSet, HashMap, HashSet},
fs::File,
io::{BufReader, BufWriter},
marker::PhantomData,
net::{IpAddr, SocketAddr},
path::PathBuf,
};
use weareshared::{
helper::ReadWrite,
packets::{Data, Object, Packet, Resource},
resources::{Prefab, PrefabIndex},
store::ResourceStore,
tree::SceneTree,
};
#[derive(Parser, Debug)]
struct Args {
#[arg(short, long, default_value = "::")]
bind_addr: IpAddr,
#[arg(short, long, default_value = "28555")]
port: u16,
}
struct State {
store: ResourceStore,
tree: SceneTree,
prefab_index: PrefabIndex,
prefab_index_pending: HashSet>,
prefab_index_have: HashSet>,
conn_object_ownership: HashMap>,
}
fn main() -> Result<()> {
env_logger::init_from_env("LOG");
let args = Args::parse();
let net = ServerNetwork::new(SocketAddr::new(args.bind_addr, args.port))?;
let mut state = State::new()?;
let pi_file = prefab_index_path();
if pi_file.exists() {
let mut pi_file = BufReader::new(File::open(pi_file)?);
state.prefab_index = PrefabIndex::read(&mut pi_file)?;
info!("prefab index loaded (len={})", state.prefab_index.0.len())
}
while let Ok((conn, packet)) = net.recv.recv() {
if let Err(e) = state.handle_packet(conn, packet, &net) {
warn!("state handler error: ({conn}) {e}");
}
}
Ok(())
}
impl State {
pub fn new() -> Result {
Ok(Self {
store: ResourceStore::new_env()?,
tree: SceneTree::default(),
prefab_index: PrefabIndex::default(),
prefab_index_pending: HashSet::new(),
prefab_index_have: HashSet::new(),
conn_object_ownership: HashMap::new(),
})
}
pub fn prime_client(&self, conn: u128, net: &ServerNetwork) -> Result<()> {
net.broadcast(
Packet::PrefabIndex(self.store.set(&self.prefab_index)?),
true,
);
for p in self.tree.prime_client() {
net.send(conn, p, true);
}
Ok(())
}
pub fn handle_packet(&mut self, conn: u128, packet: Packet, net: &ServerNetwork) -> Result<()> {
match packet {
Packet::Connect(_) => {
self.prime_client(conn, net)?;
}
Packet::Disconnect => {
if let Some(owned) = self.conn_object_ownership.remove(&conn) {
for o in owned {
debug!("removing conn owned object {o}");
self.tree.remove_recursive(o, &mut net.br());
}
}
}
Packet::RequestResource(resource) => {
if let Some(r) = self.store.get_raw(resource)? {
debug!("resource is cached");
net.send(conn, Packet::RespondResource(resource, Data(r)), true);
} else {
debug!("resource not cached, request is broadcast");
net.broadcast(Packet::RequestResource(resource), true);
}
}
Packet::RespondResource(_, data) => {
let resource = self.store.set_raw(&data.0)?;
net.broadcast(Packet::RespondResource(resource, data), true);
if self
.prefab_index_pending
.remove(&Resource(resource.0, PhantomData))
{
let resource = Resource(resource.0, PhantomData::);
if let Some(prefab) = self.store.get(resource.clone())? {
self.add_prefab_to_index(resource, net, prefab)?;
}
}
}
Packet::Add(object, resource) => {
self.conn_object_ownership
.entry(conn)
.or_default()
.insert(object);
self.tree.add(object, resource.clone());
net.broadcast(Packet::Add(object, resource), true);
}
Packet::Remove(object) => {
self.conn_object_ownership
.entry(conn)
.or_default()
.remove(&object);
self.tree.remove_reparent(object, &mut net.br());
net.broadcast(Packet::Remove(object), true);
}
Packet::Position(object, pos, rot) => {
self.tree.packet(&Packet::Position(object, pos, rot));
net.broadcast(Packet::Position(object, pos, rot), false);
}
Packet::Pose(object, vec) => {
self.tree.packet(&Packet::Pose(object, vec.clone()));
net.broadcast(Packet::Pose(object, vec), false);
}
Packet::Parent(parent, child) => {
self.tree.reparent(parent, child);
net.broadcast(Packet::Parent(parent, child), true);
}
Packet::Sound(object, vec) => {
net.broadcast(Packet::Sound(object, vec), true);
}
Packet::PrefabIndex(_) => {
// ok
}
Packet::AnnouncePrefab(resource) => {
if self.prefab_index_have.insert(resource.clone()) {
if let Some(prefab) = self.store.get(resource.clone())? {
self.add_prefab_to_index(resource, net, prefab)?;
} else {
self.prefab_index_pending.insert(resource.clone());
net.broadcast(
Packet::RequestResource(Resource(resource.0, PhantomData)),
true,
);
}
}
}
Packet::Chat(object, message) => {
info!("[CHAT] {}", message.0);
net.broadcast(Packet::Chat(object, message), true);
}
}
Ok(())
}
pub fn add_prefab_to_index(
&mut self,
resource: Resource,
net: &ServerNetwork,
prefab: Prefab,
) -> Result<()> {
if let Some(name) = prefab.name {
self.prefab_index.0.insert(name, resource);
self.prefab_index
.write(&mut BufWriter::new(File::create(prefab_index_path())?))?;
net.broadcast(
Packet::PrefabIndex(self.store.set(&self.prefab_index)?),
true,
);
}
Ok(())
}
}
pub fn prefab_index_path() -> PathBuf {
xdg::BaseDirectories::with_prefix("wearechat")
.unwrap()
.place_data_file("prefab_index")
.unwrap()
}