Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zero copy serialization #305

Merged
merged 8 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[workspace]
members = ["daemon"]
members = [".", "daemon", "utils"]
default-members = [".", "daemon"]

[package]
name = "swww"
version = "0.9.5-master"
authors = ["Leonardo Gibrowski Faé <[email protected]>"]
edition = "2021"
rust-version = "1.74"
rust-version = "1.75"

# Enable some optimizations in debug mode. Otherwise, it is a pain to test it
[profile.dev]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

### Dependencies:

- Up to date stable rustc compiler and cargo (specifically, MSRV is 1.74.0)
- Up to date stable rustc compiler and cargo (specifically, MSRV is 1.75.0)

To build, clone this repository and run:
```
Expand Down
17 changes: 12 additions & 5 deletions daemon/src/animations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::{

use utils::{
compression::Decompressor,
ipc::{self, Animation, Answer, BgImg, Img},
ipc::{self, Animation, Answer, BgImg, ImgReq},
};

use crate::wallpaper::{AnimationToken, Wallpaper};
Expand Down Expand Up @@ -36,7 +36,7 @@ impl Animator {
scope: &'a Scope<'b, '_>,
transition: &'b ipc::Transition,
img: &'b [u8],
path: &'b String,
path: &'b str,
dim: (u32, u32),
wallpapers: &'b mut Vec<Arc<Wallpaper>>,
) where
Expand Down Expand Up @@ -68,7 +68,7 @@ impl Animator {
pub(super) fn transition(
&mut self,
transition: ipc::Transition,
imgs: Box<[Img]>,
imgs: Box<[ImgReq]>,
animations: Option<Box<[Animation]>>,
mut wallpapers: Vec<Vec<Arc<Wallpaper>>>,
) -> Answer {
Expand All @@ -78,10 +78,17 @@ impl Animator {
.name("animation spawner".to_string())
.spawn(move || {
thread::scope(|s| {
for (Img { img, path, dim, .. }, wallpapers) in
for (ImgReq { img, path, dim, .. }, wallpapers) in
imgs.iter().zip(wallpapers.iter_mut())
{
Self::spawn_transition_thread(s, &transition, img, path, *dim, wallpapers);
Self::spawn_transition_thread(
s,
&transition,
img.bytes(),
path.str(),
*dim,
wallpapers,
);
}
});
drop(imgs);
Expand Down
22 changes: 11 additions & 11 deletions daemon/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ use wayland_client::{
};

use utils::ipc::{
connect_to_socket, get_socket_path, read_socket, Answer, BgInfo, ImageRequest, PixelFormat,
Request, Scale,
connect_to_socket, get_socket_path, read_socket, Answer, BgInfo, ImageReq, MmappedStr,
PixelFormat, RequestRecv, RequestSend, Scale,
};

use animations::Animator;
Expand Down Expand Up @@ -363,9 +363,9 @@ impl Daemon {
return;
}
};
let request = Request::receive(bytes);
let request = RequestRecv::receive(bytes);
let answer = match request {
Request::Clear(clear) => {
RequestRecv::Clear(clear) => {
let wallpapers = self.find_wallpapers_by_names(&clear.outputs);
std::thread::Builder::new()
.stack_size(1 << 15)
Expand All @@ -386,17 +386,17 @@ impl Daemon {
.unwrap(); // builder only failed if the name contains null bytes
Answer::Ok
}
Request::Ping => Answer::Ping(
RequestRecv::Ping => Answer::Ping(
self.wallpapers
.iter()
.all(|w| w.configured.load(std::sync::atomic::Ordering::Acquire)),
),
Request::Kill => {
RequestRecv::Kill => {
exit_daemon();
Answer::Ok
}
Request::Query => Answer::Info(self.wallpapers_info()),
Request::Img(ImageRequest {
RequestRecv::Query => Answer::Info(self.wallpapers_info()),
RequestRecv::Img(ImageReq {
transition,
imgs,
outputs,
Expand Down Expand Up @@ -426,11 +426,11 @@ impl Daemon {
.collect()
}

fn find_wallpapers_by_names(&self, names: &[String]) -> Vec<Arc<Wallpaper>> {
fn find_wallpapers_by_names(&self, names: &[MmappedStr]) -> Vec<Arc<Wallpaper>> {
self.wallpapers
.iter()
.filter_map(|wallpaper| {
if names.is_empty() || names.iter().any(|n| wallpaper.has_name(n)) {
if names.is_empty() || names.iter().any(|n| wallpaper.has_name(n.str())) {
return Some(Arc::clone(wallpaper));
}
None
Expand Down Expand Up @@ -864,7 +864,7 @@ pub fn is_daemon_running(addr: &PathBuf) -> Result<bool, String> {
Err(_) => return Ok(false),
};

Request::Ping.send(&sock)?;
RequestSend::Ping.send(&sock)?;
let answer = Answer::receive(read_socket(&sock)?);
match answer {
Answer::Ping(_) => Ok(true),
Expand Down
30 changes: 15 additions & 15 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{path::PathBuf, process::Stdio, time::Duration};

use utils::{
cache,
ipc::{self, connect_to_socket, get_socket_path, read_socket, Answer, Request},
ipc::{self, connect_to_socket, get_socket_path, read_socket, Answer, RequestSend},
};

mod imgproc;
Expand Down Expand Up @@ -61,7 +61,7 @@ fn main() -> Result<(), String> {

loop {
let socket = connect_to_socket(&get_socket_path(), 5, 100)?;
Request::Ping.send(&socket)?;
RequestSend::Ping.send(&socket)?;
let bytes = read_socket(&socket)?;
let answer = Answer::receive(bytes);
if let Answer::Ping(configured) = answer {
Expand Down Expand Up @@ -114,18 +114,19 @@ fn process_swww_args(args: &Swww) -> Result<(), String> {
Ok(())
}

fn make_request(args: &Swww) -> Result<Option<Request>, String> {
fn make_request(args: &Swww) -> Result<Option<RequestSend>, String> {
match args {
Swww::Clear(c) => {
let (format, _, _) = get_format_dims_and_outputs(&[])?;
let mut color = c.color;
if format.must_swap_r_and_b_channels() {
color.swap(0, 2);
}
Ok(Some(Request::Clear(ipc::Clear {
let clear = ipc::ClearSend {
color,
outputs: split_cmdline_outputs(&c.outputs),
})))
};
Ok(Some(RequestSend::Clear(clear.create_request())))
}
Swww::Restore(restore) => {
let requested_outputs = split_cmdline_outputs(&restore.outputs);
Expand All @@ -137,18 +138,17 @@ fn make_request(args: &Swww) -> Result<Option<Request>, String> {
let requested_outputs = split_cmdline_outputs(&img.outputs);
let (format, dims, outputs) = get_format_dims_and_outputs(&requested_outputs)?;
let imgbuf = ImgBuf::new(&img.path)?;
Ok(Some(Request::Img(make_img_request(
img, &imgbuf, &dims, format, &outputs,
)?)))
let img_request = make_img_request(img, &imgbuf, &dims, format, &outputs)?;
Ok(Some(RequestSend::Img(img_request)))
}
Swww::Init { no_cache, .. } => {
if !*no_cache {
restore_from_cache(&[])?;
}
Ok(None)
}
Swww::Kill => Ok(Some(Request::Kill)),
Swww::Query => Ok(Some(Request::Query)),
Swww::Kill => Ok(Some(RequestSend::Kill)),
Swww::Query => Ok(Some(RequestSend::Query)),
}
}

Expand All @@ -158,7 +158,7 @@ fn make_img_request(
dims: &[(u32, u32)],
pixel_format: ipc::PixelFormat,
outputs: &[Vec<String>],
) -> Result<ipc::ImageRequest, String> {
) -> Result<ipc::Mmap, String> {
let img_raw = imgbuf.decode(pixel_format)?;
let transition = make_transition(img);
let mut img_req_builder = ipc::ImageRequestBuilder::new(transition);
Expand Down Expand Up @@ -212,13 +212,13 @@ fn make_img_request(
};

img_req_builder.push(
ipc::Img {
ipc::ImgSend {
img,
path,
dim,
format: pixel_format,
},
outputs.to_owned().into_boxed_slice(),
outputs,
animation,
);
}
Expand All @@ -235,7 +235,7 @@ fn get_format_dims_and_outputs(
let mut imgs: Vec<ipc::BgImg> = Vec::new();

let socket = connect_to_socket(&get_socket_path(), 5, 100)?;
Request::Query.send(&socket)?;
RequestSend::Query.send(&socket)?;
let bytes = read_socket(&socket)?;
drop(socket);
let answer = Answer::receive(bytes);
Expand Down Expand Up @@ -316,7 +316,7 @@ fn is_daemon_running() -> Result<bool, String> {
Err(_) => return Ok(false),
};

Request::Ping.send(&socket)?;
RequestSend::Ping.send(&socket)?;
let answer = Answer::receive(read_socket(&socket)?);
match answer {
Answer::Ping(_) => Ok(true),
Expand Down
2 changes: 1 addition & 1 deletion utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ edition = "2021"
license-file = "../LICENSE"

[dependencies]
rustix = { version = "0.38", default-features = false, features = [ "net", "shm", "mm" ] }
rustix = { version = "0.38", default-features = false, features = [ "std", "net", "shm", "mm", "param" ] }

[build-dependencies]
pkg-config = "0.3"
Expand Down
16 changes: 7 additions & 9 deletions utils/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::{
path::{Path, PathBuf},
};

use crate::ipc::{Animation, PixelFormat};
use crate::ipc::{Animation, Mmap, PixelFormat};

pub(crate) fn store(output_name: &str, img_path: &str) -> io::Result<()> {
let mut filepath = cache_dir()?;
Expand All @@ -19,7 +19,7 @@ pub(crate) fn store(output_name: &str, img_path: &str) -> io::Result<()> {
}

pub(crate) fn store_animation_frames(
animation: &Animation,
animation: &[u8],
path: &Path,
dimensions: (u32, u32),
pixel_format: PixelFormat,
Expand All @@ -28,11 +28,8 @@ pub(crate) fn store_animation_frames(
let mut filepath = cache_dir()?;
filepath.push(&filename);

let mut bytes = Vec::new();
animation.serialize(&mut bytes);

if !filepath.is_file() {
File::create(filepath)?.write_all(&bytes)
File::create(filepath)?.write_all(animation)
} else {
Ok(())
}
Expand All @@ -52,10 +49,11 @@ pub fn load_animation_frames(

for entry in read_dir.into_iter().flatten() {
if entry.path() == filepath {
let mut buf = Vec::new();
File::open(&filepath)?.read_to_end(&mut buf)?;
let fd = File::open(&filepath)?.into();
let len = rustix::fs::seek(&fd, rustix::fs::SeekFrom::End(0))?;
let mmap = Mmap::from_fd(fd, len as usize);

match std::panic::catch_unwind(|| Animation::deserialize(&buf)) {
match std::panic::catch_unwind(|| Animation::deserialize(&mmap, mmap.slice())) {
Ok((frames, _)) => return Ok(Some(frames)),
Err(e) => eprintln!("Error loading animation frames: {e:?}"),
}
Expand Down
42 changes: 29 additions & 13 deletions utils/src/compression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use comp::pack_bytes;
use decomp::{unpack_bytes_3channels, unpack_bytes_4channels};
use std::ffi::{c_char, c_int};

use crate::ipc::PixelFormat;
use crate::ipc::{ImageRequestBuilder, Mmap, MmappedBytes, PixelFormat};
mod comp;
mod cpu;
mod decomp;
Expand Down Expand Up @@ -42,9 +42,14 @@ extern "C" {
fn LZ4_compressBound(input_size: c_int) -> c_int;
}

enum Inner {
Boxed(Box<[u8]>),
Mmapped(MmappedBytes),
}

/// This struct represents the cached difference between the previous frame and the next
pub struct BitPack {
inner: Box<[u8]>,
inner: Inner,
/// This field will ensure we won't ever try to unpack the images on a buffer of the wrong size,
/// which ultimately is what allows us to use unsafe in the unpack_bytes function
expected_buf_size: u32,
Expand All @@ -53,24 +58,25 @@ pub struct BitPack {
}

impl BitPack {
pub(crate) fn serialize(&self, buf: &mut Vec<u8>) {
pub(crate) fn serialize(&self, buf: &mut ImageRequestBuilder) {
let Self {
inner,
expected_buf_size,
compressed_size,
..
} = self;
buf.extend((inner.len() as u32).to_ne_bytes());
buf.extend((expected_buf_size).to_ne_bytes());
buf.extend((compressed_size).to_ne_bytes());
buf.extend(inner.iter());
buf.extend(&(self.bytes().len() as u32).to_ne_bytes());
buf.extend(&(expected_buf_size).to_ne_bytes());
buf.extend(&(compressed_size).to_ne_bytes());
buf.extend(self.bytes());
}

pub(crate) fn deserialize(bytes: &[u8]) -> (Self, usize) {
#[must_use]
pub(crate) fn deserialize(map: &Mmap, bytes: &[u8]) -> (Self, usize) {
assert!(bytes.len() > 12);
let len = u32::from_ne_bytes(bytes[0..4].try_into().unwrap()) as usize;
let expected_buf_size = u32::from_ne_bytes(bytes[4..8].try_into().unwrap());
let compressed_size = i32::from_ne_bytes(bytes[8..12].try_into().unwrap());
let inner = bytes[12..12 + len].into();
let inner = Inner::Mmapped(MmappedBytes::new_with_len(map, &bytes[12..12 + len], len));
(
Self {
inner,
Expand All @@ -80,6 +86,15 @@ impl BitPack {
12 + len,
)
}

#[inline]
#[must_use]
fn bytes(&self) -> &[u8] {
match &self.inner {
Inner::Boxed(b) => b.as_ref(),
Inner::Mmapped(m) => m.bytes(),
}
}
}

/// Struct responsible for compressing our data. We use it to cache vector extensions that might
Expand Down Expand Up @@ -156,7 +171,7 @@ impl Compressor {
};

Some(BitPack {
inner: v.into_boxed_slice(),
inner: Inner::Boxed(v.into_boxed_slice()),
expected_buf_size,
compressed_size: self.buf.len() as i32,
})
Expand Down Expand Up @@ -240,10 +255,11 @@ impl Decompressor {
// SAFETY: errors will never happen because BitPacked is *always* only produced
// with correct lz4 compression, and ptr has the necessary capacity
let size = unsafe {
let bytes = bitpack.bytes();
LZ4_decompress_safe(
bitpack.inner.as_ptr() as _,
bytes.as_ptr() as _,
self.ptr.as_ptr() as _,
bitpack.inner.len() as c_int,
bytes.len() as c_int,
bitpack.compressed_size as c_int,
)
};
Expand Down
Loading
Loading