pub mod discovery;
pub mod hub;
pub mod names;
pub mod net;
pub mod patch;
pub mod protocol;
use std::collections::{BTreeMap, HashMap, VecDeque};
use image::RgbaImage;
use crate::raster::{self, IRect};
use patch::{Edit, Patch, Region};
use protocol::{FromHub, Kid, PeerId, Presence, TILE, Tile, ToHub};
#[derive(Debug, PartialEq)]
pub enum Received {
Nothing,
Picture(Region),
Kids,
}
pub struct Session {
pub me: PeerId,
pub host: PeerId,
pub kids: Vec<Kid>,
pub ask_first: bool,
pub waiting: Vec<Kid>,
confirmed: RgbaImage,
seq: u64,
pub base: RgbaImage,
pending: VecDeque<(u64, Edit)>,
next_id: u64,
outbox: Vec<ToHub>,
pub foreign: u64,
pub version: u64,
previews: BTreeMap<PeerId, HashMap<(u16, u16), Patch>>,
pub overlay: RgbaImage,
pub overlay_dirty: IRect,
sent: HashMap<(u16, u16), Patch>,
resend_all: bool,
last_presence: Option<Presence>,
pub pointers: BTreeMap<PeerId, (Presence, f64)>,
pub now: f64,
}
impl Session {
pub fn new(me: PeerId, host: PeerId, seq: u64, picture: RgbaImage, kids: Vec<Kid>) -> Self {
Self {
me,
host,
kids,
ask_first: false,
waiting: Vec::new(),
base: picture.clone(),
confirmed: picture,
seq,
pending: VecDeque::new(),
next_id: 1,
outbox: Vec::new(),
foreign: 0,
version: 0,
previews: BTreeMap::new(),
overlay: RgbaImage::new(1, 1),
overlay_dirty: IRect::EMPTY,
sent: HashMap::new(),
resend_all: false,
last_presence: None,
pointers: BTreeMap::new(),
now: 0.0,
}
.with_blank_overlay()
}
fn with_blank_overlay(mut self) -> Self {
self.clear_previews();
self
}
fn clear_previews(&mut self) {
let (w, h) = self.base.dimensions();
self.previews.clear();
self.overlay = RgbaImage::new(w, h);
self.overlay_dirty = IRect::of_image(&self.overlay);
}
pub fn rejoin(&mut self, me: PeerId, host: PeerId, seq: u64, picture: RgbaImage, kids: Vec<Kid>) {
(self.me, self.host, self.seq, self.kids) = (me, host, seq, kids);
self.waiting.clear();
self.confirmed = picture;
self.base = self.confirmed.clone();
for (_, edit) in &self.pending {
edit.apply(&mut self.base);
}
self.outbox = self.pending.iter().map(|(id, edit)| ToHub::Edit { id: *id, edit: edit.clone() }).collect();
self.sent.clear();
self.resend_all = true;
self.last_presence = None;
self.pointers.clear();
self.clear_previews();
self.version += 1;
}
pub fn has_previews(&self) -> bool {
self.previews.values().any(|tiles| !tiles.is_empty())
}
fn recompose(&mut self, (tx, ty): (u16, u16)) {
let r = Tile::rect(tx, ty).intersect(IRect::of_image(&self.overlay));
if r.is_empty() {
return;
}
raster::fill_rect(&mut self.overlay, r, [0, 0, 0, 0]);
for tiles in self.previews.values() {
if let Some(p) = tiles.get(&(tx, ty)) {
p.apply_within(&mut self.overlay, r);
}
}
self.overlay_dirty = self.overlay_dirty.union(r);
}
fn drop_preview(&mut self, kid: PeerId) {
if let Some(tiles) = self.previews.remove(&kid) {
for k in tiles.into_keys() {
self.recompose(k);
}
}
}
pub fn preview_tiles(&mut self, screen: &RgbaImage, changed: IRect) -> Vec<Tile> {
if screen.dimensions() != self.base.dimensions() {
return Vec::new();
}
let bounds = IRect::of_image(screen);
let area = if std::mem::take(&mut self.resend_all) { bounds } else { changed.intersect(bounds) };
if area.is_empty() {
return Vec::new();
}
let mut out = Vec::new();
for ty in area.y0 / TILE..=(area.y1 - 1) / TILE {
for tx in area.x0 / TILE..=(area.x1 - 1) / TILE {
let key = (tx as u16, ty as u16);
let now = Patch::diff(&self.base, screen, Tile::rect(key.0, key.1));
if now.as_ref() == self.sent.get(&key) {
continue;
}
match &now {
Some(p) => self.sent.insert(key, p.clone()),
None => self.sent.remove(&key),
};
out.push(Tile { tx: key.0, ty: key.1, patch: now });
}
}
out
}
pub fn presence(&mut self, p: Presence) -> Option<ToHub> {
(self.last_presence != Some(p)).then(|| {
self.last_presence = Some(p);
ToHub::Presence(p)
})
}
pub fn confirmed(&self) -> &RgbaImage {
&self.confirmed
}
#[cfg(test)]
pub fn unconfirmed(&self) -> usize {
self.pending.len()
}
pub fn commit(&mut self, edit: Edit) {
edit.apply(&mut self.base);
let id = self.next_id;
self.next_id += 1;
self.outbox.push(ToHub::Edit { id, edit: edit.clone() });
self.pending.push_back((id, edit));
self.version += 1;
self.sent.clear();
self.resend_all = true;
if self.overlay.dimensions() != self.base.dimensions() {
self.clear_previews();
}
}
pub fn take_outbox(&mut self) -> Vec<ToHub> {
std::mem::take(&mut self.outbox)
}
pub fn send(&mut self, msg: ToHub) {
self.outbox.push(msg);
}
pub fn replaces_picture(&self, msg: &FromHub) -> bool {
matches!(msg, FromHub::Sequenced { from, edit: Edit::Replace(_), .. } if *from != self.me)
}
pub fn receive(&mut self, msg: FromHub) -> Received {
match msg {
FromHub::Welcome { .. } | FromHub::Asking(_) | FromHub::Rejected(_) | FromHub::Ping => Received::Nothing,
FromHub::Kids(kids) | FromHub::KickedOut { kids, .. } => {
self.kids = kids;
let gone: Vec<PeerId> = self.previews.keys().filter(|id| !self.kids.iter().any(|k| k.id == **id)).copied().collect();
for id in gone {
self.drop_preview(id);
}
self.pointers.retain(|id, _| self.kids.iter().any(|k| k.id == *id));
Received::Kids
}
FromHub::Waiting(kids) => {
self.waiting = kids;
Received::Nothing
}
FromHub::AskFirst(on) => {
self.ask_first = on;
Received::Nothing
}
FromHub::Preview { from, tiles } => {
let theirs = self.previews.entry(from).or_default();
let keys: Vec<(u16, u16)> = tiles.iter().map(|t| (t.tx, t.ty)).collect();
for t in tiles {
match t.patch {
Some(p) => theirs.insert((t.tx, t.ty), p),
None => theirs.remove(&(t.tx, t.ty)),
};
}
for k in keys {
self.recompose(k);
}
Received::Nothing
}
FromHub::Presence { from, presence } => {
self.pointers.insert(from, (presence, self.now));
Received::Nothing
}
FromHub::Sequenced { seq, from, id, edit } => {
self.seq = seq;
let mine = from == self.me && self.pending.front().is_some_and(|(p, _)| *p == id);
let region = edit.region(&self.confirmed);
edit.apply(&mut self.confirmed);
if mine {
self.pending.pop_front();
return Received::Nothing;
}
self.foreign += 1;
self.version += 1;
self.drop_preview(from);
let region = self.rebuild(region);
if region == Region::Whole && self.overlay.dimensions() != self.base.dimensions() {
self.clear_previews();
self.sent.clear();
self.resend_all = true;
}
Received::Picture(region)
}
}
}
fn rebuild(&mut self, region: Region) -> Region {
let same_size = self.base.dimensions() == self.confirmed.dimensions();
let only_patches = self.pending.iter().all(|(_, e)| matches!(e, Edit::Patch(_)));
match region {
Region::Rect(r) if same_size && only_patches => {
raster::copy_rect(&mut self.base, &self.confirmed, r);
for (_, edit) in &self.pending {
if let Edit::Patch(p) = edit {
p.apply_within(&mut self.base, r);
}
}
Region::Rect(r)
}
_ => {
self.base = self.confirmed.clone();
for (_, edit) in &self.pending {
edit.apply(&mut self.base);
}
Region::Whole
}
}
}
}