1
0
Fork 0
forked from wry/wry

refactor: split cargo workspace

This commit is contained in:
kossLAN 2026-06-05 11:56:21 -04:00
parent 5db14936e7
commit 1c21bd1259
695 changed files with 32023 additions and 44964 deletions

View file

@ -0,0 +1,10 @@
[package]
name = "jay-algorithms"
version = "0.4.0"
edition = "2024"
license = "GPL-3.0-only"
description = "Internal dependency of the Jay compositor"
repository = "https://github.com/mahkoh/jay"
[dependencies]
smallvec = { version = "1.8.0", features = ["const_generics", "const_new", "union"] }

View file

@ -0,0 +1,10 @@
#![allow(
clippy::mem_replace_with_default,
clippy::comparison_chain,
clippy::collapsible_else_if,
clippy::needless_lifetimes
)]
pub mod qoi;
pub mod rect;
mod windows;

View file

@ -0,0 +1,91 @@
pub fn xrgb8888_encode_qoi(bytes: &[u8], width: u32, height: u32, stride: u32) -> Vec<u8> {
const OP_RGB: u8 = 0b1111_1110;
const OP_INDEX: u8 = 0b0000_0000;
const OP_DIFF: u8 = 0b0100_0000;
const OP_LUMA: u8 = 0b1000_0000;
const OP_RUN: u8 = 0b1100_0000;
let mut res = vec![];
let width_bytes_be = width.to_be_bytes();
let height_bytes_be = height.to_be_bytes();
let header = [
b'q',
b'o',
b'i',
b'f',
width_bytes_be[0],
width_bytes_be[1],
width_bytes_be[2],
width_bytes_be[3],
height_bytes_be[0],
height_bytes_be[1],
height_bytes_be[2],
height_bytes_be[3],
3,
0,
];
res.extend_from_slice(&header);
let mut prev_pixel = [0, 0, 0, 0xff];
let mut array = [[0; 4]; 64];
let mut run_length = 0;
for line in bytes.chunks_exact(stride as _) {
for &pixel in array_chunks::<_, 4>(&line[..(width * 4) as _]) {
let pixel = [pixel[2], pixel[1], pixel[0], 0xff];
if pixel == prev_pixel {
run_length += 1;
if run_length == 62 {
res.push(OP_RUN | (run_length - 1));
run_length = 0;
}
continue;
}
if run_length > 0 {
res.push(OP_RUN | (run_length - 1));
run_length = 0;
}
let prev = prev_pixel;
prev_pixel = pixel;
let index = {
let sum = 0u8
.wrapping_add(pixel[0].wrapping_mul(3))
.wrapping_add(pixel[1].wrapping_mul(5))
.wrapping_add(pixel[2].wrapping_mul(7))
.wrapping_add(255u8.wrapping_mul(11));
sum & 63
};
if array[index as usize] == pixel {
res.push(OP_INDEX | index);
continue;
}
array[index as usize] = pixel;
let dr = pixel[0].wrapping_sub(prev[0]);
let dg = pixel[1].wrapping_sub(prev[1]);
let db = pixel[2].wrapping_sub(prev[2]);
let dr_2 = dr.wrapping_add(2);
let dg_2 = dg.wrapping_add(2);
let db_2 = db.wrapping_add(2);
if dr_2 | dg_2 | db_2 | 3 == 3 {
res.push(OP_DIFF | (dr_2 << 4) | (dg_2 << 2) | db_2);
continue;
}
let dr_dg_8 = dr.wrapping_sub(dg).wrapping_add(8);
let db_dg_8 = db.wrapping_sub(dg).wrapping_add(8);
let dg_32 = dg.wrapping_add(32);
if (dg_32 | 63 == 63) && (dr_dg_8 | db_dg_8 | 15 == 15) {
res.extend_from_slice(&[OP_LUMA | dg_32, (dr_dg_8 << 4) | db_dg_8]);
continue;
}
res.extend_from_slice(&[OP_RGB, pixel[0], pixel[1], pixel[2]]);
}
}
if run_length > 0 {
res.push(OP_RUN | (run_length - 1));
}
res.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 1]);
res
}
fn array_chunks<T, const N: usize>(slice: &[T]) -> &[[T; N]] {
let len = slice.len() / N;
unsafe { std::slice::from_raw_parts(slice.as_ptr() as _, len) }
}

View file

@ -0,0 +1,76 @@
pub mod region;
use {
smallvec::SmallVec,
std::fmt::{Debug, Formatter},
};
pub trait Tag: Copy + Eq + Ord + Debug + Default + Sized {
const IS_SIGNIFICANT: bool;
fn constrain(self) -> Self;
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Default)]
pub struct NoTag;
impl Tag for NoTag {
const IS_SIGNIFICANT: bool = false;
#[inline(always)]
fn constrain(self) -> Self {
Self
}
}
impl Tag for u32 {
const IS_SIGNIFICANT: bool = true;
#[inline(always)]
fn constrain(self) -> Self {
self & 1
}
}
#[derive(Copy, Clone, Eq, PartialEq, Default)]
pub struct RectRaw<T = NoTag>
where
T: Tag,
{
pub x1: i32,
pub y1: i32,
pub x2: i32,
pub y2: i32,
pub tag: T,
}
impl<T> Debug for RectRaw<T>
where
T: Tag,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_struct("Rect");
debug
.field("x1", &self.x1)
.field("y1", &self.y1)
.field("x2", &self.x2)
.field("y2", &self.y2)
.field("width", &(self.x2 - self.x1))
.field("height", &(self.y2 - self.y1));
if T::IS_SIGNIFICANT {
debug.field("tag", &self.tag);
}
debug.finish()
}
}
impl<T> RectRaw<T>
where
T: Tag,
{
fn is_empty(&self) -> bool {
self.x1 == self.x2 || self.y1 == self.y2
}
}
type Container<T = NoTag> = SmallVec<[RectRaw<T>; 1]>;

View file

@ -0,0 +1,687 @@
use {
crate::{
rect::{Container, NoTag, RectRaw, Tag},
windows::WindowsExt,
},
std::{cmp::Ordering, collections::BinaryHeap, marker::PhantomData, mem, ops::Deref},
};
pub fn union(left: &Container, right: &Container) -> Container {
op::<_, _, _, Union>(left, right)
}
pub fn subtract(left: &Container, right: &Container) -> Container {
op::<_, _, _, Subtract>(left, right)
}
pub fn intersect(left: &Container, right: &Container) -> Container {
op::<_, _, _, Intersect<NoTag>>(left, right)
}
pub fn intersect_tagged(left: &Container<u32>, right: &Container) -> Container<u32> {
op::<_, _, _, Intersect<u32>>(left, right)
}
struct Bands<'a, T>
where
T: Tag,
{
rects: &'a [RectRaw<T>],
}
#[derive(Copy, Clone)]
struct Band<'a, T>
where
T: Tag,
{
rects: &'a [RectRaw<T>],
y1: i32,
y2: i32,
}
impl<'a, T> Band<'a, T>
where
T: Tag,
{
fn can_merge_with(&self, next: &Band<'_, T>) -> bool {
next.rects.len() == self.rects.len()
&& next.y1 == self.y2
&& next
.rects
.iter()
.zip(self.rects.iter())
.all(|(a, b)| (a.x1, a.x2, a.tag) == (b.x1, b.x2, b.tag))
}
}
impl<'a, T> Iterator for Bands<'a, T>
where
T: Tag,
{
type Item = Band<'a, T>;
fn next(&mut self) -> Option<Self::Item> {
if self.rects.is_empty() {
return None;
}
let y1 = self.rects[0].y1;
let y2 = self.rects[0].y2;
for (pos, rect) in self.rects[1..].iter().enumerate() {
if rect.y1 != y1 {
let (res, rects) = self.rects.split_at(pos + 1);
self.rects = rects;
return Some(Band { rects: res, y1, y2 });
}
}
Some(Band {
rects: mem::replace(&mut self.rects, &[]),
y1,
y2,
})
}
}
#[inline]
pub fn extents<T>(a: &[RectRaw<T>]) -> RectRaw
where
T: Tag,
{
let mut a = a.iter();
let mut res = match a.next() {
Some(a) => *a,
_ => return RectRaw::default(),
};
for a in a {
res.x1 = res.x1.min(a.x1);
res.y1 = res.y1.min(a.y1);
res.x2 = res.x2.max(a.x2);
res.y2 = res.y2.max(a.y2);
}
RectRaw {
x1: res.x1,
y1: res.y1,
x2: res.x2,
y2: res.y2,
tag: NoTag,
}
}
fn op<T1, T2, T3, O: Op<T1, T2, T3>>(a: &[RectRaw<T2>], b: &[RectRaw<T3>]) -> Container<T1>
where
T1: Tag,
T2: Tag,
T3: Tag,
{
let mut res = Container::new();
let mut prev_band_y2 = 0;
let mut prev_band_start = 0;
let mut cur_band_start;
let mut a_bands = Bands { rects: a };
let mut b_bands = Bands { rects: b };
let mut a_opt = a_bands.next();
let mut b_opt = b_bands.next();
macro_rules! fixup_new_band {
($y1:expr, $y2:expr) => {{
if prev_band_y2 != $y1 || !coalesce(&mut res, prev_band_start, cur_band_start, $y2) {
prev_band_start = cur_band_start;
}
prev_band_y2 = $y2;
}};
}
macro_rules! append_nonoverlapping {
($append_opt:expr, $map:ident, $a:expr, $a_opt:expr, $a_bands:expr, $b:expr) => {{
if $append_opt {
let y2 = $a.y2.min($b.y1);
cur_band_start = res.len();
res.reserve($a.rects.len());
for rect in $a.rects {
res.push(RectRaw {
x1: rect.x1,
y1: $a.y1,
x2: rect.x2,
y2,
tag: O::$map(rect.tag),
});
}
fixup_new_band!($a.y1, y2);
}
if $a.y2 <= $b.y1 {
$a_opt = $a_bands.next();
} else {
$a.y1 = $b.y1;
}
}};
}
while let (Some(a), Some(b)) = (&mut a_opt, &mut b_opt) {
if a.y1 < b.y1 {
append_nonoverlapping!(O::APPEND_NON_A, map_t2_to_t1, a, a_opt, a_bands, b);
} else if b.y1 < a.y1 {
append_nonoverlapping!(O::APPEND_NON_B, map_t3_to_t1, b, b_opt, b_bands, a);
} else {
let y2 = a.y2.min(b.y2);
cur_band_start = res.len();
O::handle_band(&mut res, a.rects, b.rects, a.y1, y2);
if res.len() > cur_band_start {
fixup_new_band!(a.y1, y2);
}
if a.y2 == y2 {
a_opt = a_bands.next();
} else {
a.y1 = y2;
}
if b.y2 == y2 {
b_opt = b_bands.next();
} else {
b.y1 = y2;
}
}
}
macro_rules! push_trailing {
($a_opt:expr, $a_bands:expr, $map:ident) => {{
while let Some(a) = $a_opt {
cur_band_start = res.len();
res.reserve(a.rects.len());
for rect in a.rects {
res.push(RectRaw {
x1: rect.x1,
y1: a.y1,
x2: rect.x2,
y2: a.y2,
tag: O::$map(rect.tag),
});
}
fixup_new_band!(a.y1, a.y2);
$a_opt = $a_bands.next();
}
}};
}
if O::APPEND_NON_A {
push_trailing!(a_opt, a_bands, map_t2_to_t1);
}
if O::APPEND_NON_B {
push_trailing!(b_opt, b_bands, map_t3_to_t1);
}
res.shrink_to_fit();
res
}
fn coalesce<T>(new: &mut Container<T>, a: usize, b: usize, y2: i32) -> bool
where
T: Tag,
{
if new.len() - b != b - a {
return false;
}
let slice_a = &new[a..b];
let slice_b = &new[b..];
for (a, b) in slice_a.iter().zip(slice_b.iter()) {
if (a.x1, a.x2, a.tag) != (b.x1, b.x2, b.tag) {
return false;
}
}
for rect in &mut new[a..b] {
rect.y2 = y2;
}
new.truncate(b);
true
}
trait Op<T1, T2, T3>
where
T1: Tag,
T2: Tag,
T3: Tag,
{
const APPEND_NON_A: bool;
const APPEND_NON_B: bool;
fn handle_band(new: &mut Container<T1>, a: &[RectRaw<T2>], b: &[RectRaw<T3>], y1: i32, y2: i32);
fn map_t2_to_t1(tag: T2) -> T1;
fn map_t3_to_t1(tag: T3) -> T1;
}
struct Union;
impl Op<NoTag, NoTag, NoTag> for Union {
const APPEND_NON_A: bool = true;
const APPEND_NON_B: bool = true;
fn handle_band(new: &mut Container, mut a: &[RectRaw], mut b: &[RectRaw], y1: i32, y2: i32) {
let mut x1;
let mut x2;
macro_rules! push {
() => {
new.push(RectRaw {
x1,
y1,
x2,
y2,
tag: NoTag,
});
};
}
macro_rules! merge {
($r:expr) => {
if $r.x1 <= x2 {
if $r.x2 > x2 {
x2 = $r.x2;
}
} else {
push!();
x1 = $r.x1;
x2 = $r.x2;
}
};
}
if a[0].x1 < b[0].x1 {
x1 = a[0].x1;
x2 = a[0].x2;
a = &a[1..];
} else {
x1 = b[0].x1;
x2 = b[0].x2;
b = &b[1..];
}
let mut a_iter = a.iter();
let mut b_iter = b.iter();
let mut a_opt = a_iter.next();
let mut b_opt = b_iter.next();
while let (Some(a), Some(b)) = (a_opt, b_opt) {
if a.x1 < b.x1 {
merge!(a);
a_opt = a_iter.next();
} else {
merge!(b);
b_opt = b_iter.next();
}
}
while let Some(a) = a_opt {
merge!(a);
a_opt = a_iter.next();
}
while let Some(b) = b_opt {
merge!(b);
b_opt = b_iter.next();
}
push!();
}
#[inline(always)]
fn map_t2_to_t1(_tag: NoTag) -> NoTag {
NoTag
}
#[inline(always)]
fn map_t3_to_t1(_tag: NoTag) -> NoTag {
NoTag
}
}
struct Subtract;
impl Op<NoTag, NoTag, NoTag> for Subtract {
const APPEND_NON_A: bool = true;
const APPEND_NON_B: bool = false;
fn handle_band(new: &mut Container, a: &[RectRaw], b: &[RectRaw], y1: i32, y2: i32) {
let mut x1;
let mut x2;
macro_rules! push {
($x2:expr) => {
new.push(RectRaw {
x1,
y1,
x2: $x2,
y2,
tag: NoTag,
});
};
}
let mut a_iter = a.iter();
let mut b_iter = b.iter();
macro_rules! pull {
() => {
match a_iter.next() {
Some(n) => {
x1 = n.x1;
x2 = n.x2;
}
_ => return,
}
};
}
pull!();
let mut b_opt = b_iter.next();
while let Some(b) = b_opt {
if b.x2 <= x1 {
b_opt = b_iter.next();
} else if b.x1 >= x2 {
push!(x2);
pull!();
} else {
if b.x1 > x1 {
push!(b.x1);
}
if b.x2 < x2 {
x1 = b.x2;
} else {
pull!();
}
}
}
loop {
push!(x2);
pull!();
}
}
#[inline(always)]
fn map_t2_to_t1(_tag: NoTag) -> NoTag {
NoTag
}
#[inline(always)]
fn map_t3_to_t1(_tag: NoTag) -> NoTag {
NoTag
}
}
struct Intersect<T>(PhantomData<T>);
impl<T> Op<T, T, NoTag> for Intersect<T>
where
T: Tag,
{
const APPEND_NON_A: bool = false;
const APPEND_NON_B: bool = false;
fn handle_band(new: &mut Container<T>, a: &[RectRaw<T>], b: &[RectRaw], y1: i32, y2: i32) {
let mut x1;
let mut x2;
let mut tag;
macro_rules! push {
($x2:expr) => {
new.push(RectRaw {
x1,
y1,
x2: $x2,
y2,
tag,
});
};
}
let mut a_iter = a.iter();
let mut b_iter = b.iter();
macro_rules! pull {
() => {
match a_iter.next() {
Some(n) => {
x1 = n.x1;
x2 = n.x2;
tag = n.tag;
}
_ => return,
}
};
}
pull!();
let mut b_opt = b_iter.next();
while let Some(b) = b_opt {
if b.x2 <= x1 {
b_opt = b_iter.next();
} else if b.x1 >= x2 {
pull!();
} else {
x1 = x1.max(b.x1);
if x2 <= b.x2 {
push!(x2);
pull!();
} else {
push!(b.x2);
b_opt = b_iter.next();
}
}
}
}
#[inline(always)]
fn map_t2_to_t1(_tag: T) -> T {
unreachable!()
}
#[inline(always)]
fn map_t3_to_t1(_tag: NoTag) -> T {
unreachable!()
}
}
pub fn rects_to_bands(rects_tmp: &[RectRaw]) -> Container {
rects_to_bands_(rects_tmp)
}
pub fn rects_to_bands_tagged(rects_tmp: &[RectRaw<u32>]) -> Container<u32> {
rects_to_bands_(rects_tmp)
}
#[inline(always)]
fn rects_to_bands_<T>(rects_tmp: &[RectRaw<T>]) -> Container<T>
where
T: Tag,
{
#[derive(Copy, Clone)]
struct W<T>(RectRaw<T>)
where
T: Tag;
impl<T> Eq for W<T> where T: Tag {}
impl<T> PartialEq<Self> for W<T>
where
T: Tag,
{
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T> PartialOrd<Self> for W<T>
where
T: Tag,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T> Ord for W<T>
where
T: Tag,
{
fn cmp(&self, other: &Self) -> Ordering {
self.0
.y1
.cmp(&other.0.y1)
.then_with(|| self.0.x1.cmp(&other.0.x1))
.then_with(|| self.0.tag.cmp(&other.0.tag))
.reverse()
}
}
impl<T> Deref for W<T>
where
T: Tag,
{
type Target = RectRaw<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
let ys = {
let mut tmp: Vec<_> = rects_tmp.iter().flat_map(|r| [r.y1, r.y2]).collect();
tmp.sort_unstable();
let mut last = None;
let mut res = vec![];
for y in tmp {
if Some(y) != last {
last = Some(y);
res.push(y);
}
}
res
};
let mut rects = BinaryHeap::with_capacity(rects_tmp.len());
for rect in rects_tmp.iter().copied() {
if !rect.is_empty() {
rects.push(W(rect));
}
}
let mut res = Container::new();
for &[y1, y2] in ys.array_windows_ext::<2>() {
#[expect(clippy::never_loop)]
loop {
macro_rules! check_rect {
($rect:expr) => {{
if $rect.y1 != y1 {
break;
}
rects.pop();
if y2 < $rect.y2 {
$rect.0.y1 = y2;
rects.push($rect);
}
}};
}
if let Some(mut rect) = rects.peek().copied() {
check_rect!(rect);
let mut x1 = rect.x1;
let mut x2 = rect.x2;
let mut tag: T = rect.tag;
while let Some(mut rect) = rects.peek().copied() {
check_rect!(rect);
if rect.x1 > x2 || (rect.tag != tag && rect.x1 == x2) {
res.push(RectRaw {
x1,
x2,
y1,
y2,
tag: tag.constrain(),
});
x1 = rect.x1;
x2 = rect.x2;
tag = rect.tag;
} else {
if rect.tag == tag {
x2 = x2.max(rect.x2);
} else if rect.tag > tag {
if rect.x2 > x2 {
rect.0.x1 = x2;
rect.0.y1 = y1;
rect.0.y2 = y2;
rects.push(rect);
}
} else {
if x2 > rect.x2 {
rects.push(W(RectRaw {
x1: rect.x2,
y1,
x2,
y2,
tag,
}));
}
res.push(RectRaw {
x1,
y1,
x2: rect.x1,
y2,
tag: tag.constrain(),
});
x1 = rect.x1;
x2 = rect.x2;
tag = rect.tag;
}
}
}
res.push(RectRaw {
x1,
x2,
y1,
y2,
tag: tag.constrain(),
});
}
break;
}
}
let mut needs_merge = false;
let mut num_elements = res.len();
let mut bands = Bands { rects: &res }.peekable();
while let Some(band) = bands.next() {
let next = match bands.peek() {
Some(next) => next,
_ => break,
};
if band.can_merge_with(next) {
needs_merge = true;
num_elements -= band.rects.len();
}
}
if !needs_merge {
res.shrink_to_fit();
return res;
}
let mut merged = Container::with_capacity(num_elements);
let mut bands = Bands { rects: &res }.peekable();
while let Some(mut band) = bands.next() {
while let Some(next) = bands.peek() {
if band.can_merge_with(next) {
band.y2 = next.y2;
bands.next();
} else {
break;
}
}
for mut rect in band.rects.iter().copied() {
rect.y2 = band.y2;
merged.push(rect);
}
}
merged
}

View file

@ -0,0 +1,38 @@
pub trait WindowsExt<T> {
type Windows<'a, const N: usize>: Iterator<Item = &'a [T; N]>
where
Self: 'a,
T: 'a;
fn array_windows_ext<'a, const N: usize>(&'a self) -> Self::Windows<'a, N>;
}
impl<T> WindowsExt<T> for [T] {
type Windows<'a, const N: usize>
= WindowsIter<'a, T, N>
where
T: 'a;
fn array_windows_ext<'a, const N: usize>(&'a self) -> Self::Windows<'a, N> {
WindowsIter { slice: self }
}
}
pub struct WindowsIter<'a, T, const N: usize> {
slice: &'a [T],
}
impl<'a, T, const N: usize> Iterator for WindowsIter<'a, T, N> {
type Item = &'a [T; N];
fn next(&mut self) -> Option<Self::Item> {
if self.slice.len() < N {
return None;
}
let res = unsafe { &*self.slice.as_ptr().cast::<[T; N]>() };
if N > 0 {
self.slice = &self.slice[1..];
}
Some(res)
}
}

View file

@ -0,0 +1,12 @@
[package]
name = "jay-allocator"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-formats = { version = "0.1.0", path = "../formats" }
jay-video-types = { version = "0.1.0", path = "../video-types" }
thiserror = "2.0.11"
uapi = "0.2.13"

View file

@ -0,0 +1,95 @@
use {
jay_formats::Format,
jay_video_types::{
Modifier,
dmabuf::{DmaBuf, DmaBufIds},
},
std::{
error::Error,
ops::{BitOr, BitOrAssign, Not},
rc::Rc,
},
thiserror::Error,
uapi::{OwnedFd, c},
};
#[derive(Debug, Error)]
#[error(transparent)]
pub struct AllocatorError(#[from] pub Box<dyn Error + Send>);
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct BufferUsage(u32);
impl BufferUsage {
pub fn none() -> Self {
Self(0)
}
pub fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
}
impl BitOr for BufferUsage {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
impl BitOrAssign for BufferUsage {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
impl Not for BufferUsage {
type Output = Self;
fn not(self) -> Self::Output {
Self(!self.0)
}
}
pub const BO_USE_SCANOUT: BufferUsage = BufferUsage(1 << 0);
pub const BO_USE_CURSOR: BufferUsage = BufferUsage(1 << 1);
pub const BO_USE_RENDERING: BufferUsage = BufferUsage(1 << 2);
pub const BO_USE_WRITE: BufferUsage = BufferUsage(1 << 3);
pub const BO_USE_LINEAR: BufferUsage = BufferUsage(1 << 4);
pub const BO_USE_PROTECTED: BufferUsage = BufferUsage(1 << 5);
pub trait Allocator {
fn drm(&self) -> Option<&dyn AllocatorDrm>;
fn create_bo(
&self,
dma_buf_ids: &DmaBufIds,
width: i32,
height: i32,
format: &'static Format,
modifiers: &[Modifier],
usage: BufferUsage,
) -> Result<Rc<dyn BufferObject>, AllocatorError>;
fn import_dmabuf(
&self,
dmabuf: &DmaBuf,
usage: BufferUsage,
) -> Result<Rc<dyn BufferObject>, AllocatorError>;
}
pub trait AllocatorDrm {
fn dev(&self) -> c::dev_t;
fn dup_render_fd(&self) -> Result<Rc<OwnedFd>, AllocatorError>;
}
pub trait BufferObject {
fn dmabuf(&self) -> &DmaBuf;
fn map_read(self: Rc<Self>) -> Result<Box<dyn MappedBuffer>, AllocatorError>;
fn map_write(self: Rc<Self>) -> Result<Box<dyn MappedBuffer>, AllocatorError>;
}
pub trait MappedBuffer {
unsafe fn data(&self) -> &[u8];
fn data_ptr(&self) -> *mut u8;
fn stride(&self) -> i32;
}

View file

@ -0,0 +1,14 @@
[package]
name = "jay-async-engine"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-time = { version = "0.1.0", path = "../time" }
jay-tracy = { version = "0.1.0", path = "../tracy" }
jay-utils = { version = "0.1.0", path = "../utils" }
[features]
it = []
tracy = ["jay-tracy/tracy"]

View file

@ -0,0 +1,288 @@
use {
crate::{AsyncEngine, Phase},
jay_tracy::ZoneName,
jay_utils::{
numcell::NumCell,
ptr_ext::{MutPtrExt, PtrExt},
},
std::{
cell::{Cell, UnsafeCell},
future::Future,
mem::ManuallyDrop,
pin::Pin,
ptr,
rc::Rc,
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
},
};
#[must_use]
pub struct SpawnedFuture<T: 'static> {
vtable: &'static SpawnedFutureVtable<T>,
data: *mut u8,
}
impl<T> Future for SpawnedFuture<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
unsafe { (self.vtable.poll)(self.data, cx) }
}
}
impl<T> Drop for SpawnedFuture<T> {
fn drop(&mut self) {
unsafe {
(self.vtable.drop)(self.data);
}
}
}
struct SpawnedFutureVTableProxy<T, F>(T, F);
impl<T: 'static, F: Future<Output = T>> SpawnedFutureVTableProxy<T, F> {
const VTABLE: &'static SpawnedFutureVtable<T> = &SpawnedFutureVtable {
poll: Self::poll,
drop: Self::drop,
};
unsafe fn poll(data: *mut u8, ctx: &mut Context<'_>) -> Poll<T> {
unsafe {
let task = (data as *const Task<T, F>).deref();
if &task.state & COMPLETED == 0 {
task.waker.set(Some(ctx.waker().clone()));
Poll::Pending
} else if &task.state & EMPTIED == 0 {
task.state.or_assign(EMPTIED);
Poll::Ready(ptr::read(&*task.data.get().deref().result))
} else {
panic!("Future polled after it has already been emptied");
}
}
}
unsafe fn drop(data: *mut u8) {
unsafe {
{
let task = (data as *const Task<T, F>).deref();
task.state.or_assign(CANCELLED);
if &task.state & RUNNING == 0 {
task.drop_data();
}
}
Task::<T, F>::dec_ref_count(data as _);
}
}
}
struct SpawnedFutureVtable<T> {
poll: unsafe fn(data: *mut u8, ctx: &mut Context<'_>) -> Poll<T>,
drop: unsafe fn(data: *mut u8),
}
union TaskData<T, F: Future<Output = T>> {
result: ManuallyDrop<T>,
future: ManuallyDrop<F>,
}
const RUNNING: u32 = 1;
const RUN_AGAIN: u32 = 2;
const COMPLETED: u32 = 4;
const EMPTIED: u32 = 8;
const CANCELLED: u32 = 16;
struct Task<T, F: Future<Output = T>> {
ref_count: NumCell<u64>,
phase: Phase,
state: NumCell<u32>,
data: UnsafeCell<TaskData<T, F>>,
waker: Cell<Option<Waker>>,
queue: Rc<AsyncEngine>,
#[cfg_attr(not(feature = "tracy"), expect(dead_code))]
zone: ZoneName,
}
pub(super) struct Runnable {
data: *const u8,
run: unsafe fn(data: *const u8, run: bool),
}
impl Runnable {
pub(super) fn run(self) {
let slf = ManuallyDrop::new(self);
unsafe {
(slf.run)(slf.data, true);
}
}
}
impl Drop for Runnable {
fn drop(&mut self) {
unsafe {
(self.run)(self.data, false);
}
}
}
impl AsyncEngine {
pub(super) fn spawn_<T, F: Future<Output = T>>(
self: &Rc<Self>,
#[cfg_attr(not(feature = "tracy"), expect(unused_variables))] name: &str,
phase: Phase,
f: F,
) -> SpawnedFuture<T> {
let f = Box::new(Task {
ref_count: NumCell::new(1),
phase,
state: NumCell::new(0),
data: UnsafeCell::new(TaskData {
future: ManuallyDrop::new(f),
}),
waker: Cell::new(None),
queue: self.clone(),
zone: jay_tracy::create_zone_name!("task:{}", name),
});
unsafe {
f.schedule_run();
}
let f = Box::into_raw(f);
SpawnedFuture {
vtable: SpawnedFutureVTableProxy::<T, F>::VTABLE,
data: f as _,
}
}
}
impl<T, F: Future<Output = T>> Task<T, F> {
const VTABLE: &'static RawWakerVTable = &RawWakerVTable::new(
Self::waker_clone,
Self::waker_wake,
Self::waker_wake_by_ref,
Self::waker_drop,
);
unsafe fn run_proxy(data: *const u8, run: bool) {
unsafe {
let task = data as *const Self;
if run {
task.deref().run();
} else {
Self::task_runnable_dropped(task);
}
Self::dec_ref_count(task);
}
}
#[cold]
unsafe fn task_runnable_dropped(task: *const Self) {
unsafe {
let task = task.deref();
task.state.and_assign(!RUNNING);
if task.state.get() & CANCELLED != 0 {
task.drop_data();
}
}
}
unsafe fn dec_ref_count(slf: *const Self) {
unsafe {
if slf.deref().ref_count.fetch_sub(1) == 1 {
drop(Box::from_raw(slf as *mut Self));
}
}
}
unsafe fn inc_ref_count(&self) {
self.ref_count.fetch_add(1);
}
unsafe fn waker_clone(data: *const ()) -> RawWaker {
unsafe {
let task = &mut *(data as *mut Self);
task.inc_ref_count();
RawWaker::new(data, Self::VTABLE)
}
}
unsafe fn waker_wake(data: *const ()) {
unsafe {
Self::waker_wake_by_ref(data);
Self::waker_drop(data);
}
}
unsafe fn waker_wake_by_ref(data: *const ()) {
unsafe {
(data as *const Self).deref().schedule_run();
}
}
unsafe fn waker_drop(data: *const ()) {
unsafe { Self::dec_ref_count(data as _) }
}
unsafe fn schedule_run(&self) {
unsafe {
if &self.state & (COMPLETED | CANCELLED) == 0 {
if &self.state & RUNNING == 0 {
self.state.or_assign(RUNNING);
self.inc_ref_count();
let data = self as *const _ as _;
self.queue.push(
Runnable {
data,
run: Self::run_proxy,
},
self.phase,
);
} else {
self.state.or_assign(RUN_AGAIN);
}
}
}
}
unsafe fn run(&self) {
unsafe {
if &self.state & CANCELLED == 0 {
let data = self.data.get().deref_mut();
self.inc_ref_count();
let raw_waker = RawWaker::new(self as *const _ as _, Self::VTABLE);
let waker = Waker::from_raw(raw_waker);
let mut ctx = Context::from_waker(&waker);
let poll = {
jay_tracy::dynamic_zone!(self.zone);
Pin::new_unchecked(&mut *data.future).poll(&mut ctx)
};
if let Poll::Ready(d) = poll {
ManuallyDrop::drop(&mut data.future);
ptr::write(&mut data.result, ManuallyDrop::new(d));
self.state.or_assign(COMPLETED);
if let Some(waker) = self.waker.take() {
waker.wake();
}
}
}
self.state.and_assign(!RUNNING);
if &self.state & CANCELLED != 0 {
self.drop_data();
} else if &self.state & RUN_AGAIN != 0 {
self.state.and_assign(!RUN_AGAIN);
self.schedule_run()
}
}
}
unsafe fn drop_data(&self) {
unsafe {
if &self.state & COMPLETED == 0 {
ManuallyDrop::drop(&mut self.data.get().deref_mut().future);
} else if &self.state & EMPTIED == 0 {
ManuallyDrop::drop(&mut self.data.get().deref_mut().result);
}
}
}
}

View file

@ -0,0 +1,27 @@
use {
crate::AsyncEngine,
std::{
future::Future,
pin::Pin,
rc::Rc,
task::{Context, Poll},
},
};
pub struct Yield {
pub(super) iteration: u64,
pub(super) queue: Rc<AsyncEngine>,
}
impl Future for Yield {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.queue.iteration() > self.iteration {
Poll::Ready(())
} else {
self.queue.push_yield(cx.waker().clone());
Poll::Pending
}
}
}

View file

@ -0,0 +1,169 @@
mod ae_task;
mod ae_yield;
mod run_toplevel;
pub use {ae_task::SpawnedFuture, ae_yield::Yield, run_toplevel::*};
use {
crate::ae_task::Runnable,
jay_time::Time,
jay_utils::{array, numcell::NumCell, syncqueue::SyncQueue},
std::{
cell::{Cell, RefCell},
collections::VecDeque,
future::Future,
rc::Rc,
task::Waker,
},
};
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Phase {
EventHandling,
Layout,
PostLayout,
Present,
}
const NUM_PHASES: usize = 4;
pub struct AsyncEngine {
num_queued: NumCell<usize>,
queues: [SyncQueue<Runnable>; NUM_PHASES],
iteration: NumCell<u64>,
yields: SyncQueue<Waker>,
stash: RefCell<VecDeque<Runnable>>,
yield_stash: RefCell<VecDeque<Waker>>,
stopped: Cell<bool>,
now: Cell<Option<Time>>,
#[cfg(feature = "it")]
idle: Cell<Option<Waker>>,
}
impl AsyncEngine {
pub fn new() -> Rc<Self> {
Rc::new(Self {
num_queued: Default::default(),
queues: array::from_fn(|_| Default::default()),
iteration: Default::default(),
yields: Default::default(),
stash: Default::default(),
yield_stash: Default::default(),
stopped: Cell::new(false),
now: Default::default(),
#[cfg(feature = "it")]
idle: Default::default(),
})
}
pub fn stop(&self) {
self.stopped.set(true);
}
pub fn clear(&self) {
self.stash.borrow_mut().clear();
self.yield_stash.borrow_mut().clear();
self.yields.take();
for queue in &self.queues {
queue.take();
}
}
pub fn spawn<T, F: Future<Output = T> + 'static>(
self: &Rc<Self>,
name: &str,
f: F,
) -> SpawnedFuture<T> {
self.spawn_(name, Phase::EventHandling, f)
}
pub fn spawn2<T, F: Future<Output = T> + 'static>(
self: &Rc<Self>,
name: &str,
phase: Phase,
f: F,
) -> SpawnedFuture<T> {
self.spawn_(name, phase, f)
}
pub fn yield_now(self: &Rc<Self>) -> Yield {
Yield {
iteration: self.iteration(),
queue: self.clone(),
}
}
pub fn dispatch(&self) {
let mut stash = self.stash.borrow_mut();
let mut yield_stash = self.yield_stash.borrow_mut();
loop {
if self.num_queued.get() == 0 {
#[cfg(feature = "it")]
if let Some(idle) = self.idle.take() {
idle.wake();
continue;
}
break;
}
self.now.take();
let mut phase = 0;
while phase < NUM_PHASES {
self.queues[phase].swap(&mut *stash);
if stash.is_empty() {
phase += 1;
continue;
}
self.num_queued.fetch_sub(stash.len());
while let Some(runnable) = stash.pop_front() {
runnable.run();
if self.stopped.get() {
return;
}
}
}
self.iteration.fetch_add(1);
self.yields.swap(&mut *yield_stash);
while let Some(waker) = yield_stash.pop_front() {
waker.wake();
}
}
}
#[cfg(feature = "it")]
pub async fn idle(&self) {
use std::{future::poll_fn, task::Poll};
let mut register = true;
poll_fn(|ctx| {
if register {
self.idle.set(Some(ctx.waker().clone()));
register = false;
Poll::Pending
} else {
Poll::Ready(())
}
})
.await
}
fn push(&self, runnable: Runnable, phase: Phase) {
self.queues[phase as usize].push(runnable);
self.num_queued.fetch_add(1);
}
fn push_yield(&self, waker: Waker) {
self.yields.push(waker);
}
pub fn iteration(&self) -> u64 {
self.iteration.get()
}
pub fn now(&self) -> Time {
match self.now.get() {
Some(t) => t,
None => {
let now = Time::now_unchecked();
self.now.set(Some(now));
now
}
}
}
}

View file

@ -0,0 +1,44 @@
use {
crate::{AsyncEngine, SpawnedFuture},
jay_utils::queue::AsyncQueue,
std::rc::Rc,
};
pub struct RunToplevelFuture {
_future: SpawnedFuture<()>,
}
pub struct RunToplevel {
queue: AsyncQueue<Box<dyn FnOnce()>>,
}
impl RunToplevel {
pub fn install(eng: &Rc<AsyncEngine>) -> (RunToplevelFuture, Rc<RunToplevel>) {
let slf = Rc::new(RunToplevel {
queue: Default::default(),
});
let future = eng.spawn("run toplevel", {
let slf = slf.clone();
async move {
loop {
let f = slf.queue.pop().await;
f();
}
}
});
let future = RunToplevelFuture { _future: future };
(future, slf)
}
pub fn schedule<F: FnOnce() + 'static>(&self, f: F) {
self.schedule_dyn(Box::new(f));
}
pub fn clear(&self) {
self.queue.clear();
}
fn schedule_dyn(&self, f: Box<dyn FnOnce()>) {
self.queue.push(f);
}
}

12
crates/bufio/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "jay-bufio"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-io-uring = { version = "0.1.0", path = "../io-uring" }
jay-utils = { version = "0.1.0", path = "../utils" }
thiserror = "2.0.11"
uapi = "0.2.13"

183
crates/bufio/src/lib.rs Normal file
View file

@ -0,0 +1,183 @@
use {
jay_io_uring::{IoUring, IoUringError},
jay_utils::{
buf::{Buf, DynamicBuf},
queue::AsyncQueue,
stack::Stack,
},
std::{
collections::VecDeque,
mem::{self},
rc::Rc,
},
thiserror::Error,
uapi::{OwnedFd, c},
};
#[derive(Debug, Error)]
pub enum BufIoError {
#[error("Could not write to the socket")]
FlushError(#[source] IoUringError),
#[error("Could not read from the socket")]
ReadError(#[source] IoUringError),
#[error("The socket is closed")]
Closed,
}
pub struct BufIoMessage {
pub fds: Vec<Rc<OwnedFd>>,
pub buf: Buf,
}
struct MessageOffset {
msg: BufIoMessage,
offset: usize,
}
pub struct BufIo {
fd: Rc<OwnedFd>,
ring: Rc<IoUring>,
bufs: Stack<Buf>,
outgoing: AsyncQueue<BufIoMessage>,
}
pub struct BufIoIncoming {
bufio: Rc<BufIo>,
buf: Buf,
buf_start: usize,
buf_end: usize,
pub fds: VecDeque<Rc<OwnedFd>>,
}
struct Outgoing {
bufio: Rc<BufIo>,
msgs: VecDeque<MessageOffset>,
bufs: Vec<Buf>,
}
impl BufIo {
pub fn new(fd: &Rc<OwnedFd>, ring: &Rc<IoUring>) -> Self {
Self {
fd: fd.clone(),
ring: ring.clone(),
bufs: Default::default(),
outgoing: Default::default(),
}
}
pub fn shutdown(&self) {
let _ = uapi::shutdown(self.fd.raw(), c::SHUT_RDWR);
}
pub fn buf(&self) -> DynamicBuf {
let buf = self.bufs.pop().unwrap_or_default();
DynamicBuf::from_buf(buf)
}
pub fn send(&self, msg: BufIoMessage) {
self.outgoing.push(msg);
}
pub async fn outgoing(self: Rc<Self>) -> Result<(), BufIoError> {
let mut outgoing = Outgoing {
bufio: self,
msgs: Default::default(),
bufs: vec![],
};
outgoing.run().await
}
pub fn incoming(self: &Rc<Self>) -> BufIoIncoming {
BufIoIncoming {
bufio: self.clone(),
buf: Buf::new(4096),
buf_start: 0,
buf_end: 0,
fds: Default::default(),
}
}
}
impl BufIoIncoming {
pub async fn fill_msg_buf(
&mut self,
mut n: usize,
buf: &mut Vec<u8>,
) -> Result<(), BufIoError> {
while n > 0 {
if self.buf_start == self.buf_end {
self.buf_start = 0;
self.buf_end = 0;
let res = self
.bufio
.ring
.recvmsg(&self.bufio.fd, &mut [self.buf.clone()], &mut self.fds)
.await;
match res {
Ok(n) => self.buf_end = n,
Err(e) => return Err(BufIoError::ReadError(e)),
}
if self.buf_start == self.buf_end {
return Err(BufIoError::Closed);
}
}
let read = n.min(self.buf_end - self.buf_start);
let buf_start = self.buf_start;
buf.extend_from_slice(&self.buf[buf_start..buf_start + read]);
n -= read;
self.buf_start += read;
}
Ok(())
}
}
impl Outgoing {
async fn run(&mut self) -> Result<(), BufIoError> {
loop {
self.bufio.outgoing.non_empty().await;
if let Err(e) = self.try_flush().await {
return Err(BufIoError::FlushError(e));
}
}
}
async fn try_flush(&mut self) -> Result<(), IoUringError> {
loop {
while let Some(msg) = self.bufio.outgoing.try_pop() {
self.msgs.push_back(MessageOffset { msg, offset: 0 });
}
if self.msgs.is_empty() {
return Ok(());
}
let mut fds = Vec::new();
for msg in &mut self.msgs {
if msg.msg.fds.len() > 0 {
if fds.len() > 0 || self.bufs.len() > 0 {
break;
}
fds = mem::take(&mut msg.msg.fds);
}
self.bufs.push(msg.msg.buf.slice(msg.offset..));
}
let res = self
.bufio
.ring
.sendmsg(&self.bufio.fd, &mut self.bufs, fds, None)
.await;
self.bufs.clear();
let mut n = res?;
while n > 0 {
let len = self.msgs[0].msg.buf.len() - self.msgs[0].offset;
if n < len {
self.msgs[0].offset += n;
break;
}
n -= len;
let msg = self.msgs.pop_front().unwrap();
self.bufio.bufs.push(msg.msg.buf);
}
}
}
}

8
crates/bugs/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "jay-bugs"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
ahash = "0.8.7"

38
crates/bugs/src/lib.rs Normal file
View file

@ -0,0 +1,38 @@
use {ahash::AHashMap, std::sync::LazyLock};
static BUGS: LazyLock<AHashMap<&'static str, Bugs>> = LazyLock::new(|| {
let mut map = AHashMap::new();
map.insert(
"chromium",
Bugs {
respect_min_max_size: true,
..Default::default()
},
);
map.insert(
"Alacritty",
Bugs {
min_width: Some(100),
min_height: Some(100),
..Default::default()
},
);
map
});
pub fn get(app_id: &str) -> &'static Bugs {
BUGS.get(app_id).unwrap_or(&NONE)
}
pub static NONE: Bugs = Bugs {
respect_min_max_size: false,
min_width: None,
min_height: None,
};
#[derive(Default, Debug)]
pub struct Bugs {
pub respect_min_max_size: bool,
pub min_width: Option<i32>,
pub min_height: Option<i32>,
}

View file

@ -0,0 +1,18 @@
[package]
name = "jay-clientmem"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-cpu-worker = { version = "0.1.0", path = "../cpu-worker" }
jay-gfx-types = { version = "0.1.0", path = "../gfx-types" }
jay-tracy = { version = "0.1.0", path = "../tracy" }
jay-utils = { version = "0.1.0", path = "../utils" }
log = "0.4.20"
thiserror = "2.0.11"
uapi = "0.2.13"
[features]
tracy = ["jay-tracy/tracy"]

331
crates/clientmem/src/lib.rs Normal file
View file

@ -0,0 +1,331 @@
use {
jay_cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker},
jay_gfx_types::{ShmMemory, ShmMemoryBacking},
jay_utils::{
oserror::{OsError, OsErrorExt2},
page_size::page_size,
vec_ext::VecExt,
},
std::{
cell::Cell,
error::Error,
mem::{ManuallyDrop, MaybeUninit},
ops::Deref,
ptr,
rc::Rc,
sync::atomic::{Ordering, compiler_fence},
},
thiserror::Error,
uapi::{
OwnedFd, Pod,
c::{self, raise},
ftruncate,
},
};
#[derive(Copy, Clone, Debug)]
pub struct ClientMemClient<'a> {
pub comm: &'a str,
pub id: u64,
}
#[derive(Debug, Error)]
pub enum ClientMemError {
#[error("Could not install the sigbus handler")]
SigactionFailed(#[source] jay_utils::oserror::OsError),
#[error("A SIGBUS occurred while accessing mapped memory")]
Sigbus,
#[error("mmap failed")]
MmapFailed(#[source] jay_utils::oserror::OsError),
#[error("Length was not a multiple of the data element size")]
InvalidLength,
}
pub struct ClientMem {
fd: ManuallyDrop<Rc<OwnedFd>>,
failed: Cell<bool>,
sigbus_impossible: bool,
data: *const [Cell<u8>],
cpu: Option<Rc<CpuWorker>>,
}
#[derive(Clone)]
pub struct ClientMemOffset {
mem: Rc<ClientMem>,
offset: usize,
data: *const [Cell<u8>],
}
impl ClientMem {
pub fn new(
fd: &Rc<OwnedFd>,
len: usize,
read_only: bool,
client: Option<ClientMemClient<'_>>,
cpu: Option<&Rc<CpuWorker>>,
is_udmabuf: bool,
) -> Result<Self, ClientMemError> {
Self::new2(fd, len, read_only, client, cpu, c::MAP_SHARED, is_udmabuf)
}
pub fn new_private(
fd: &Rc<OwnedFd>,
len: usize,
read_only: bool,
client: Option<ClientMemClient<'_>>,
cpu: Option<&Rc<CpuWorker>>,
) -> Result<Self, ClientMemError> {
Self::new2(fd, len, read_only, client, cpu, c::MAP_PRIVATE, false)
}
fn new2(
fd: &Rc<OwnedFd>,
len: usize,
read_only: bool,
client: Option<ClientMemClient<'_>>,
cpu: Option<&Rc<CpuWorker>>,
flags: c::c_int,
is_udmabuf: bool,
) -> Result<Self, ClientMemError> {
let mut sigbus_impossible = is_udmabuf;
let mut real_size = None;
if !sigbus_impossible
&& let Ok(seals) = uapi::fcntl_get_seals(fd.raw())
&& seals & c::F_SEAL_SHRINK != 0
&& let Ok(stat) = uapi::fstat(fd.raw())
{
real_size = Some(stat.st_size as usize);
sigbus_impossible = stat.st_size as u64 >= len as u64;
}
if !sigbus_impossible && let Some(client) = client {
log::debug!(
"Client {} ({}) has created a shm buffer that might cause SIGBUS",
client.comm,
client.id,
);
}
let len = len.next_multiple_of(page_size());
if let Some(real_size) = real_size
&& real_size < len
{
let _ = ftruncate(fd.raw(), len as _);
}
let data = if len == 0 {
&mut [][..]
} else {
let prot = match read_only {
true => c::PROT_READ,
false => c::PROT_READ | c::PROT_WRITE,
};
unsafe {
let data = c::mmap64(ptr::null_mut(), len, prot, flags, fd.raw(), 0);
if data == c::MAP_FAILED {
return Err(ClientMemError::MmapFailed(OsError::default()));
}
std::slice::from_raw_parts_mut(data as *mut Cell<u8>, len)
}
};
Ok(Self {
fd: ManuallyDrop::new(fd.clone()),
failed: Cell::new(false),
sigbus_impossible,
data,
cpu: cpu.cloned(),
})
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn offset(self: &Rc<Self>, offset: usize, len: usize) -> ClientMemOffset {
let mem = unsafe { &*self.data };
ClientMemOffset {
mem: self.clone(),
offset,
data: &mem[offset..][..len],
}
}
pub fn fd(&self) -> &Rc<OwnedFd> {
&self.fd
}
pub fn is_sealed_memfd(&self) -> bool {
self.sigbus_impossible
}
}
impl ClientMemOffset {
pub fn pool(&self) -> &ClientMem {
&self.mem
}
pub fn offset(&self) -> usize {
self.offset
}
pub fn ptr(&self) -> *const [Cell<u8>] {
self.data
}
pub fn access<T, F: FnOnce(&[Cell<u8>]) -> T>(&self, f: F) -> Result<T, ClientMemError> {
unsafe {
if self.mem.sigbus_impossible {
return Ok(f(&*self.data));
}
let mref = MemRef {
mem: &*self.mem,
outer: MEM.get(),
};
MEM.set(&mref);
compiler_fence(Ordering::SeqCst);
let res = f(&*self.data);
MEM.set(mref.outer);
compiler_fence(Ordering::SeqCst);
match self.mem.failed.get() {
true => Err(ClientMemError::Sigbus),
_ => Ok(res),
}
}
}
pub fn read<T: Pod>(&self, dst: &mut Vec<T>) -> Result<(), ClientMemError> {
if self.data.len().checked_rem(std::mem::size_of::<T>()) != Some(0) {
return Err(ClientMemError::InvalidLength);
}
self.access(|v| {
let len_elements = v.len() / std::mem::size_of::<T>();
dst.reserve(len_elements);
let (_, unused) = dst.split_at_spare_mut_bytes_ext();
unused[..v.len()].copy_from_slice(uapi::as_maybe_uninit_bytes(v));
unsafe {
dst.set_len(dst.len() + len_elements);
}
})
}
}
impl Drop for ClientMem {
fn drop(&mut self) {
let fd = unsafe { ManuallyDrop::take(&mut self.fd) };
if let Some(cpu) = &self.cpu {
let pending = cpu.submit(Box::new(CloseMemWork {
fd: Rc::try_unwrap(fd).ok(),
data: self.data,
}));
pending.detach();
} else {
unsafe {
c::munmap(self.data as _, self.len());
}
}
}
}
struct MemRef {
mem: *const ClientMem,
outer: *const MemRef,
}
thread_local! {
static MEM: Cell<*const MemRef> = const { Cell::new(ptr::null()) };
}
unsafe fn kill() -> ! {
unsafe {
c::signal(c::SIGBUS, c::SIG_DFL);
raise(c::SIGBUS);
}
unreachable!();
}
unsafe extern "C" fn sigbus(sig: i32, info: &c::siginfo_t, _ucontext: *mut c::c_void) {
unsafe {
assert_eq!(sig, c::SIGBUS);
let mut memr_ptr = MEM.get();
while !memr_ptr.is_null() {
let memr = &*memr_ptr;
let mem = &*memr.mem;
let lo = mem.data as *mut u8 as usize;
let hi = lo + mem.len();
let fault_addr = info.si_addr() as usize;
if fault_addr < lo || fault_addr >= hi {
memr_ptr = memr.outer;
continue;
}
let res = c::mmap64(
lo as _,
hi - lo,
c::PROT_WRITE | c::PROT_READ,
c::MAP_ANONYMOUS | c::MAP_PRIVATE | c::MAP_FIXED,
-1,
0,
);
if res == c::MAP_FAILED {
kill();
}
mem.failed.set(true);
return;
}
kill();
}
}
pub fn init() -> Result<(), ClientMemError> {
unsafe {
let mut action: c::sigaction = MaybeUninit::zeroed().assume_init();
action.sa_sigaction =
sigbus as unsafe extern "C" fn(i32, &c::siginfo_t, *mut c::c_void) as _;
action.sa_flags = c::SA_NODEFER | c::SA_SIGINFO;
let res = c::sigaction(c::SIGBUS, &action, ptr::null_mut());
uapi::map_err!(res)
.map(drop)
.map_os_err(ClientMemError::SigactionFailed)
}
}
struct CloseMemWork {
fd: Option<OwnedFd>,
data: *const [Cell<u8>],
}
unsafe impl Send for CloseMemWork {}
impl CpuJob for CloseMemWork {
fn work(&mut self) -> &mut dyn CpuWork {
self
}
fn completed(self: Box<Self>) {
// nothing
}
}
impl CpuWork for CloseMemWork {
fn run(&mut self) -> Option<Box<dyn AsyncCpuWork>> {
jay_tracy::zone!("CloseMemWork");
self.fd.take();
unsafe {
c::munmap(self.data as _, self.data.len());
}
None
}
}
impl ShmMemory for ClientMemOffset {
fn len(&self) -> usize {
self.data.len()
}
fn safe_access(&self) -> ShmMemoryBacking {
match self.mem.is_sealed_memfd() {
true => ShmMemoryBacking::Ptr(self.data),
false => ShmMemoryBacking::Fd(self.mem.fd.deref().clone(), self.offset),
}
}
fn access(&self, f: &mut dyn FnMut(&[Cell<u8>])) -> Result<(), Box<dyn Error + Sync + Send>> {
self.access(f).map_err(|e| e.into())
}
}

8
crates/cmm/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "jay-cmm"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-utils = { version = "0.1.0", path = "../utils" }

View file

@ -0,0 +1,87 @@
use {
crate::{
cmm_eotf::Eotf,
cmm_luminance::{Luminance, TargetLuminance, white_balance},
cmm_manager::Shared,
cmm_primaries::{NamedPrimaries, Primaries},
cmm_render_intent::RenderIntent,
cmm_transform::{ColorMatrix, Local, Xyz, bradford_adjustment},
},
jay_utils::ordered_float::F64,
std::rc::Rc,
};
linear_ids!(LinearColorDescriptionIds, LinearColorDescriptionId, u64);
linear_ids!(ColorDescriptionIds, ColorDescriptionId, u64);
#[derive(Debug)]
pub struct LinearColorDescription {
pub id: LinearColorDescriptionId,
pub primaries: Primaries,
pub xyz_from_local: ColorMatrix<Xyz, Local>,
pub local_from_xyz: ColorMatrix<Local, Xyz>,
pub luminance: Luminance,
pub target_primaries: Primaries,
pub target_luminance: TargetLuminance,
pub max_cll: Option<F64>,
pub max_fall: Option<F64>,
pub(super) shared: Rc<Shared>,
}
#[derive(Debug)]
pub struct ColorDescription {
pub id: ColorDescriptionId,
pub linear: Rc<LinearColorDescription>,
pub named_primaries: Option<NamedPrimaries>,
pub eotf: Eotf,
pub(super) shared: Rc<Shared>,
}
impl LinearColorDescription {
pub fn color_transform(&self, target: &Self, intent: RenderIntent) -> ColorMatrix {
let mut mat = target.local_from_xyz;
if self.luminance != target.luminance {
mat *= white_balance(
&self.luminance,
&target.luminance,
target.primaries.wp,
intent,
);
}
if self.primaries.wp != target.primaries.wp && intent.bradford_adjustment() {
mat *= bradford_adjustment(self.primaries.wp, target.primaries.wp);
}
mat * self.xyz_from_local
}
pub fn embeds_into(&self, target: &Self) -> bool {
if self.id == target.id {
return true;
}
if self.primaries != target.primaries {
return false;
}
if self.luminance != target.luminance {
return false;
}
true
}
}
impl ColorDescription {
pub fn embeds_into(&self, target: &Self) -> bool {
self.eotf == target.eotf && self.linear.embeds_into(&target.linear)
}
}
impl Drop for LinearColorDescription {
fn drop(&mut self) {
self.shared.dead_linear.fetch_add(1);
}
}
impl Drop for ColorDescription {
fn drop(&mut self) {
self.shared.dead_complete.fetch_add(1);
}
}

View file

@ -0,0 +1,60 @@
use jay_utils::ordered_float::F32;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Eotf {
Linear,
St2084Pq,
Bt1886(F32),
Gamma22,
Gamma24,
Gamma28,
St240,
Log100,
Log316,
St428,
Pow(EotfPow),
CompoundPower24,
}
const MUL: u32 = 10_000;
const MUL_F32: f32 = MUL as f32;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct EotfPow(pub u32);
impl EotfPow {
pub const MIN: Self = Self(10_000);
pub const LINEAR: Self = Self(10_000);
pub const GAMMA22: Self = Self(22_000);
pub const GAMMA24: Self = Self(24_000);
pub const GAMMA28: Self = Self(28_000);
pub const MAX: Self = Self(100_000);
pub fn eotf_f32(self) -> f32 {
self.0 as f32 / MUL_F32
}
pub fn inv_eotf_f32(self) -> f32 {
MUL_F32 / self.0 as f32
}
}
pub fn bt1886_eotf_args(c: F32) -> [f32; 4] {
let c = c.0;
let gamma = 1.0 / 2.4;
let a1 = 1.0 / (1.0 - c);
let a2 = 1.0 - c.powf(gamma);
let a3 = c.powf(gamma);
let a4 = c;
[a1, a2, a3, a4]
}
pub fn bt1886_inv_eotf_args(c: F32) -> [f32; 4] {
let c = c.0;
let gamma = 1.0 / 2.4;
let a1 = 1.0 / (1.0 - c.powf(gamma));
let a2 = 1.0 - c;
let a3 = c;
let a4 = c.powf(gamma);
[a1, a2, a3, a4]
}

View file

@ -0,0 +1,94 @@
use crate::{
cmm_render_intent::RenderIntent,
cmm_transform::{ColorMatrix, Xyz},
};
use jay_utils::ordered_float::F64;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Luminance {
pub min: F64,
pub max: F64,
pub white: F64,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct TargetLuminance {
pub min: F64,
pub max: F64,
}
impl Luminance {
pub const SRGB: Self = Self {
min: F64(0.2),
max: F64(80.0),
white: F64(80.0),
};
pub const BT1886: Self = Self {
min: F64(0.01),
max: F64(100.0),
white: F64(100.0),
};
pub const ST2084_PQ: Self = Self {
min: F64(0.0),
max: F64(10000.0),
white: F64(203.0),
};
pub const HLG: Self = Self {
min: F64(0.005),
max: F64(1000.0),
white: F64(203.0),
};
pub const WINDOWS_SCRGB: Self = Self {
min: Self::ST2084_PQ.min,
max: Self::ST2084_PQ.max,
// This causes the white balance formula (with target ST2084_PQ) to simplify to
// `Y * 80 / 10000`, meaning that sRGB pure white maps to a luminance of
// 80 cd/m^2.
white: F64(Self::ST2084_PQ.white.0 / 80.0 * Self::ST2084_PQ.max.0),
};
}
impl Luminance {
pub fn to_target(&self) -> TargetLuminance {
TargetLuminance {
min: self.min,
max: self.max,
}
}
}
impl Default for Luminance {
fn default() -> Self {
Self::SRGB
}
}
#[expect(non_snake_case)]
pub fn white_balance(
from: &Luminance,
to: &Luminance,
w_to: (F64, F64),
intent: RenderIntent,
) -> ColorMatrix<Xyz, Xyz> {
let a = ((from.max - from.min) / (to.max - to.min) * (to.white - to.min)
/ (from.white - from.min))
.0;
let d = match intent.black_point_compensation() {
true => 0.0,
false => ((from.min - to.min) / (to.max - to.min)).0,
};
let s = a - d;
let (F64(x_to), F64(y_to)) = w_to;
let X_to = x_to / y_to;
let Y_to = 1.0;
let Z_to = (1.0 - x_to - y_to) / y_to;
ColorMatrix::new([
[s, 0.0, 0.0, d * X_to],
[0.0, s, 0.0, d * Y_to],
[0.0, 0.0, s, d * Z_to],
])
}

View file

@ -0,0 +1,251 @@
use {
crate::{
cmm_description::{
ColorDescription, ColorDescriptionIds, LinearColorDescription,
LinearColorDescriptionId, LinearColorDescriptionIds,
},
cmm_eotf::Eotf,
cmm_luminance::{Luminance, TargetLuminance},
cmm_primaries::{NamedPrimaries, Primaries},
},
jay_utils::{copyhashmap::CopyHashMap, numcell::NumCell, ordered_float::F64},
std::rc::{Rc, Weak},
};
pub struct ColorManager {
linear_ids: LinearColorDescriptionIds,
linear_descriptions: CopyHashMap<LinearDescriptionKey, Weak<LinearColorDescription>>,
complete_descriptions: CopyHashMap<CompleteDescriptionKey, Weak<ColorDescription>>,
shared: Rc<Shared>,
srgb_gamma22: Rc<ColorDescription>,
srgb_linear: Rc<ColorDescription>,
windows_scrgb: Rc<ColorDescription>,
}
#[derive(Debug, Default)]
pub(super) struct Shared {
pub(super) dead_linear: NumCell<usize>,
pub(super) dead_complete: NumCell<usize>,
pub(super) complete_ids: ColorDescriptionIds,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
struct LinearDescriptionKey {
primaries: Primaries,
luminance: Luminance,
target_primaries: Primaries,
target_luminance: TargetLuminance,
max_cll: Option<F64>,
max_fall: Option<F64>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
struct CompleteDescriptionKey {
linear: LinearColorDescriptionId,
named_primaries: Option<NamedPrimaries>,
eotf: Eotf,
}
impl ColorManager {
pub fn new() -> Rc<Self> {
let linear_ids = LinearColorDescriptionIds::default();
let linear_descriptions = CopyHashMap::default();
let complete_descriptions = CopyHashMap::default();
let shared = Rc::new(Shared::default());
let _ = shared.complete_ids.next();
let srgb_gamma22 = get_description(
&shared,
&linear_descriptions,
&complete_descriptions,
&linear_ids,
Some(NamedPrimaries::Srgb),
Primaries::SRGB,
Luminance::SRGB,
Eotf::Gamma22,
Primaries::SRGB,
Luminance::SRGB.to_target(),
None,
None,
);
let srgb_linear = get_description2(
&shared,
&srgb_gamma22.linear,
&complete_descriptions,
Some(NamedPrimaries::Srgb),
Eotf::Linear,
);
let windows_scrgb = get_description(
&shared,
&linear_descriptions,
&complete_descriptions,
&linear_ids,
Some(NamedPrimaries::Srgb),
Primaries::SRGB,
Luminance::WINDOWS_SCRGB,
Eotf::Linear,
Primaries::BT2020,
Luminance::ST2084_PQ.to_target(),
None,
None,
);
Rc::new(Self {
linear_ids,
linear_descriptions,
complete_descriptions,
shared,
srgb_gamma22,
srgb_linear,
windows_scrgb,
})
}
pub fn srgb_gamma22(&self) -> &Rc<ColorDescription> {
&self.srgb_gamma22
}
pub fn srgb_linear(&self) -> &Rc<ColorDescription> {
&self.srgb_linear
}
pub fn windows_scrgb(&self) -> &Rc<ColorDescription> {
&self.windows_scrgb
}
pub fn get_description(
self: &Rc<Self>,
named_primaries: Option<NamedPrimaries>,
primaries: Primaries,
luminance: Luminance,
eotf: Eotf,
target_primaries: Primaries,
target_luminance: TargetLuminance,
max_cll: Option<F64>,
max_fall: Option<F64>,
) -> Rc<ColorDescription> {
get_description(
&self.shared,
&self.linear_descriptions,
&self.complete_descriptions,
&self.linear_ids,
named_primaries,
primaries,
luminance,
eotf,
target_primaries,
target_luminance,
max_cll,
max_fall,
)
}
pub fn get_with_tf(
self: &Rc<Self>,
cd: &Rc<ColorDescription>,
eotf: Eotf,
) -> Rc<ColorDescription> {
get_description2(
&self.shared,
&cd.linear,
&self.complete_descriptions,
cd.named_primaries,
eotf,
)
}
}
fn get_description(
shared: &Rc<Shared>,
linear_descriptions: &CopyHashMap<LinearDescriptionKey, Weak<LinearColorDescription>>,
complete_descriptions: &CopyHashMap<CompleteDescriptionKey, Weak<ColorDescription>>,
linear_ids: &LinearColorDescriptionIds,
named_primaries: Option<NamedPrimaries>,
primaries: Primaries,
luminance: Luminance,
eotf: Eotf,
target_primaries: Primaries,
target_luminance: TargetLuminance,
max_cll: Option<F64>,
max_fall: Option<F64>,
) -> Rc<ColorDescription> {
macro_rules! gc {
($d:ident, $i:expr) => {
if $d.len() > 16 && $i.get() * 2 > $d.len() {
$d.lock().retain(|_, d| d.strong_count() > 0);
$i.set(0);
}
};
}
gc!(linear_descriptions, &shared.dead_linear);
gc!(complete_descriptions, &shared.dead_complete);
let key = LinearDescriptionKey {
primaries,
luminance,
target_primaries,
target_luminance,
max_cll,
max_fall,
};
if let Some(d) = linear_descriptions.get(&key) {
if let Some(d) = d.upgrade() {
return get_description2(shared, &d, complete_descriptions, named_primaries, eotf);
}
shared.dead_linear.fetch_sub(1);
}
let (xyz_from_local, local_from_xyz) = primaries.matrices();
let d = Rc::new(LinearColorDescription {
id: linear_ids.next(),
primaries,
xyz_from_local,
local_from_xyz,
luminance,
target_primaries,
target_luminance,
max_cll,
max_fall,
shared: shared.clone(),
});
linear_descriptions.set(key, Rc::downgrade(&d));
let key = CompleteDescriptionKey {
linear: d.id,
named_primaries,
eotf,
};
let d = Rc::new(ColorDescription {
id: shared.complete_ids.next(),
linear: d,
named_primaries,
eotf,
shared: shared.clone(),
});
complete_descriptions.set(key, Rc::downgrade(&d));
d
}
fn get_description2(
shared: &Rc<Shared>,
ld: &Rc<LinearColorDescription>,
complete_descriptions: &CopyHashMap<CompleteDescriptionKey, Weak<ColorDescription>>,
named_primaries: Option<NamedPrimaries>,
eotf: Eotf,
) -> Rc<ColorDescription> {
let key = CompleteDescriptionKey {
linear: ld.id,
named_primaries,
eotf,
};
if let Some(d) = complete_descriptions.get(&key) {
if let Some(d) = d.upgrade() {
return d;
}
shared.dead_complete.fetch_sub(1);
}
let d = Rc::new(ColorDescription {
id: shared.complete_ids.next(),
linear: ld.clone(),
named_primaries,
eotf,
shared: shared.clone(),
});
complete_descriptions.set(key, Rc::downgrade(&d));
d
}

View file

@ -0,0 +1,111 @@
use {jay_utils::ordered_float::F64, std::hash::Hash};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum NamedPrimaries {
Srgb,
PalM,
Pal,
Ntsc,
GenericFilm,
Bt2020,
Cie1931Xyz,
DciP3,
DisplayP3,
AdobeRgb,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Primaries {
pub r: (F64, F64),
pub g: (F64, F64),
pub b: (F64, F64),
pub wp: (F64, F64),
}
impl Primaries {
pub const SRGB: Self = Self {
r: (F64(0.64), F64(0.33)),
g: (F64(0.3), F64(0.6)),
b: (F64(0.15), F64(0.06)),
wp: (F64(0.3127), F64(0.3290)),
};
pub const PAL_M: Self = Self {
r: (F64(0.67), F64(0.33)),
g: (F64(0.21), F64(0.71)),
b: (F64(0.14), F64(0.08)),
wp: (F64(0.310), F64(0.316)),
};
pub const PAL: Self = Self {
r: (F64(0.64), F64(0.33)),
g: (F64(0.29), F64(0.60)),
b: (F64(0.15), F64(0.06)),
wp: (F64(0.3127), F64(0.3290)),
};
pub const NTSC: Self = Self {
r: (F64(0.630), F64(0.340)),
g: (F64(0.310), F64(0.595)),
b: (F64(0.155), F64(0.070)),
wp: (F64(0.3127), F64(0.3290)),
};
pub const GENERIC_FILM: Self = Self {
r: (F64(0.681), F64(0.319)),
g: (F64(0.243), F64(0.692)),
b: (F64(0.145), F64(0.049)),
wp: (F64(0.310), F64(0.316)),
};
pub const BT2020: Self = Self {
r: (F64(0.708), F64(0.292)),
g: (F64(0.170), F64(0.797)),
b: (F64(0.131), F64(0.046)),
wp: (F64(0.3127), F64(0.3290)),
};
pub const CIE1931_XYZ: Self = Self {
r: (F64(1.0), F64(0.0)),
g: (F64(0.0), F64(1.0)),
b: (F64(0.0), F64(0.0)),
wp: (F64(1.0 / 3.0), F64(1.0 / 3.0)),
};
pub const DCI_P3: Self = Self {
r: (F64(0.680), F64(0.320)),
g: (F64(0.265), F64(0.690)),
b: (F64(0.150), F64(0.060)),
wp: (F64(0.314), F64(0.351)),
};
pub const DISPLAY_P3: Self = Self {
r: (F64(0.680), F64(0.320)),
g: (F64(0.265), F64(0.690)),
b: (F64(0.150), F64(0.060)),
wp: (F64(0.3127), F64(0.3290)),
};
pub const ADOBE_RGB: Self = Self {
r: (F64(0.64), F64(0.33)),
g: (F64(0.21), F64(0.71)),
b: (F64(0.15), F64(0.06)),
wp: (F64(0.3127), F64(0.3290)),
};
}
impl NamedPrimaries {
pub const fn primaries(self) -> Primaries {
match self {
NamedPrimaries::Srgb => Primaries::SRGB,
NamedPrimaries::PalM => Primaries::PAL_M,
NamedPrimaries::Pal => Primaries::PAL,
NamedPrimaries::Ntsc => Primaries::NTSC,
NamedPrimaries::GenericFilm => Primaries::GENERIC_FILM,
NamedPrimaries::Bt2020 => Primaries::BT2020,
NamedPrimaries::Cie1931Xyz => Primaries::CIE1931_XYZ,
NamedPrimaries::DciP3 => Primaries::DCI_P3,
NamedPrimaries::DisplayP3 => Primaries::DISPLAY_P3,
NamedPrimaries::AdobeRgb => Primaries::ADOBE_RGB,
}
}
}

View file

@ -0,0 +1,28 @@
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
pub enum RenderIntent {
#[default]
Perceptual,
Relative,
RelativeBpc,
AbsoluteNoAdaptation,
}
impl RenderIntent {
pub fn black_point_compensation(self) -> bool {
match self {
RenderIntent::Perceptual => true,
RenderIntent::RelativeBpc => true,
RenderIntent::Relative => false,
RenderIntent::AbsoluteNoAdaptation => false,
}
}
pub fn bradford_adjustment(self) -> bool {
match self {
RenderIntent::Perceptual => true,
RenderIntent::RelativeBpc => true,
RenderIntent::Relative => true,
RenderIntent::AbsoluteNoAdaptation => false,
}
}
}

214
crates/cmm/src/cmm_tests.rs Normal file
View file

@ -0,0 +1,214 @@
mod matrices {
use {crate::cmm_primaries::Primaries, jay_utils::ordered_float::F64};
fn check(primaries: Primaries, expected: [[f64; 4]; 3]) {
let (ltg, gtl) = primaries.matrices();
println!("{:#?}", ltg);
assert!((ltg.0[0][0].0 - expected[0][0]).abs() < 0.001);
assert!((ltg.0[0][1].0 - expected[0][1]).abs() < 0.001);
assert!((ltg.0[0][2].0 - expected[0][2]).abs() < 0.001);
assert!((ltg.0[0][3].0 - expected[0][3]).abs() < 0.001);
assert!((ltg.0[1][0].0 - expected[1][0]).abs() < 0.001);
assert!((ltg.0[1][1].0 - expected[1][1]).abs() < 0.001);
assert!((ltg.0[1][2].0 - expected[1][2]).abs() < 0.001);
assert!((ltg.0[1][3].0 - expected[1][3]).abs() < 0.001);
assert!((ltg.0[2][0].0 - expected[2][0]).abs() < 0.001);
assert!((ltg.0[2][1].0 - expected[2][1]).abs() < 0.001);
assert!((ltg.0[2][2].0 - expected[2][2]).abs() < 0.001);
assert!((ltg.0[2][3].0 - expected[2][3]).abs() < 0.001);
let roundtrip = gtl * ltg;
assert!((roundtrip.0[0][0].0 - 1.0).abs() < 0.001);
assert!((roundtrip.0[0][1].0 - 0.0).abs() < 0.001);
assert!((roundtrip.0[0][2].0 - 0.0).abs() < 0.001);
assert!((roundtrip.0[0][3].0 - 0.0).abs() < 0.001);
assert!((roundtrip.0[1][0].0 - 0.0).abs() < 0.001);
assert!((roundtrip.0[1][1].0 - 1.0).abs() < 0.001);
assert!((roundtrip.0[1][2].0 - 0.0).abs() < 0.001);
assert!((roundtrip.0[1][3].0 - 0.0).abs() < 0.001);
assert!((roundtrip.0[2][0].0 - 0.0).abs() < 0.001);
assert!((roundtrip.0[2][1].0 - 0.0).abs() < 0.001);
assert!((roundtrip.0[2][2].0 - 1.0).abs() < 0.001);
assert!((roundtrip.0[2][3].0 - 0.0).abs() < 0.001);
}
#[test]
fn srgb() {
check(
Primaries::SRGB,
[
[0.4124564, 0.3575761, 0.1804375, 0.0],
[0.2126729, 0.7151522, 0.0721750, 0.0],
[0.0193339, 0.1191920, 0.9503041, 0.0],
],
);
}
#[test]
fn cie1931_xyz() {
check(
Primaries::CIE1931_XYZ,
[
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
],
);
}
#[test]
fn adobe_rgb() {
check(
Primaries::ADOBE_RGB,
[
[0.5767309, 0.1855540, 0.1881852, 0.0],
[0.2973769, 0.6273491, 0.0752741, 0.0],
[0.0270343, 0.0706872, 0.9911085, 0.0],
],
);
}
#[test]
fn apple_rgb() {
check(
Primaries {
r: (F64(0.625), F64(0.34)),
g: (F64(0.28), F64(0.595)),
b: (F64(0.155), F64(0.07)),
wp: (F64(0.31271), F64(0.32902)),
},
[
[0.4497288, 0.3162486, 0.1844926, 0.0],
[0.2446525, 0.6720283, 0.0833192, 0.0],
[0.0251848, 0.1411824, 0.9224628, 0.0],
],
);
}
#[test]
fn bt2020() {
check(
Primaries::BT2020,
[
[0.636958, 0.144617, 0.168881, 0.0],
[0.262700, 0.677998, 0.059302, 0.0],
[0.000000, 0.028073, 1.060985, 0.0],
],
);
}
#[test]
fn pal() {
check(
Primaries::PAL,
[
[0.4306190, 0.3415419, 0.1783091, 0.0],
[0.2220379, 0.7066384, 0.0713236, 0.0],
[0.0201853, 0.1295504, 0.9390944, 0.0],
],
);
}
#[test]
fn dci_p3() {
check(
Primaries::DCI_P3,
[
[0.445170, 0.277134, 0.172283, 0.0],
[0.209492, 0.721595, 0.068913, 0.0],
[-0.000000, 0.047061, 0.907355, 0.0],
],
);
}
#[test]
fn display_p3() {
check(
Primaries::DISPLAY_P3,
[
[0.486571, 0.265668, 0.198217, 0.0],
[0.228975, 0.691739, 0.079287, 0.0],
[-0.000000, 0.045113, 1.043944, 0.0],
],
);
}
}
mod transforms {
use crate::{
cmm_eotf::Eotf, cmm_luminance::Luminance, cmm_manager::ColorManager,
cmm_primaries::Primaries, cmm_render_intent::RenderIntent,
};
fn check(p1: Primaries, p2: Primaries, expected: [[f64; 4]; 3]) {
let manager = ColorManager::new();
let d = |p| {
manager.get_description(
None,
p,
Luminance::SRGB,
Eotf::Linear,
p,
Luminance::SRGB.to_target(),
None,
None,
)
};
let d1 = d(p1);
let d2 = d(p2);
let m = d1
.linear
.color_transform(&d2.linear, RenderIntent::Perceptual);
println!("{:#?}", m);
assert!((m.0[0][0].0 - expected[0][0]).abs() < 0.001);
assert!((m.0[0][1].0 - expected[0][1]).abs() < 0.001);
assert!((m.0[0][2].0 - expected[0][2]).abs() < 0.001);
assert!((m.0[0][3].0 - expected[0][3]).abs() < 0.001);
assert!((m.0[1][0].0 - expected[1][0]).abs() < 0.001);
assert!((m.0[1][1].0 - expected[1][1]).abs() < 0.001);
assert!((m.0[1][2].0 - expected[1][2]).abs() < 0.001);
assert!((m.0[1][3].0 - expected[1][3]).abs() < 0.001);
assert!((m.0[2][0].0 - expected[2][0]).abs() < 0.001);
assert!((m.0[2][1].0 - expected[2][1]).abs() < 0.001);
assert!((m.0[2][2].0 - expected[2][2]).abs() < 0.001);
assert!((m.0[2][3].0 - expected[2][3]).abs() < 0.001);
}
#[test]
fn srgb_to_bt2020() {
check(
Primaries::SRGB,
Primaries::BT2020,
[
[0.627404, 0.329283, 0.043313, 0.0],
[0.069097, 0.919540, 0.011362, 0.0],
[0.016391, 0.088013, 0.895595, 0.0],
],
)
}
#[test]
fn bt2020_to_srgb() {
check(
Primaries::BT2020,
Primaries::SRGB,
[
[1.660491, -0.587641, -0.072850, 0.0],
[-0.124550, 1.132900, -0.008349, 0.0],
[-0.018151, -0.100579, 1.118730, 0.0],
],
)
}
#[test]
fn srgb_to_dci_p3() {
check(
Primaries::SRGB,
Primaries::DCI_P3,
[
[0.868580, 0.128919, 0.002501, 0.0],
[0.034540, 0.961811, 0.003648, 0.0],
[0.016771, 0.071040, 0.912189, 0.0],
],
)
}
}

View file

@ -0,0 +1,235 @@
use {
crate::cmm_primaries::Primaries,
jay_utils::ordered_float::F64,
std::{
fmt,
fmt::{Debug, Formatter},
hash::{Hash, Hasher},
marker::PhantomData,
ops::{Mul, MulAssign},
},
};
pub struct ColorMatrix<To = Local, From = Local>(pub [[F64; 4]; 3], PhantomData<(To, From)>);
#[derive(Copy, Clone)]
pub struct Local;
#[derive(Copy, Clone)]
pub struct Xyz;
#[derive(Copy, Clone)]
pub struct Bradford;
impl<T, U> Copy for ColorMatrix<T, U> {}
impl<T, U> Clone for ColorMatrix<T, U> {
fn clone(&self) -> Self {
*self
}
}
impl<T, U> PartialEq<Self> for ColorMatrix<T, U> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T, U> Eq for ColorMatrix<T, U> {}
impl<T, U> Hash for ColorMatrix<T, U> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<T, U> Debug for ColorMatrix<T, U> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ColorMatrix")
.field(&format_matrix(&self.0))
.finish()
}
}
fn format_matrix<'a>(m: &'a [[F64; 4]; 3]) -> impl Debug + use<'a> {
fmt::from_fn(move |f| {
let iter = m
.iter()
.copied()
.chain(Some([F64(0.0), F64(0.0), F64(0.0), F64(1.0)]))
.enumerate();
if f.alternate() {
for (idx, row) in iter {
if idx > 0 {
f.write_str("\n")?;
}
write!(
f,
"{:7.4} {:7.4} {:7.4} {:7.4}",
row[0], row[1], row[2], row[3]
)?;
}
} else {
f.write_str("[")?;
for (idx, row) in iter {
if idx > 0 {
f.write_str(", ")?;
}
write!(
f,
"[{:.4}, {:.4}, {:.4}, {:.4}]",
row[0], row[1], row[2], row[3]
)?;
}
f.write_str("]")?;
}
Ok(())
})
}
impl<T, U, V> Mul<ColorMatrix<U, T>> for ColorMatrix<V, U> {
type Output = ColorMatrix<V, T>;
fn mul(self, rhs: ColorMatrix<U, T>) -> Self::Output {
let a = &self.0;
let b = &rhs.0;
macro_rules! mul {
($ar:expr, $bc:expr) => {
a[$ar][0] * b[0][$bc] + a[$ar][1] * b[1][$bc] + a[$ar][2] * b[2][$bc]
};
}
let m = [
[mul!(0, 0), mul!(0, 1), mul!(0, 2), mul!(0, 3) + a[0][3]],
[mul!(1, 0), mul!(1, 1), mul!(1, 2), mul!(1, 3) + a[1][3]],
[mul!(2, 0), mul!(2, 1), mul!(2, 2), mul!(2, 3) + a[2][3]],
];
ColorMatrix(m, PhantomData)
}
}
impl<U, V> MulAssign<ColorMatrix<U, U>> for ColorMatrix<V, U> {
fn mul_assign(&mut self, rhs: ColorMatrix<U, U>) {
*self = *self * rhs;
}
}
impl<T, U> Mul<[f64; 3]> for ColorMatrix<T, U> {
type Output = [f64; 3];
fn mul(self, rhs: [f64; 3]) -> Self::Output {
let a = &self.0;
macro_rules! mul {
($ar:expr) => {
a[$ar][0].0 * rhs[0] + a[$ar][1].0 * rhs[1] + a[$ar][2].0 * rhs[2]
};
}
[mul!(0), mul!(1), mul!(2)]
}
}
impl<T, U> ColorMatrix<T, U> {
pub const fn new(m: [[f64; 4]; 3]) -> Self {
let m = [
[F64(m[0][0]), F64(m[0][1]), F64(m[0][2]), F64(m[0][3])],
[F64(m[1][0]), F64(m[1][1]), F64(m[1][2]), F64(m[1][3])],
[F64(m[2][0]), F64(m[2][1]), F64(m[2][2]), F64(m[2][3])],
];
Self(m, PhantomData)
}
pub const fn to_f32(&self) -> [[f32; 4]; 4] {
let m = &self.0;
macro_rules! map {
($r:expr, $c:expr) => {
m[$r][$c].0 as f32
};
}
[
[map!(0, 0), map!(0, 1), map!(0, 2), map!(0, 3)],
[map!(1, 0), map!(1, 1), map!(1, 2), map!(1, 3)],
[map!(2, 0), map!(2, 1), map!(2, 2), map!(2, 3)],
[0.0, 0.0, 0.0, 1.0],
]
}
}
impl ColorMatrix<Bradford, Xyz> {
const BFD: Self = Self::new([
[0.8951, 0.2664, -0.1614, 0.0],
[-0.7502, 1.7135, 0.0367, 0.0],
[0.0389, -0.0685, 1.0296, 0.0],
]);
}
impl ColorMatrix<Xyz, Bradford> {
const BFD_INV: Self = Self::new([
[0.9870, -0.1471, 0.1600, 0.0],
[0.4323, 0.5184, 0.0493, 0.0],
[-0.0085, 0.04, 0.9685, 0.0],
]);
}
#[expect(non_snake_case)]
pub fn bradford_adjustment(w_from: (F64, F64), w_to: (F64, F64)) -> ColorMatrix<Xyz, Xyz> {
let (F64(x_from), F64(y_from)) = w_from;
let (F64(x_to), F64(y_to)) = w_to;
let X_from = x_from / y_from;
let Z_from = (1.0 - x_from - y_from) / y_from;
let X_to = x_to / y_to;
let Z_to = (1.0 - x_to - y_to) / y_to;
let [R_from, G_from, B_from] = ColorMatrix::BFD * [X_from, 1.0, Z_from];
let [R_to, G_to, B_to] = ColorMatrix::BFD * [X_to, 1.0, Z_to];
let adj = ColorMatrix::new([
[R_to / R_from, 0.0, 0.0, 0.0],
[0.0, G_to / G_from, 0.0, 0.0],
[0.0, 0.0, B_to / B_from, 0.0],
]);
ColorMatrix::BFD_INV * adj * ColorMatrix::BFD
}
impl Primaries {
#[expect(non_snake_case)]
pub const fn matrices(&self) -> (ColorMatrix<Xyz, Local>, ColorMatrix<Local, Xyz>) {
let (F64(xw), F64(yw)) = self.wp;
let Xw = xw / yw;
let Zw = (1.0 - xw - yw) / yw;
let (F64(xr), F64(yr)) = self.r;
let (F64(xg), F64(yg)) = self.g;
let (F64(xb), F64(yb)) = self.b;
let zr = 1.0 - xr - yr;
let zg = 1.0 - xg - yg;
let zb = 1.0 - xb - yb;
let srx = yg * zb - zg * yb;
let sry = zg * xb - xg * zb;
let srz = xg * yb - yg * xb;
let sgx = zr * yb - yr * zb;
let sgz = yr * xb - xr * yb;
let sgy = xr * zb - zr * xb;
let sbx = yr * zg - zr * yg;
let sby = zr * xg - xr * zg;
let sbz = xr * yg - yr * xg;
let det = srz + sgz + sbz;
let sr = srx * Xw + sry + srz * Zw;
let sg = sgx * Xw + sgy + sgz * Zw;
let sb = sbx * Xw + sby + sbz * Zw;
let det_inv = 1.0 / det;
let sr_inv = 1.0 / sr;
let sg_inv = 1.0 / sg;
let sb_inv = 1.0 / sb;
let srp = sr * det_inv;
let sgp = sg * det_inv;
let sbp = sb * det_inv;
let XYZ_from_local = [
[srp * xr, sgp * xg, sbp * xb, 0.0],
[srp * yr, sgp * yg, sbp * yb, 0.0],
[srp * zr, sgp * zg, sbp * zb, 0.0],
];
let local_from_XYZ = [
[srx * sr_inv, sry * sr_inv, srz * sr_inv, 0.0],
[sgx * sg_inv, sgy * sg_inv, sgz * sg_inv, 0.0],
[sbx * sb_inv, sby * sb_inv, sbz * sb_inv, 0.0],
];
(
ColorMatrix::new(XYZ_from_local),
ColorMatrix::new(local_from_XYZ),
)
}
}

53
crates/cmm/src/lib.rs Normal file
View file

@ -0,0 +1,53 @@
macro_rules! linear_ids {
($ids:ident, $id:ident, $ty:ty $(,)?) => {
#[derive(Debug)]
pub struct $ids {
next: jay_utils::numcell::NumCell<$ty>,
}
impl Default for $ids {
fn default() -> Self {
Self {
next: jay_utils::numcell::NumCell::new(1),
}
}
}
impl $ids {
pub fn next(&self) -> $id {
$id(self.next.fetch_add(1))
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct $id($ty);
impl $id {
#[allow(dead_code)]
pub fn raw(&self) -> $ty {
self.0
}
#[allow(dead_code)]
pub fn from_raw(id: $ty) -> Self {
Self(id)
}
}
impl std::fmt::Display for $id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
};
}
pub mod cmm_description;
pub mod cmm_eotf;
pub mod cmm_luminance;
pub mod cmm_manager;
pub mod cmm_primaries;
pub mod cmm_render_intent;
#[cfg(test)]
mod cmm_tests;
pub mod cmm_transform;

View file

@ -0,0 +1,24 @@
[package]
name = "jay-cpu-worker"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-async-engine = { version = "0.1.0", path = "../async-engine" }
jay-geometry = { version = "0.1.0", path = "../geometry" }
jay-io-uring = { version = "0.1.0", path = "../io-uring" }
jay-tracy = { version = "0.1.0", path = "../tracy" }
jay-utils = { version = "0.1.0", path = "../utils" }
log = { version = "0.4.20", features = ["std"] }
parking_lot = "0.12.1"
thiserror = "2.0.11"
uapi = "0.2.13"
[dev-dependencies]
jay-wheel = { version = "0.1.0", path = "../wheel" }
[features]
it = []
tracy = ["jay-tracy/tracy", "jay-async-engine/tracy"]

View file

@ -0,0 +1,2 @@
pub mod img_copy;
pub mod read_write;

View file

@ -0,0 +1,60 @@
use {
crate::{AsyncCpuWork, CpuWork},
jay_geometry::Rect,
std::ptr,
};
pub struct ImgCopyWork {
pub src: *mut u8,
pub dst: *mut u8,
pub width: i32,
pub stride: i32,
pub bpp: i32,
pub rects: Vec<Rect>,
_priv: (),
}
unsafe impl Send for ImgCopyWork {}
impl ImgCopyWork {
pub unsafe fn new() -> Self {
Self {
src: ptr::null_mut(),
dst: ptr::null_mut(),
width: 0,
stride: 0,
bpp: 0,
rects: vec![],
_priv: (),
}
}
}
impl CpuWork for ImgCopyWork {
fn run(&mut self) -> Option<Box<dyn AsyncCpuWork>> {
jay_tracy::zone!("ImgCopyWork");
for rect in &self.rects {
let mut offset = rect.y1() * self.stride + rect.x1() * self.bpp;
if rect.width() == self.width {
let offset = offset as usize;
let len = rect.height() * self.stride;
unsafe {
ptr::copy_nonoverlapping(self.src.add(offset), self.dst.add(offset), len as _);
}
} else {
let len = rect.width() * self.bpp;
for _ in 0..rect.height() {
unsafe {
ptr::copy_nonoverlapping(
self.src.add(offset as _),
self.dst.add(offset as _),
len as _,
);
}
offset += self.stride;
}
}
}
None
}
}

View file

@ -0,0 +1,145 @@
use {
crate::{AsyncCpuWork, CompletedWork, CpuWork, WorkCompletion},
jay_async_engine::{AsyncEngine, SpawnedFuture},
jay_io_uring::{IoUring, IoUringError, IoUringTaskId},
std::{
any::Any,
ptr,
rc::Rc,
slice,
sync::{
Arc,
atomic::{AtomicBool, AtomicU64, Ordering::Relaxed},
},
},
thiserror::Error,
uapi::{Fd, c},
};
#[derive(Debug, Error)]
pub enum ReadWriteJobError {
#[error("An io_uring error occurred")]
IoUring(#[source] IoUringError),
#[error("The job was cancelled")]
Cancelled,
#[error("Tried to operate outside the bounds of the file descriptor")]
OutOfBounds,
}
pub struct ReadWriteWork {
cancel: Arc<CancelState>,
config: Option<Box<ReadWriteWorkConfig>>,
}
unsafe impl Send for ReadWriteWork {}
impl ReadWriteWork {
pub unsafe fn new() -> Self {
let cancel = Arc::new(CancelState::default());
ReadWriteWork {
cancel: cancel.clone(),
config: Some(Box::new(ReadWriteWorkConfig {
fd: -1,
offset: 0,
ptr: ptr::null_mut(),
len: 0,
write: false,
cancel,
result: None,
})),
}
}
pub fn config(&mut self) -> &mut ReadWriteWorkConfig {
self.config.as_mut().unwrap()
}
}
pub struct ReadWriteWorkConfig {
pub fd: c::c_int,
pub offset: usize,
pub ptr: *mut u8,
pub len: usize,
pub write: bool,
pub result: Option<Result<(), ReadWriteJobError>>,
cancel: Arc<CancelState>,
}
#[derive(Default)]
struct CancelState {
cancelled: AtomicBool,
cancel_id: AtomicU64,
}
impl CpuWork for ReadWriteWork {
fn run(&mut self) -> Option<Box<dyn AsyncCpuWork>> {
self.cancel.cancelled.store(false, Relaxed);
self.cancel.cancel_id.store(0, Relaxed);
self.config.take().map(|b| b as _)
}
fn cancel_async(&mut self, ring: &Rc<IoUring>) {
self.cancel.cancelled.store(true, Relaxed);
let id = self.cancel.cancel_id.load(Relaxed);
if id != 0 {
ring.cancel(IoUringTaskId::from_raw(id));
}
}
fn async_work_done(&mut self, work: Box<dyn AsyncCpuWork>) {
let work = (work as Box<dyn Any>).downcast().unwrap();
self.config = Some(work);
}
}
impl AsyncCpuWork for ReadWriteWorkConfig {
fn run(
mut self: Box<Self>,
eng: &Rc<AsyncEngine>,
ring: &Rc<IoUring>,
completion: WorkCompletion,
) -> SpawnedFuture<CompletedWork> {
let ring = ring.clone();
eng.spawn("shm read/write", async move {
let res = loop {
if self.cancel.cancelled.load(Relaxed) {
break Err(ReadWriteJobError::Cancelled);
}
if self.len == 0 {
break Ok(());
};
let res = if self.write {
ring.write_no_cancel(
Fd::new(self.fd),
self.offset,
unsafe { slice::from_raw_parts(self.ptr, self.len) },
None,
|id| self.cancel.cancel_id.store(id.raw(), Relaxed),
)
.await
} else {
ring.read_no_cancel(
Fd::new(self.fd),
self.offset,
unsafe { slice::from_raw_parts_mut(self.ptr, self.len) },
|id| self.cancel.cancel_id.store(id.raw(), Relaxed),
)
.await
};
match res {
Ok(0) => break Err(ReadWriteJobError::OutOfBounds),
Ok(n) => {
self.len -= n;
self.offset += n;
unsafe {
self.ptr = self.ptr.add(n);
}
}
Err(e) => break Err(ReadWriteJobError::IoUring(e)),
}
};
self.result = Some(res);
completion.complete(self)
})
}
}

View file

@ -0,0 +1,509 @@
pub mod jobs;
#[cfg(test)]
mod tests;
use {
jay_async_engine::{AsyncEngine, SpawnedFuture},
jay_io_uring::IoUring,
jay_utils::{
buf::TypedBuf,
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
numcell::NumCell,
oserror::{OsError, OsErrorExt2},
pipe::{Pipe, pipe},
ptr_ext::MutPtrExt,
queue::AsyncQueue,
stack::Stack,
},
parking_lot::{Condvar, Mutex},
std::{
any::Any,
cell::{Cell, RefCell},
collections::VecDeque,
mem,
ptr::NonNull,
rc::Rc,
sync::Arc,
thread,
},
thiserror::Error,
uapi::{OwnedFd, c},
};
pub trait CpuJob {
fn work(&mut self) -> &mut dyn CpuWork;
fn completed(self: Box<Self>);
}
pub trait CpuWork: Send {
fn run(&mut self) -> Option<Box<dyn AsyncCpuWork>>;
fn cancel_async(&mut self, ring: &Rc<IoUring>) {
let _ = ring;
unreachable!();
}
fn async_work_done(&mut self, work: Box<dyn AsyncCpuWork>) {
let _ = work;
unreachable!();
}
}
pub trait AsyncCpuWork: Any {
fn run(
self: Box<Self>,
eng: &Rc<AsyncEngine>,
ring: &Rc<IoUring>,
completion: WorkCompletion,
) -> SpawnedFuture<CompletedWork>;
}
pub struct WorkCompletion {
worker: Rc<Worker>,
id: CpuJobId,
}
pub struct CompletedWork(());
impl WorkCompletion {
pub fn complete(self, work: Box<dyn AsyncCpuWork>) -> CompletedWork {
let job = self.worker.async_jobs.remove(&self.id).unwrap();
unsafe {
job.work.deref_mut().async_work_done(work);
}
self.worker.send_completion(self.id);
CompletedWork(())
}
}
pub struct CpuWorker {
data: Rc<CpuWorkerData>,
_completions_listener: SpawnedFuture<()>,
_job_enqueuer: SpawnedFuture<()>,
}
#[must_use]
pub struct PendingJob {
id: CpuJobId,
thread_data: Rc<CpuWorkerData>,
job_data: Rc<PendingJobData>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
enum PendingJobState {
#[default]
Waiting,
Abandoned,
Completed,
}
#[derive(Default)]
struct PendingJobData {
job: Cell<Option<NonNull<dyn CpuJob>>>,
state: Cell<PendingJobState>,
}
enum Job {
New {
id: CpuJobId,
work: *mut dyn CpuWork,
},
Cancel {
id: CpuJobId,
},
}
unsafe impl Send for Job {}
#[derive(Default)]
struct CompletedJobsExchange {
queue: VecDeque<CpuJobId>,
condvar: Option<Arc<Condvar>>,
}
struct CpuWorkerData {
next: CpuJobIds,
jobs_to_enqueue: AsyncQueue<Job>,
new_jobs: Arc<Mutex<VecDeque<Job>>>,
have_new_jobs: Rc<OwnedFd>,
completed_jobs_remote: Arc<Mutex<CompletedJobsExchange>>,
completed_jobs_local: RefCell<VecDeque<CpuJobId>>,
have_completed_jobs: Rc<OwnedFd>,
pending_jobs: CopyHashMap<CpuJobId, Rc<PendingJobData>>,
ring: Rc<IoUring>,
_stop: OwnedFd,
pending_job_data_cache: Stack<Rc<PendingJobData>>,
sync_wake_condvar: Arc<Condvar>,
}
#[derive(Debug)]
struct CpuJobIds {
next: NumCell<u64>,
}
impl Default for CpuJobIds {
fn default() -> Self {
Self {
next: NumCell::new(1),
}
}
}
impl CpuJobIds {
fn next(&self) -> CpuJobId {
CpuJobId(self.next.fetch_add(1))
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
struct CpuJobId(u64);
#[derive(Debug, Error)]
pub enum CpuWorkerError {
#[error("Could not create a pipe")]
Pipe(#[source] OsError),
#[error("Could not create an eventfd")]
EventFd(#[source] OsError),
#[error("Could not dup an eventfd")]
Dup(#[source] OsError),
}
impl PendingJob {
pub fn detach(self) {
match self.job_data.state.get() {
PendingJobState::Waiting => {
self.job_data.state.set(PendingJobState::Abandoned);
}
PendingJobState::Abandoned => {
unreachable!();
}
PendingJobState::Completed => {}
}
}
}
impl Drop for CpuWorker {
fn drop(&mut self) {
self.data.do_equeue_jobs();
if self.data.pending_jobs.is_not_empty() {
log::warn!("CpuWorker dropped with pending jobs. Completed jobs will not be triggered.")
}
}
}
impl Drop for PendingJob {
fn drop(&mut self) {
match self.job_data.state.get() {
PendingJobState::Waiting => {
log::warn!("PendingJob dropped before completion. Blocking.");
let data = &self.thread_data;
let id = self.id;
self.job_data.state.set(PendingJobState::Abandoned);
data.jobs_to_enqueue.push(Job::Cancel { id });
data.do_equeue_jobs();
loop {
data.dispatch_completions();
if !data.pending_jobs.contains(&id) {
break;
}
let mut remote = data.completed_jobs_remote.lock();
while remote.queue.is_empty() {
remote.condvar = Some(data.sync_wake_condvar.clone());
data.sync_wake_condvar.wait(&mut remote);
}
}
}
PendingJobState::Abandoned => {}
PendingJobState::Completed => {
self.thread_data
.pending_job_data_cache
.push(self.job_data.clone());
}
}
}
}
impl CpuWorkerData {
fn clear(&self) {
self.jobs_to_enqueue.clear();
self.new_jobs.lock().clear();
self.completed_jobs_remote.lock().queue.clear();
self.completed_jobs_local.borrow_mut().clear();
self.pending_jobs.clear();
self.pending_job_data_cache.take();
}
async fn wait_for_completions(self: Rc<Self>) {
let mut buf = TypedBuf::<u64>::new();
loop {
if let Err(e) = self.ring.read(&self.have_completed_jobs, buf.buf()).await {
log::error!("Could not wait for job completions: {}", ErrorFmt(e));
return;
}
self.dispatch_completions();
}
}
fn dispatch_completions(&self) {
let completions = &mut *self.completed_jobs_local.borrow_mut();
mem::swap(completions, &mut self.completed_jobs_remote.lock().queue);
while let Some(id) = completions.pop_front() {
let job_data = self.pending_jobs.remove(&id).unwrap();
let job = job_data.job.take().unwrap();
let job = unsafe { Box::from_raw(job.as_ptr()) };
match job_data.state.get() {
PendingJobState::Waiting => {
job_data.state.set(PendingJobState::Completed);
job.completed();
}
PendingJobState::Abandoned => {
self.pending_job_data_cache.push(job_data);
}
PendingJobState::Completed => {
unreachable!();
}
}
}
}
async fn equeue_jobs(self: Rc<Self>) {
loop {
self.jobs_to_enqueue.non_empty().await;
self.do_equeue_jobs();
}
}
fn do_equeue_jobs(&self) {
self.jobs_to_enqueue.move_to(&mut self.new_jobs.lock());
if let Err(e) = uapi::eventfd_write(self.have_new_jobs.raw(), 1) {
panic!("Could not signal eventfd: {}", ErrorFmt(e));
}
}
}
impl CpuWorker {
pub fn new(ring: &Rc<IoUring>, eng: &Rc<AsyncEngine>) -> Result<Self, CpuWorkerError> {
let new_jobs: Arc<Mutex<VecDeque<Job>>> = Default::default();
let completed_jobs: Arc<Mutex<CompletedJobsExchange>> = Default::default();
let Pipe {
read: stop_read,
write: stop_write,
} = pipe().map_err(CpuWorkerError::Pipe)?;
let have_new_jobs = uapi::eventfd(0, c::EFD_CLOEXEC).map_os_err(CpuWorkerError::EventFd)?;
let have_completed_jobs =
uapi::eventfd(0, c::EFD_CLOEXEC).map_os_err(CpuWorkerError::EventFd)?;
thread::Builder::new()
.name("cpu worker".to_string())
.spawn({
let new_jobs = new_jobs.clone();
let completed_jobs = completed_jobs.clone();
let have_new_jobs = uapi::fcntl_dupfd_cloexec(have_new_jobs.raw(), 0)
.map_os_err(CpuWorkerError::Dup)?;
let have_completed_jobs = uapi::fcntl_dupfd_cloexec(have_completed_jobs.raw(), 0)
.map_os_err(CpuWorkerError::Dup)?;
move || {
work(
new_jobs,
completed_jobs,
stop_write,
have_new_jobs,
have_completed_jobs,
)
}
})
.unwrap();
let data = Rc::new(CpuWorkerData {
next: Default::default(),
jobs_to_enqueue: Default::default(),
new_jobs,
have_new_jobs: Rc::new(have_new_jobs),
completed_jobs_remote: completed_jobs,
completed_jobs_local: Default::default(),
have_completed_jobs: Rc::new(have_completed_jobs),
pending_jobs: Default::default(),
ring: ring.clone(),
_stop: stop_read,
pending_job_data_cache: Default::default(),
sync_wake_condvar: Arc::new(Condvar::new()),
});
Ok(Self {
_completions_listener: eng.spawn(
"cpu worker completions",
data.clone().wait_for_completions(),
),
_job_enqueuer: eng.spawn("cpu worker enqueue", data.clone().equeue_jobs()),
data,
})
}
pub fn clear(&self) {
self.data.clear();
}
pub fn submit(&self, job: Box<dyn CpuJob>) -> PendingJob {
let mut job = NonNull::from(Box::leak(job));
let id = self.data.next.next();
self.data.jobs_to_enqueue.push(Job::New {
id,
work: unsafe { job.as_mut().work() },
});
let job_data = self.data.pending_job_data_cache.pop().unwrap_or_default();
job_data.job.set(Some(job));
job_data.state.set(PendingJobState::Waiting);
self.data.pending_jobs.set(id, job_data.clone());
PendingJob {
id,
thread_data: self.data.clone(),
job_data,
}
}
#[cfg(feature = "it")]
pub fn wait_idle(&self) -> bool {
let was_idle = self.data.pending_jobs.is_empty();
loop {
self.data.dispatch_completions();
if self.data.pending_jobs.is_empty() {
break;
}
let mut remote = self.data.completed_jobs_remote.lock();
while remote.queue.is_empty() {
remote.condvar = Some(self.data.sync_wake_condvar.clone());
self.data.sync_wake_condvar.wait(&mut remote);
}
}
was_idle
}
}
fn work(
new_jobs: Arc<Mutex<VecDeque<Job>>>,
completed_jobs: Arc<Mutex<CompletedJobsExchange>>,
stop: OwnedFd,
have_new_jobs: OwnedFd,
have_completed_jobs: OwnedFd,
) {
let eng = AsyncEngine::new();
let ring = IoUring::new(&eng, 32).unwrap();
let worker = Rc::new(Worker {
eng,
ring,
completed_jobs,
have_completed_jobs,
async_jobs: Default::default(),
stopped: Cell::new(false),
});
let _stop_listener = worker
.eng
.spawn("stop listener", worker.clone().handle_stop(stop));
let _new_job_listener = worker.eng.spawn(
"new job listener",
worker.clone().handle_new_jobs(new_jobs, have_new_jobs),
);
if let Err(e) = worker.ring.run() {
panic!("io_uring failed: {}", ErrorFmt(e));
}
}
struct Worker {
eng: Rc<AsyncEngine>,
ring: Rc<IoUring>,
completed_jobs: Arc<Mutex<CompletedJobsExchange>>,
have_completed_jobs: OwnedFd,
async_jobs: CopyHashMap<CpuJobId, AsyncJob>,
stopped: Cell<bool>,
}
struct AsyncJob {
_future: SpawnedFuture<CompletedWork>,
work: *mut dyn CpuWork,
}
impl Worker {
async fn handle_stop(self: Rc<Self>, stop: OwnedFd) {
let stop = Rc::new(stop);
if let Err(e) = self.ring.poll(&stop, 0).await {
log::error!(
"Could not wait for stop fd to become readable: {}",
ErrorFmt(e)
);
} else {
assert!(self.async_jobs.is_empty());
self.stopped.set(true);
self.ring.stop();
}
}
async fn handle_new_jobs(
self: Rc<Self>,
jobs_remote: Arc<Mutex<VecDeque<Job>>>,
new_jobs: OwnedFd,
) {
let mut buf = TypedBuf::<u64>::new();
let new_jobs = Rc::new(new_jobs);
let mut jobs = VecDeque::new();
loop {
if let Err(e) = self.ring.read(&new_jobs, buf.buf()).await {
if self.stopped.get() {
return;
}
panic!(
"Could not wait for new jobs fd to be signaled: {}",
ErrorFmt(e),
);
}
mem::swap(&mut jobs, &mut *jobs_remote.lock());
while let Some(job) = jobs.pop_front() {
self.handle_new_job(job);
}
}
}
fn handle_new_job(self: &Rc<Self>, job: Job) {
match job {
Job::Cancel { id } => {
let mut jobs = self.async_jobs.lock();
if let Some(job) = jobs.get_mut(&id) {
unsafe {
job.work.deref_mut().cancel_async(&self.ring);
}
}
}
Job::New { id, work } => match unsafe { work.deref_mut() }.run() {
None => {
self.send_completion(id);
return;
}
Some(w) => {
let completion = WorkCompletion {
worker: self.clone(),
id,
};
let future = w.run(&self.eng, &self.ring, completion);
self.async_jobs.set(
id,
AsyncJob {
_future: future,
work,
},
);
}
},
}
}
fn send_completion(&self, id: CpuJobId) {
let cv = {
let mut exchange = self.completed_jobs.lock();
exchange.queue.push_back(id);
exchange.condvar.take()
};
if let Some(cv) = cv {
cv.notify_all();
}
if let Err(e) = uapi::eventfd_write(self.have_completed_jobs.raw(), 1) {
panic!("Could not signal job completion: {}", ErrorFmt(e));
}
}
}

View file

@ -0,0 +1,111 @@
use {
crate::{AsyncCpuWork, CompletedWork, CpuJob, CpuWork, CpuWorker, WorkCompletion},
jay_async_engine::{AsyncEngine, SpawnedFuture},
jay_io_uring::IoUring,
jay_utils::asyncevent::AsyncEvent,
jay_wheel::Wheel,
std::{future::pending, rc::Rc, sync::Arc},
uapi::{OwnedFd, c::EFD_CLOEXEC},
};
struct Job {
ae: Rc<AsyncEvent>,
work: Work,
cancel: bool,
}
struct Work(Arc<OwnedFd>);
struct AsyncWork(Arc<OwnedFd>);
impl CpuJob for Job {
fn work(&mut self) -> &mut dyn CpuWork {
&mut self.work
}
fn completed(self: Box<Self>) {
if self.cancel {
unreachable!();
} else {
self.ae.trigger();
}
}
}
impl Drop for Job {
fn drop(&mut self) {
if self.cancel {
self.ae.trigger();
}
}
}
impl CpuWork for Work {
fn run(&mut self) -> Option<Box<dyn AsyncCpuWork>> {
Some(Box::new(AsyncWork(self.0.clone())))
}
fn cancel_async(&mut self, _ring: &Rc<IoUring>) {
uapi::eventfd_write(self.0.raw(), 1).unwrap();
}
fn async_work_done(&mut self, work: Box<dyn AsyncCpuWork>) {
let _ = work;
}
}
impl AsyncCpuWork for AsyncWork {
fn run(
self: Box<Self>,
eng: &Rc<AsyncEngine>,
ring: &Rc<IoUring>,
completion: WorkCompletion,
) -> SpawnedFuture<CompletedWork> {
let ring = ring.clone();
eng.spawn("", async move {
let mut buf = [0; 8];
let res = ring
.read_no_cancel(self.0.borrow(), 0, &mut buf, |_| ())
.await;
res.unwrap();
completion.complete(self)
})
}
}
fn run(cancel: bool) {
let eng = AsyncEngine::new();
let ring = IoUring::new(&eng, 32).unwrap();
let ring2 = ring.clone();
let wheel = Wheel::new(&eng, &ring).unwrap();
let cpu = Rc::new(CpuWorker::new(&ring, &eng).unwrap());
let ae = Rc::new(AsyncEvent::default());
let eventfd = Arc::new(uapi::eventfd(0, EFD_CLOEXEC).unwrap());
let pending_job = cpu.submit(Box::new(Job {
ae: ae.clone(),
work: Work(eventfd.clone()),
cancel,
}));
let _fut1 = eng.spawn("", async move {
wheel.timeout(1).await.unwrap();
if cancel {
drop(pending_job);
} else {
uapi::eventfd_write(eventfd.raw(), 1).unwrap();
pending::<()>().await;
}
});
let _fut2 = eng.spawn("", async move {
ae.triggered().await;
ring2.stop();
});
ring.run().unwrap();
}
#[test]
fn cancel() {
run(true);
}
#[test]
fn complete() {
run(false);
}

View file

@ -0,0 +1,12 @@
[package]
name = "jay-criteria"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-utils = { version = "0.1.0", path = "../utils" }
ahash = "0.8.7"
linearize = { version = "0.1.3", features = ["derive"] }
regex = "1.11.1"

View file

@ -0,0 +1,18 @@
mod crit_downstream;
mod crit_middle;
mod crit_root;
mod crit_target;
mod crit_upstream;
pub use {
crit_downstream::{CritDownstream, CritDownstreamData},
crit_middle::{CritMiddle, CritMiddleCriterion},
crit_root::{
CritFixedRootCriterion, CritFixedRootCriterionBase, CritRoot, CritRootCriterion,
CritRootFixed,
},
crit_target::{CritMgr, CritTarget, CritTargetOwner, WeakCritTargetOwner},
crit_upstream::{
CritUpstreamData, CritUpstreamNode, CritUpstreamNodeBase, CritUpstreamNodeData,
},
};

View file

@ -0,0 +1,52 @@
use {
crate::{
CritMatcherId,
crit_graph::{CritTarget, crit_upstream::CritUpstreamNode},
},
std::rc::Rc,
};
pub struct CritDownstreamData<Target>
where
Target: CritTarget,
{
id: CritMatcherId,
pub(super) upstream: Vec<Rc<dyn CritUpstreamNode<Target>>>,
}
pub trait CritDownstream<Target>: 'static {
fn update_matched(self: Rc<Self>, target: &Target, matched: bool);
}
impl<Target> CritDownstreamData<Target>
where
Target: CritTarget,
{
pub fn new(id: CritMatcherId, upstream: &[Rc<dyn CritUpstreamNode<Target>>]) -> Self {
Self {
id,
upstream: upstream.to_vec(),
}
}
pub fn attach(&self, slf: &Rc<impl CritDownstream<Target>>) {
for upstream in &self.upstream {
upstream.attach(self.id, slf.clone() as _);
}
}
pub fn not(&self, mgr: &Target::Mgr) -> Vec<Rc<dyn CritUpstreamNode<Target>>> {
self.upstream.iter().map(|n| n.not(mgr)).collect()
}
}
impl<Target> Drop for CritDownstreamData<Target>
where
Target: CritTarget,
{
fn drop(&mut self) {
for el in &self.upstream {
el.detach(self.id);
}
}
}

View file

@ -0,0 +1,111 @@
use {
crate::{
CritUpstreamNode,
crit_graph::{
CritDownstream, CritDownstreamData, CritTarget, CritUpstreamData,
crit_target::CritMgr,
crit_upstream::{CritUpstreamNodeBase, CritUpstreamNodeData},
},
crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData},
},
std::rc::Rc,
};
pub struct CritMiddle<Target, T>
where
Target: CritTarget,
T: CritMiddleCriterion<Target>,
{
upstream: CritDownstreamData<Target>,
downstream: CritUpstreamData<Target, CritMiddleData<T::Data>>,
criterion: T,
}
#[derive(Default)]
pub struct CritMiddleData<T> {
matches: usize,
data: T,
}
pub trait CritMiddleCriterion<Target>: Sized + 'static {
type Data: Default;
type Not: CritMiddleCriterion<Target>;
fn update_matched(&self, target: &Target, node: &mut Self::Data, matched: bool) -> bool;
fn pull(&self, upstream: &[Rc<dyn CritUpstreamNode<Target>>], target: &Target) -> bool;
fn not(&self) -> Self::Not;
}
impl<Target, T> CritMiddle<Target, T>
where
Target: CritTarget,
T: CritMiddleCriterion<Target>,
{
pub fn new(
mgr: &Target::Mgr,
upstream: &[Rc<dyn CritUpstreamNode<Target>>],
criterion: T,
) -> Rc<Self> {
let id = mgr.id();
let slf = Rc::new_cyclic(|slf| Self {
upstream: CritDownstreamData::new(id, upstream),
downstream: CritUpstreamData::new(slf, id),
criterion,
});
slf.upstream.attach(&slf);
slf
}
}
impl<Target, T> CritDownstream<Target> for CritMiddle<Target, T>
where
Target: CritTarget,
T: CritMiddleCriterion<Target>,
{
fn update_matched(self: Rc<Self>, target: &Target, matched: bool) {
let mut node = self.downstream.get_or_create(target);
let change = self
.criterion
.update_matched(target, &mut node.data, matched);
let matches = match matched {
true => node.matches + 1,
false => node.matches - 1,
};
node.matches = matches;
self.downstream
.update_matched(target, node, change, matches == 0);
}
}
impl<Target, T> CritUpstreamNodeBase<Target> for CritMiddle<Target, T>
where
Target: CritTarget,
T: CritMiddleCriterion<Target>,
{
type Data = CritMiddleData<T::Data>;
fn data(&self) -> &CritUpstreamData<Target, Self::Data> {
&self.downstream
}
fn not(&self, mgr: &Target::Mgr) -> Rc<dyn CritUpstreamNode<Target>> {
let upstream = self.upstream.not(mgr);
CritMiddle::new(mgr, &upstream, self.criterion.not())
}
fn pull(&self, target: &Target) -> bool {
self.criterion.pull(&self.upstream.upstream, target)
}
}
impl<Target, T> CritDestroyListenerBase<Target> for CritMiddle<Target, T>
where
Target: CritTarget,
T: CritMiddleCriterion<Target>,
{
type Data = CritUpstreamNodeData<Target, CritMiddleData<T::Data>>;
fn data(&self) -> &CritPerTargetData<Target, Self::Data> {
&self.downstream.nodes
}
}

View file

@ -0,0 +1,175 @@
use {
crate::{
CritMatcherId, CritUpstreamNode, FixedRootMatcher, RootMatcherMap,
crit_graph::{
CritTarget, CritUpstreamData,
crit_target::CritMgr,
crit_upstream::{CritUpstreamNodeBase, CritUpstreamNodeData},
},
crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData},
},
std::{marker::PhantomData, rc::Rc},
};
pub struct CritRoot<Target, T>
where
Target: CritTarget,
T: CritRootCriterion<Target>,
{
id: CritMatcherId,
downstream: CritUpstreamData<Target, ()>,
not: bool,
criterion: Rc<T>,
roots: Rc<Target::RootMatchers>,
}
pub struct CritRootFixed<Target, Crit>(pub Crit, pub PhantomData<fn(&Target)>);
pub trait CritRootCriterion<Target>: Sized + 'static
where
Target: CritTarget,
{
fn matches(&self, data: &Target) -> bool;
fn nodes(roots: &Target::RootMatchers) -> Option<&RootMatcherMap<Target, Self>> {
let _ = roots;
None
}
fn not(&self, mgr: &Target::Mgr) -> Option<Rc<dyn CritUpstreamNode<Target>>> {
let _ = mgr;
None
}
}
pub trait CritFixedRootCriterionBase<Target>: Sized + 'static
where
Target: CritTarget,
{
fn constant(&self) -> bool;
fn not<'a>(&self, mgr: &'a Target::Mgr) -> &'a FixedRootMatcher<Target, Self>
where
Self: CritFixedRootCriterion<Target>;
}
pub trait CritFixedRootCriterion<Target>: CritFixedRootCriterionBase<Target>
where
Target: CritTarget,
{
const COMPARE: bool = true;
fn matches(&self, data: &Target) -> bool;
}
impl<Target, T> CritRootCriterion<Target> for CritRootFixed<Target, T>
where
Target: CritTarget,
T: CritFixedRootCriterion<Target>,
{
fn matches(&self, data: &Target) -> bool {
let mut res = self.0.matches(data);
if T::COMPARE {
res = res == self.0.constant();
}
res
}
fn not(&self, mgr: &Target::Mgr) -> Option<Rc<dyn CritUpstreamNode<Target>>> {
Some(self.0.not(mgr)[!self.0.constant()].clone())
}
}
impl<Target, T> CritUpstreamNodeBase<Target> for CritRoot<Target, T>
where
Target: CritTarget,
T: CritRootCriterion<Target>,
{
type Data = ();
fn data(&self) -> &CritUpstreamData<Target, Self::Data> {
&self.downstream
}
fn not(&self, mgr: &Target::Mgr) -> Rc<dyn CritUpstreamNode<Target>> {
if let Some(node) = self.criterion.not(mgr) {
return node;
}
let id = mgr.id();
Self::new_(&self.roots, id, self.criterion.clone(), !self.not)
}
fn pull(&self, target: &Target) -> bool {
self.criterion.matches(target) ^ self.not
}
}
impl<Target, T> CritRoot<Target, T>
where
Target: CritTarget,
T: CritRootCriterion<Target>,
{
pub fn new(roots: &Rc<Target::RootMatchers>, id: CritMatcherId, criterion: T) -> Rc<Self> {
Self::new_(roots, id, Rc::new(criterion), false)
}
fn new_(
roots: &Rc<Target::RootMatchers>,
id: CritMatcherId,
criterion: Rc<T>,
not: bool,
) -> Rc<Self> {
let slf = Rc::new_cyclic(|slf| Self {
id,
downstream: CritUpstreamData::new(slf, id),
not,
criterion,
roots: roots.clone(),
});
if let Some(nodes) = T::nodes(roots) {
nodes.set(id, Rc::downgrade(&slf));
}
slf
}
pub fn clear(&self) {
self.downstream.clear();
}
pub fn handle(&self, target: &Target) {
let new = self.criterion.matches(target) ^ self.not;
let node = match new {
true => self.downstream.get_or_create(target),
false => match self.downstream.get(target) {
Some(n) => n,
None => return,
},
};
self.downstream.update_matched(target, node, new, !new);
}
pub fn has_downstream(&self) -> bool {
self.downstream.has_downstream()
}
}
impl<Target, T> CritDestroyListenerBase<Target> for CritRoot<Target, T>
where
Target: CritTarget,
T: CritRootCriterion<Target>,
{
type Data = CritUpstreamNodeData<Target, ()>;
fn data(&self) -> &CritPerTargetData<Target, Self::Data> {
&self.downstream.nodes
}
}
impl<Target, T> Drop for CritRoot<Target, T>
where
Target: CritTarget,
T: CritRootCriterion<Target>,
{
fn drop(&mut self) {
if let Some(nodes) = T::nodes(&self.roots) {
nodes.remove(&self.id);
}
}
}

View file

@ -0,0 +1,46 @@
use {
crate::{
CritDestroyListener, CritMatcherId, FixedRootMatcher, crit_leaf::CritLeafEvent,
crit_matchers::critm_constant::CritMatchConstant,
},
jay_utils::{copyhashmap::CopyHashMap, queue::AsyncQueue},
std::{
hash::Hash,
rc::{Rc, Weak},
},
};
pub trait CritMgr: 'static {
type Target: CritTarget<Mgr = Self>;
fn id(&self) -> CritMatcherId;
fn leaf_events(&self) -> &Rc<AsyncQueue<CritLeafEvent<Self::Target>>>;
fn match_constant(&self) -> &FixedRootMatcher<Self::Target, CritMatchConstant<Self::Target>>;
fn roots(&self) -> &Rc<<Self::Target as CritTarget>::RootMatchers>;
}
pub trait CritTarget: 'static {
type Id: Copy + Hash + Eq;
type Mgr: CritMgr<Target = Self>;
type RootMatchers;
type LeafData: Copy + Eq;
type Owner: WeakCritTargetOwner<Target = Self>;
fn owner(&self) -> Self::Owner;
fn id(&self) -> Self::Id;
fn destroyed(&self) -> &CopyHashMap<CritMatcherId, Weak<dyn CritDestroyListener<Self>>>;
fn leaf_data(&self) -> Self::LeafData;
}
pub trait CritTargetOwner: 'static {
type Target: CritTarget;
fn data(&self) -> &Self::Target;
}
pub trait WeakCritTargetOwner: 'static {
type Target: CritTarget;
type Owner: CritTargetOwner<Target = Self::Target>;
fn upgrade(&self) -> Option<Self::Owner>;
}

View file

@ -0,0 +1,177 @@
use {
crate::{
CritDestroyListener, CritMatcherId,
crit_graph::{
WeakCritTargetOwner,
crit_downstream::CritDownstream,
crit_target::{CritTarget, CritTargetOwner},
},
crit_per_target_data::CritPerTargetData,
},
jay_utils::copyhashmap::CopyHashMap,
std::{
cell::RefMut,
mem,
ops::{Deref, DerefMut},
rc::{Rc, Weak},
},
};
pub struct CritUpstreamData<Target, T>
where
Target: CritTarget,
{
downstream: CopyHashMap<CritMatcherId, Weak<dyn CritDownstream<Target>>>,
pub nodes: CritPerTargetData<Target, CritUpstreamNodeData<Target, T>>,
}
pub struct CritUpstreamNodeData<Target, T>
where
Target: CritTarget,
{
matched: bool,
tl: Target::Owner,
data: T,
}
pub trait CritUpstreamNodeBase<Target>: 'static
where
Target: CritTarget,
{
type Data;
fn data(&self) -> &CritUpstreamData<Target, Self::Data>;
fn not(&self, mgr: &Target::Mgr) -> Rc<dyn CritUpstreamNode<Target>>;
fn pull(&self, target: &Target) -> bool;
}
pub trait CritUpstreamNode<Target>: 'static
where
Target: CritTarget,
{
fn attach(&self, id: CritMatcherId, downstream: Rc<dyn CritDownstream<Target>>);
fn detach(&self, id: CritMatcherId);
fn not(&self, mgr: &Target::Mgr) -> Rc<dyn CritUpstreamNode<Target>>;
fn pull(&self, target: &Target) -> bool;
fn get(&self, target: &Target) -> bool;
}
impl<Target, T> CritUpstreamNode<Target> for T
where
Target: CritTarget,
T: CritUpstreamNodeBase<Target>,
{
fn attach(&self, id: CritMatcherId, downstream: Rc<dyn CritDownstream<Target>>) {
let data = self.data();
for n in data.nodes.borrow_mut().values_mut() {
if !n.matched {
continue;
}
let Some(target) = n.tl.upgrade() else {
continue;
};
downstream.clone().update_matched(target.data(), true);
}
data.downstream.set(id, Rc::downgrade(&downstream));
}
fn detach(&self, id: CritMatcherId) {
self.data().downstream.remove(&id);
}
fn not(&self, mgr: &Target::Mgr) -> Rc<dyn CritUpstreamNode<Target>> {
<T as CritUpstreamNodeBase<Target>>::not(self, mgr)
}
fn pull(&self, target: &Target) -> bool {
<T as CritUpstreamNodeBase<Target>>::pull(self, target)
}
fn get(&self, target: &Target) -> bool {
<T as CritUpstreamNodeBase<Target>>::data(self).matched(target)
}
}
impl<Target, T> Deref for CritUpstreamNodeData<Target, T>
where
Target: CritTarget,
{
type Target = T;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<Target, T> DerefMut for CritUpstreamNodeData<Target, T>
where
Target: CritTarget,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}
impl<Target, T> CritUpstreamData<Target, T>
where
Target: CritTarget,
{
pub fn new(slf: &Weak<impl CritDestroyListener<Target>>, id: CritMatcherId) -> Self {
Self {
downstream: Default::default(),
nodes: CritPerTargetData::new(slf, id),
}
}
pub fn clear(&self) {
self.nodes.clear()
}
pub fn update_matched(
&self,
target: &Target,
mut node: RefMut<CritUpstreamNodeData<Target, T>>,
matched: bool,
remove: bool,
) {
let unchanged = mem::replace(&mut node.matched, matched) == matched;
drop(node);
if remove {
self.nodes.remove(target.id());
}
if unchanged {
return;
}
for el in self.downstream.lock().values() {
if let Some(el) = el.upgrade() {
el.update_matched(target, matched);
}
}
}
pub fn get_or_create(&self, target: &Target) -> RefMut<'_, CritUpstreamNodeData<Target, T>>
where
T: Default,
{
self.nodes.get_or_create(target, || CritUpstreamNodeData {
matched: false,
tl: target.owner(),
data: Default::default(),
})
}
pub fn get(&self, target: &Target) -> Option<RefMut<'_, CritUpstreamNodeData<Target, T>>> {
self.nodes.get(target)
}
pub fn has_downstream(&self) -> bool {
self.downstream.is_not_empty()
}
pub fn matched(&self, target: &Target) -> bool {
let Some(node) = self.nodes.get(target) else {
return false;
};
node.matched
}
}

View file

@ -0,0 +1,154 @@
use {
crate::{
CritUpstreamNode,
crit_graph::{CritDownstream, CritDownstreamData, CritMgr, CritTarget},
crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData},
},
jay_utils::{cell_ext::CellExt, queue::AsyncQueue},
std::{
cell::Cell,
rc::{Rc, Weak},
slice,
},
};
pub struct CritLeafMatcher<Target>
where
Target: CritTarget,
{
upstream: CritDownstreamData<Target>,
on_match: Box<dyn Fn(Target::LeafData) -> Box<dyn FnOnce()>>,
targets: CritPerTargetData<Target, NodeHolder<Target>>,
events: Rc<AsyncQueue<CritLeafEvent<Target>>>,
}
pub struct NodeHolder<Target>
where
Target: CritTarget,
{
node: Rc<Node<Target>>,
}
struct Node<Target>
where
Target: CritTarget,
{
leaf: Weak<CritLeafMatcher<Target>>,
target_id: Target::Id,
needs_event: Cell<bool>,
new_data: Cell<Option<Target::LeafData>>,
data: Cell<Option<Target::LeafData>>,
on_unmatch: Cell<Option<Box<dyn FnOnce()>>>,
}
pub struct CritLeafEvent<Target>
where
Target: CritTarget,
{
node: Rc<Node<Target>>,
}
impl<Target> CritDownstream<Target> for CritLeafMatcher<Target>
where
Target: CritTarget,
{
fn update_matched(self: Rc<Self>, data: &Target, matched: bool) {
let node = &self
.targets
.get_or_create(data, || {
let node = Rc::new(Node {
leaf: Rc::downgrade(&self),
target_id: data.id(),
needs_event: Cell::new(true),
new_data: Cell::new(None),
data: Cell::new(None),
on_unmatch: Cell::new(None),
});
NodeHolder { node: node.clone() }
})
.node;
self.push_event(node, matched.then_some(data.leaf_data()));
}
}
impl<Target> CritLeafMatcher<Target>
where
Target: CritTarget,
{
pub(crate) fn new(
mgr: &Target::Mgr,
upstream: &Rc<dyn CritUpstreamNode<Target>>,
on_match: impl Fn(Target::LeafData) -> Box<dyn FnOnce()> + 'static,
) -> Rc<Self> {
let id = mgr.id();
let slf = Rc::new_cyclic(|slf| Self {
targets: CritPerTargetData::new(slf, id),
on_match: Box::new(on_match),
events: mgr.leaf_events().clone(),
upstream: CritDownstreamData::new(id, slice::from_ref(upstream)),
});
slf.upstream.attach(&slf);
slf
}
fn push_event(&self, node: &Rc<Node<Target>>, new_data: Option<Target::LeafData>) {
node.new_data.set(new_data);
if node.needs_event.replace(false) {
self.events.push(CritLeafEvent { node: node.clone() });
}
}
}
impl<Target> CritLeafEvent<Target>
where
Target: CritTarget,
{
pub fn run(self) {
let n = self.node;
n.needs_event.set(true);
if n.new_data != n.data
&& let Some(on_unmatch) = n.on_unmatch.take()
{
if n.leaf.strong_count() == 0 {
return;
}
on_unmatch();
}
n.data.set(n.new_data.get());
if n.data.is_some() != n.on_unmatch.is_some() {
let Some(leaf) = n.leaf.upgrade() else {
return;
};
if let Some(id) = n.data.get() {
n.on_unmatch.set(Some((leaf.on_match)(id)));
} else {
if let Some(on_unmatch) = n.on_unmatch.take() {
on_unmatch();
}
leaf.targets.remove(n.target_id);
}
}
}
}
impl<Target> Drop for NodeHolder<Target>
where
Target: CritTarget,
{
fn drop(&mut self) {
if let Some(leaf) = self.node.leaf.upgrade() {
leaf.push_event(&self.node, None);
}
}
}
impl<Target> CritDestroyListenerBase<Target> for CritLeafMatcher<Target>
where
Target: CritTarget,
{
type Data = NodeHolder<Target>;
fn data(&self) -> &CritPerTargetData<Target, Self::Data> {
&self.targets
}
}

View file

@ -0,0 +1,4 @@
pub mod critm_any_or_all;
pub mod critm_constant;
pub mod critm_exactly;
pub mod critm_string;

View file

@ -0,0 +1,73 @@
use {
crate::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode},
std::{marker::PhantomData, rc::Rc},
};
pub struct CritMatchAnyOrAll<Target>
where
Target: CritTarget,
{
all: bool,
total: usize,
_phantom: PhantomData<fn(&Target)>,
}
impl<Target> CritMatchAnyOrAll<Target>
where
Target: CritTarget,
{
pub fn new(upstream: &[Rc<dyn CritUpstreamNode<Target>>], all: bool) -> Self {
Self {
all,
total: upstream.len(),
_phantom: Default::default(),
}
}
}
impl<Target> CritMiddleCriterion<Target> for CritMatchAnyOrAll<Target>
where
Target: CritTarget,
{
type Data = usize;
type Not = Self;
fn update_matched(&self, _data: &Target, node: &mut usize, matched: bool) -> bool {
if matched {
*node += 1;
} else {
*node -= 1;
}
if self.all {
*node == self.total
} else {
*node > 0
}
}
fn pull(&self, upstream: &[Rc<dyn CritUpstreamNode<Target>>], node: &Target) -> bool {
for upstream in upstream {
if upstream.pull(node) {
if !self.all {
return true;
}
} else {
if self.all {
return false;
}
}
}
self.all
}
fn not(&self) -> Self
where
Self: Sized,
{
Self {
all: !self.all,
total: self.total,
_phantom: Default::default(),
}
}
}

View file

@ -0,0 +1,58 @@
use {
crate::{
CritMatcherIds, FixedRootMatcher,
crit_graph::{
CritFixedRootCriterion, CritFixedRootCriterionBase, CritMgr, CritRoot, CritRootFixed,
CritTarget,
},
},
linearize::static_map,
std::{marker::PhantomData, rc::Rc},
};
pub struct CritMatchConstant<Target>(pub bool, pub PhantomData<fn(&Target)>);
impl<Target> CritMatchConstant<Target>
where
Target: CritTarget,
{
pub fn create(
roots: &Rc<Target::RootMatchers>,
ids: &CritMatcherIds,
) -> FixedRootMatcher<Target, CritMatchConstant<Target>> {
static_map! {
v => CritRoot::new(
roots,
ids.next(),
CritRootFixed(Self(v, PhantomData), PhantomData),
),
}
}
}
impl<Target> CritFixedRootCriterionBase<Target> for CritMatchConstant<Target>
where
Target: CritTarget,
{
fn constant(&self) -> bool {
self.0
}
fn not<'a>(&self, mgr: &'a Target::Mgr) -> &'a FixedRootMatcher<Target, Self>
where
Self: CritFixedRootCriterion<Target>,
{
mgr.match_constant()
}
}
impl<Target> CritFixedRootCriterion<Target> for CritMatchConstant<Target>
where
Target: CritTarget,
{
const COMPARE: bool = false;
fn matches(&self, _data: &Target) -> bool {
self.0
}
}

View file

@ -0,0 +1,61 @@
use {
crate::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode},
std::{marker::PhantomData, rc::Rc},
};
pub struct CritMatchExactly<Target> {
total: usize,
num: usize,
not: bool,
_phantom: PhantomData<fn(&Target)>,
}
impl<Target> CritMatchExactly<Target> {
pub fn new(upstream: &[Rc<dyn CritUpstreamNode<Target>>], num: usize) -> Self {
Self {
total: upstream.len(),
num,
not: false,
_phantom: Default::default(),
}
}
}
impl<Target> CritMiddleCriterion<Target> for CritMatchExactly<Target>
where
Target: CritTarget,
{
type Data = usize;
type Not = Self;
fn update_matched(&self, _data: &Target, node: &mut usize, matched: bool) -> bool {
if matched {
*node += 1;
} else {
*node -= 1;
}
(*node == self.num) ^ self.not
}
fn pull(&self, upstream: &[Rc<dyn CritUpstreamNode<Target>>], node: &Target) -> bool {
let mut n = 0;
for upstream in upstream {
if upstream.pull(node) {
n += 1;
}
}
(n == self.num) ^ self.not
}
fn not(&self) -> Self
where
Self: Sized,
{
Self {
total: self.total,
num: self.total - self.num,
not: !self.not,
_phantom: Default::default(),
}
}
}

View file

@ -0,0 +1,45 @@
use {
crate::{
CritLiteralOrRegex, RootMatcherMap,
crit_graph::{CritRootCriterion, CritTarget},
},
std::marker::PhantomData,
};
pub struct CritMatchString<Target, A> {
string: CritLiteralOrRegex,
_phantom: PhantomData<(fn(&Target), A)>,
}
pub trait StringAccess<Target>: Sized + 'static
where
Target: CritTarget,
{
fn with_string(data: &Target, f: impl FnOnce(&str) -> bool) -> bool;
fn nodes(
roots: &Target::RootMatchers,
) -> &RootMatcherMap<Target, CritMatchString<Target, Self>>;
}
impl<Target, A> CritMatchString<Target, A> {
pub fn new(string: CritLiteralOrRegex) -> Self {
Self {
string,
_phantom: Default::default(),
}
}
}
impl<Target, A> CritRootCriterion<Target> for CritMatchString<Target, A>
where
Target: CritTarget,
A: StringAccess<Target>,
{
fn matches(&self, data: &Target) -> bool {
A::with_string(data, |s| self.string.matches(s))
}
fn nodes(roots: &Target::RootMatchers) -> Option<&RootMatcherMap<Target, Self>> {
Some(A::nodes(roots))
}
}

View file

@ -0,0 +1,139 @@
use {
crate::{
CritMatcherId,
crit_graph::{CritTarget, CritTargetOwner, WeakCritTargetOwner},
},
ahash::AHashMap,
std::{
cell::{RefCell, RefMut},
ops::{Deref, DerefMut},
rc::Weak,
},
};
pub struct CritPerTargetData<Target, T>
where
Target: CritTarget,
{
id: CritMatcherId,
slf: Weak<dyn CritDestroyListener<Target>>,
data: RefCell<AHashMap<Target::Id, PerTreeNodeData<Target, T>>>,
}
pub struct PerTreeNodeData<Target, T>
where
Target: CritTarget,
{
node: Target::Owner,
data: T,
}
pub trait CritDestroyListenerBase<Target>: 'static
where
Target: CritTarget,
{
type Data;
fn data(&self) -> &CritPerTargetData<Target, Self::Data>;
}
pub trait CritDestroyListener<Target>: 'static
where
Target: CritTarget,
{
fn destroyed(&self, target_id: Target::Id);
}
impl<Target, T> CritPerTargetData<Target, T>
where
Target: CritTarget,
{
pub fn new(slf: &Weak<impl CritDestroyListener<Target>>, id: CritMatcherId) -> Self {
Self {
id,
slf: slf.clone() as _,
data: Default::default(),
}
}
pub fn clear(&self) {
self.data.borrow_mut().clear();
}
pub fn get_or_create(&self, target: &Target, default: impl FnOnce() -> T) -> RefMut<'_, T> {
RefMut::map(self.data.borrow_mut(), |d| {
&mut d
.entry(target.id())
.or_insert_with(|| {
target.destroyed().set(self.id, self.slf.clone());
PerTreeNodeData {
node: target.owner(),
data: default(),
}
})
.data
})
}
pub fn get(&self, target: &Target) -> Option<RefMut<'_, T>> {
RefMut::filter_map(self.data.borrow_mut(), |d| {
d.get_mut(&target.id()).map(|d| &mut d.data)
})
.ok()
}
pub fn remove(&self, target_id: Target::Id) {
if let Some(node) = self.data.borrow_mut().remove(&target_id)
&& let Some(node) = node.node.upgrade()
{
node.data().destroyed().remove(&self.id);
}
}
pub fn borrow_mut(&self) -> RefMut<'_, AHashMap<Target::Id, PerTreeNodeData<Target, T>>> {
self.data.borrow_mut()
}
}
impl<Target, T> Drop for CritPerTargetData<Target, T>
where
Target: CritTarget,
{
fn drop(&mut self) {
for d in self.data.borrow().values() {
if let Some(n) = d.node.upgrade() {
n.data().destroyed().remove(&self.id);
}
}
}
}
impl<Target, T> CritDestroyListener<Target> for T
where
Target: CritTarget,
T: CritDestroyListenerBase<Target>,
{
fn destroyed(&self, target_id: Target::Id) {
let _v = self.data().data.borrow_mut().remove(&target_id);
}
}
impl<Target, T> Deref for PerTreeNodeData<Target, T>
where
Target: CritTarget,
{
type Target = T;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<Target, T> DerefMut for PerTreeNodeData<Target, T>
where
Target: CritTarget,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}

131
crates/criteria/src/lib.rs Normal file
View file

@ -0,0 +1,131 @@
pub mod crit_graph;
pub mod crit_leaf;
pub mod crit_matchers;
pub mod crit_per_target_data;
use {
crate::{
crit_graph::{CritMgr, CritMiddle, CritRoot, CritRootCriterion, CritRootFixed},
crit_leaf::CritLeafMatcher,
crit_matchers::{critm_any_or_all::CritMatchAnyOrAll, critm_exactly::CritMatchExactly},
},
jay_utils::{copyhashmap::CopyHashMap, numcell::NumCell},
linearize::StaticMap,
regex::Regex,
std::rc::{Rc, Weak},
};
pub use {
crit_graph::{CritTarget, CritUpstreamNode},
crit_per_target_data::CritDestroyListener,
};
#[derive(Debug)]
pub struct CritMatcherIds {
next: NumCell<u64>,
}
impl Default for CritMatcherIds {
fn default() -> Self {
Self {
next: NumCell::new(1),
}
}
}
impl CritMatcherIds {
pub fn next(&self) -> CritMatcherId {
CritMatcherId(self.next.fetch_add(1))
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct CritMatcherId(u64);
impl CritMatcherId {
#[allow(clippy::allow_attributes, dead_code)]
pub fn raw(&self) -> u64 {
self.0
}
#[allow(clippy::allow_attributes, dead_code)]
pub fn from_raw(id: u64) -> Self {
Self(id)
}
}
impl std::fmt::Display for CritMatcherId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
pub type RootMatcherMap<Target, T> = CopyHashMap<CritMatcherId, Weak<CritRoot<Target, T>>>;
pub type FixedRootMatcher<Target, T> =
StaticMap<bool, Rc<CritRoot<Target, CritRootFixed<Target, T>>>>;
#[derive(Clone)]
pub enum CritLiteralOrRegex {
Literal(String),
Regex(Regex),
}
impl CritLiteralOrRegex {
fn matches(&self, string: &str) -> bool {
match self {
CritLiteralOrRegex::Literal(p) => string == p,
CritLiteralOrRegex::Regex(r) => r.is_match(string),
}
}
}
pub trait CritMgrExt: CritMgr {
fn list(
&self,
upstream: &[Rc<dyn CritUpstreamNode<Self::Target>>],
all: bool,
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
if upstream.is_empty() {
return self.match_constant()[all].clone();
}
CritMiddle::new(self, upstream, CritMatchAnyOrAll::new(upstream, all))
}
fn exactly(
&self,
upstream: &[Rc<dyn CritUpstreamNode<Self::Target>>],
num: usize,
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
if num > upstream.len() {
return self.match_constant()[false].clone();
}
if num == 0 {
let upstream: Vec<_> = upstream.iter().map(|u| u.not(self)).collect();
return self.list(&upstream, true);
}
CritMiddle::new(self, upstream, CritMatchExactly::new(upstream, num))
}
fn leaf(
&self,
upstream: &Rc<dyn CritUpstreamNode<Self::Target>>,
on_match: impl Fn(<Self::Target as CritTarget>::LeafData) -> Box<dyn FnOnce()> + 'static,
) -> Rc<CritLeafMatcher<Self::Target>> {
CritLeafMatcher::new(self, upstream, on_match)
}
fn not(
&self,
upstream: &Rc<dyn CritUpstreamNode<Self::Target>>,
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
upstream.not(self)
}
fn root<T>(&self, criterion: T) -> Rc<dyn CritUpstreamNode<Self::Target>>
where
T: CritRootCriterion<Self::Target>,
{
CritRoot::new(self.roots(), self.id(), criterion)
}
}
impl<T> CritMgrExt for T where T: CritMgr {}

10
crates/damage/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "jay-damage"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-geometry = { version = "0.1.0", path = "../geometry" }
jay-tree-types = { version = "0.1.0", path = "../tree-types" }
jay-units = { version = "0.1.0", path = "../units" }

116
crates/damage/src/lib.rs Normal file
View file

@ -0,0 +1,116 @@
use {
jay_geometry::Rect,
jay_tree_types::Transform,
jay_units::fixed::Fixed,
};
#[derive(Copy, Clone, Debug)]
pub struct DamageMatrix {
transform: Transform,
mx: f64,
my: f64,
dx: f64,
dy: f64,
smear: i32,
}
impl Default for DamageMatrix {
fn default() -> Self {
Self {
transform: Default::default(),
mx: 1.0,
my: 1.0,
dx: 0.0,
dy: 0.0,
smear: 0,
}
}
}
impl DamageMatrix {
pub fn apply(&self, dx: i32, dy: i32, rect: Rect) -> Rect {
let x1 = rect.x1() - self.smear;
let x2 = rect.x2() + self.smear;
let y1 = rect.y1() - self.smear;
let y2 = rect.y2() + self.smear;
let [x1, y1, x2, y2] = match self.transform {
Transform::None => [x1, y1, x2, y2],
Transform::Rotate90 => [-y2, x1, -y1, x2],
Transform::Rotate180 => [-x2, -y2, -x1, -y1],
Transform::Rotate270 => [y1, -x2, y2, -x1],
Transform::Flip => [-x2, y1, -x1, y2],
Transform::FlipRotate90 => [y1, x1, y2, x2],
Transform::FlipRotate180 => [x1, -y2, x2, -y1],
Transform::FlipRotate270 => [-y2, -x2, -y1, -x1],
};
let x1 = (x1 as f64 * self.mx + self.dx).floor() as i32 + dx;
let y1 = (y1 as f64 * self.my + self.dy).floor() as i32 + dy;
let x2 = (x2 as f64 * self.mx + self.dx).ceil() as i32 + dx;
let y2 = (y2 as f64 * self.my + self.dy).ceil() as i32 + dy;
Rect::new_saturating(x1, y1, x2, y2)
}
pub fn new(
transform: Transform,
legacy_scale: i32,
buffer_width: i32,
buffer_height: i32,
viewport: Option<[Fixed; 4]>,
dst_width: i32,
dst_height: i32,
) -> DamageMatrix {
let mut buffer_width = buffer_width as f64;
let mut buffer_height = buffer_height as f64;
let dst_width = dst_width as f64;
let dst_height = dst_height as f64;
let mut mx = 1.0;
let mut my = 1.0;
if legacy_scale != 1 {
let scale_inv = 1.0 / (legacy_scale as f64);
mx = scale_inv;
my = scale_inv;
buffer_width *= scale_inv;
buffer_height *= scale_inv;
}
let (mut buffer_width, mut buffer_height) =
transform.maybe_swap((buffer_width, buffer_height));
let (mut dx, mut dy) = match transform {
Transform::None => (0.0, 0.0),
Transform::Rotate90 => (buffer_width, 0.0),
Transform::Rotate180 => (buffer_width, buffer_height),
Transform::Rotate270 => (0.0, buffer_height),
Transform::Flip => (buffer_width, 0.0),
Transform::FlipRotate90 => (0.0, 0.0),
Transform::FlipRotate180 => (0.0, buffer_height),
Transform::FlipRotate270 => (buffer_width, buffer_height),
};
if let Some([x, y, w, h]) = viewport {
dx -= x.to_f64();
dy -= y.to_f64();
buffer_width = w.to_f64();
buffer_height = h.to_f64();
}
let mut smear = false;
if dst_width != buffer_width {
let scale = dst_width / buffer_width;
mx *= scale;
dx *= scale;
smear |= dst_width > buffer_width;
}
if dst_height != buffer_height {
let scale = dst_height / buffer_height;
my *= scale;
dy *= scale;
smear |= dst_height > buffer_height;
}
DamageMatrix {
transform,
mx,
my,
dx,
dy,
smear: smear as _,
}
}
}

View file

@ -0,0 +1,14 @@
[package]
name = "jay-dbus-core"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-bufio = { version = "0.1.0", path = "../bufio" }
jay-io-uring = { version = "0.1.0", path = "../io-uring" }
jay-utils = { version = "0.1.0", path = "../utils" }
bstr = { version = "1.9.0", default-features = false, features = ["std"] }
thiserror = "2.0.11"
uapi = "0.2.13"

View file

@ -0,0 +1,181 @@
use {
super::{
TY_ARRAY, TY_BOOLEAN, TY_BYTE, TY_DOUBLE, TY_INT16, TY_INT32, TY_INT64, TY_OBJECT_PATH,
TY_SIGNATURE, TY_STRING, TY_UINT16, TY_UINT32, TY_UINT64, TY_UNIX_FD, TY_VARIANT,
},
crate::{types::Variant, DbusError, DynamicType, Parser},
jay_utils::buf::DynamicBuf,
std::ops::Deref,
};
impl DynamicType {
pub fn from_signature<'a>(mut s: &'a [u8]) -> Result<(DynamicType, &'a [u8]), DbusError> {
if s.is_empty() {
return Err(DbusError::EmptySignature);
}
let first = s[0];
s = &s[1..];
let dp = match first {
TY_BYTE => DynamicType::U8,
TY_BOOLEAN => DynamicType::Bool,
TY_INT16 => DynamicType::I16,
TY_UINT16 => DynamicType::U16,
TY_INT32 => DynamicType::I32,
TY_UINT32 => DynamicType::U32,
TY_INT64 => DynamicType::I64,
TY_UINT64 => DynamicType::U64,
TY_DOUBLE => DynamicType::F64,
TY_STRING => DynamicType::String,
TY_OBJECT_PATH => DynamicType::ObjectPath,
TY_SIGNATURE => DynamicType::Signature,
TY_VARIANT => DynamicType::Variant,
TY_UNIX_FD => DynamicType::Fd,
TY_ARRAY => {
let (elty, rem) = Self::from_signature(s)?;
s = rem;
DynamicType::Array(Box::new(elty))
}
b'{' => {
let (keyty, rem) = Self::from_signature(s)?;
let (valty, rem) = Self::from_signature(rem)?;
if rem.is_empty() {
return Err(DbusError::UnterminatedDict);
}
if rem[0] != b'}' {
return Err(DbusError::DictTrailing);
}
s = &rem[1..];
DynamicType::DictEntry(Box::new(keyty), Box::new(valty))
}
b'(' => {
let mut fields = vec![];
loop {
if s.is_empty() {
return Err(DbusError::UnterminatedStruct);
}
if s[0] == b')' {
s = &s[1..];
break DynamicType::Struct(fields);
}
let (fieldty, rem) = Self::from_signature(s)?;
s = rem;
fields.push(fieldty);
}
}
_ => return Err(DbusError::UnknownType),
};
Ok((dp, s))
}
pub fn alignment(&self) -> usize {
match self {
DynamicType::U8 => 1,
DynamicType::Bool => 4,
DynamicType::I16 => 2,
DynamicType::U16 => 2,
DynamicType::I32 => 4,
DynamicType::U32 => 4,
DynamicType::I64 => 8,
DynamicType::U64 => 8,
DynamicType::F64 => 8,
DynamicType::String => 4,
DynamicType::ObjectPath => 4,
DynamicType::Signature => 1,
DynamicType::Variant => 1,
DynamicType::Array(_) => 4,
DynamicType::DictEntry(_, _) => 8,
DynamicType::Struct(_) => 8,
DynamicType::Fd => 4,
}
}
pub fn write_signature(&self, w: &mut DynamicBuf) {
let c = match self {
DynamicType::U8 => TY_BYTE,
DynamicType::Bool => TY_BOOLEAN,
DynamicType::I16 => TY_INT16,
DynamicType::U16 => TY_UINT16,
DynamicType::I32 => TY_INT32,
DynamicType::U32 => TY_UINT32,
DynamicType::I64 => TY_INT64,
DynamicType::U64 => TY_UINT64,
DynamicType::F64 => TY_DOUBLE,
DynamicType::String => TY_STRING,
DynamicType::ObjectPath => TY_OBJECT_PATH,
DynamicType::Signature => TY_SIGNATURE,
DynamicType::Variant => TY_VARIANT,
DynamicType::Fd => TY_UNIX_FD,
DynamicType::Array(el) => {
w.push(TY_ARRAY);
el.write_signature(w);
return;
}
DynamicType::DictEntry(k, v) => {
w.push(b'{');
k.write_signature(w);
v.write_signature(w);
w.push(b'}');
return;
}
DynamicType::Struct(f) => {
w.push(b'(');
for f in f {
f.write_signature(w);
}
w.push(b')');
return;
}
};
w.push(c);
}
pub fn parse<'a>(&self, parser: &mut Parser<'a>) -> Result<Variant<'a>, DbusError> {
let var = match self {
DynamicType::U8 => Variant::U8(parser.read_pod()?),
DynamicType::Bool => Variant::Bool(parser.read_bool()?),
DynamicType::I16 => Variant::I16(parser.read_pod()?),
DynamicType::U16 => Variant::U16(parser.read_pod()?),
DynamicType::I32 => Variant::I32(parser.read_pod()?),
DynamicType::U32 => Variant::U32(parser.read_pod()?),
DynamicType::I64 => Variant::I64(parser.read_pod()?),
DynamicType::U64 => Variant::U64(parser.read_pod()?),
DynamicType::F64 => Variant::F64(f64::from_bits(parser.read_pod()?)),
DynamicType::String => Variant::String(parser.read_string()?),
DynamicType::ObjectPath => Variant::ObjectPath(parser.read_object_path()?),
DynamicType::Signature => Variant::Signature(parser.read_signature()?),
DynamicType::Variant => Variant::Variant(Box::new(parser.read_variant()?)),
DynamicType::Fd => Variant::Fd(parser.read_fd()?),
DynamicType::Array(el) => {
let len: u32 = parser.read_pod()?;
parser.align_to(el.alignment())?;
let len = len as usize;
if parser.buf.len() - parser.pos < len {
return Err(DbusError::UnexpectedEof);
}
let mut vals = vec![];
{
let mut parser =
Parser::new_at(&parser.buf[..parser.pos + len], parser.pos, parser.fds);
while !parser.eof() {
vals.push(el.parse(&mut parser)?);
}
}
parser.pos += len;
Variant::Array(el.deref().clone(), vals)
}
DynamicType::DictEntry(k, v) => {
parser.align_to(8)?;
Variant::DictEntry(Box::new(k.parse(parser)?), Box::new(v.parse(parser)?))
}
DynamicType::Struct(fields) => {
let mut vals = vec![];
parser.align_to(8)?;
for field in fields {
vals.push(field.parse(parser)?);
}
Variant::Struct(vals)
}
};
Ok(var)
}
}

View file

@ -0,0 +1,112 @@
use {
crate::{types::Variant, DbusType, Formatter},
jay_utils::buf::DynamicBuf,
std::rc::Rc,
uapi::{OwnedFd, Packed},
};
impl<'a> Formatter<'a> {
pub fn new(fds: &'a mut Vec<Rc<OwnedFd>>, buf: &'a mut DynamicBuf) -> Self {
Self { fds, buf }
}
pub fn marshal<'b, T: DbusType<'b>>(&mut self, t: &T) {
t.marshal(self)
}
pub fn pad_to(&mut self, alignment: usize) {
static BUF: [u8; 8] = [0; 8];
let len = self.buf.len().wrapping_neg() & (alignment - 1);
self.buf.extend_from_slice(&BUF[..len]);
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn write_packed<'b, T: DbusType<'b> + Packed>(&mut self, t: &T) {
self.pad_to(T::ALIGNMENT);
self.buf.extend_from_slice(uapi::as_bytes(t));
}
pub fn write_str(&mut self, s: &str) {
self.write_packed(&(s.len() as u32));
self.buf.extend_from_slice(s.as_bytes());
self.buf.push(0);
}
pub fn write_signature(&mut self, s: &[u8]) {
self.write_packed(&(s.len() as u8));
self.buf.extend_from_slice(s);
self.buf.push(0);
}
pub fn write_array<'b, T: DbusType<'b>>(&mut self, a: &[T]) {
self.pad_to(4);
let len_pos = self.buf.len();
self.write_packed(&0u32);
self.pad_to(T::ALIGNMENT);
let start = self.buf.len();
for v in a {
v.marshal(self);
}
let len = (self.buf.len() - start) as u32;
self.buf[len_pos..len_pos + 4].copy_from_slice(uapi::as_bytes(&len));
}
pub fn write_fd(&mut self, fd: &Rc<OwnedFd>) {
self.write_packed(&(self.fds.len() as u32));
self.fds.push(fd.clone());
}
pub fn write_variant(&mut self, variant: &Variant) {
let pos = self.buf.len();
self.buf.push(0);
variant.write_signature(self.buf);
self.buf.push(0);
self.buf[pos] = (self.buf.len() - pos - 2) as u8;
self.write_variant_body(variant);
}
pub fn write_variant_body(&mut self, variant: &Variant) {
match variant {
Variant::U8(v) => v.marshal(self),
Variant::Bool(v) => v.marshal(self),
Variant::I16(v) => v.marshal(self),
Variant::U16(v) => v.marshal(self),
Variant::I32(v) => v.marshal(self),
Variant::U32(v) => v.marshal(self),
Variant::I64(v) => v.marshal(self),
Variant::U64(v) => v.marshal(self),
Variant::F64(v) => v.marshal(self),
Variant::String(v) => v.marshal(self),
Variant::ObjectPath(v) => v.marshal(self),
Variant::Signature(v) => v.marshal(self),
Variant::Variant(v) => v.marshal(self),
Variant::Fd(f) => f.marshal(self),
Variant::Array(el, v) => {
self.pad_to(4);
let len_pos = self.buf.len();
self.write_packed(&0u32);
self.pad_to(el.alignment());
let start = self.buf.len();
for v in v {
self.write_variant_body(v);
}
let len = (self.buf.len() - start) as u32;
self.buf[len_pos..len_pos + 4].copy_from_slice(uapi::as_bytes(&len));
}
Variant::DictEntry(k, v) => {
self.pad_to(8);
self.write_variant_body(k);
self.write_variant_body(v);
}
Variant::Struct(f) => {
self.pad_to(8);
for v in f {
self.write_variant_body(v);
}
}
}
}
}

232
crates/dbus-core/src/lib.rs Normal file
View file

@ -0,0 +1,232 @@
pub use {property::{Get, GetReply}, types::*};
use {
jay_bufio::BufIoError,
jay_io_uring::IoUringError,
jay_utils::{buf::DynamicBuf, oserror::OsError},
std::{borrow::Cow, fmt::Display, rc::Rc},
thiserror::Error,
uapi::OwnedFd,
};
mod dynamic_type;
mod formatter;
mod parser;
pub mod property;
pub mod types;
#[derive(Debug)]
pub struct CallError {
pub name: String,
pub msg: Option<String>,
}
impl Display for CallError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(msg) = &self.msg {
write!(f, "{}: {}", self.name, msg)
} else {
write!(f, "{}", self.name)
}
}
}
#[derive(Debug, Error)]
pub enum DbusError {
#[error("Encountered an unknown type in a signature")]
UnknownType,
#[error("Function call reply does not contain a reply serial")]
NoReplySerial,
#[error("Signal message contains no interface or member or path")]
MissingSignalHeaders,
#[error("Method call message contains no interface or member or path")]
MissingMethodCallHeaders,
#[error("Error has no error name")]
NoErrorName,
#[error("The socket was killed")]
Killed,
#[error("{0}")]
CallError(CallError),
#[error("FD index is out of bounds")]
OobFds,
#[error("Variant has an invalid type")]
InvalidVariantType,
#[error("Could not create a socket")]
Socket(#[source] OsError),
#[error("Could not connect")]
Connect(#[source] IoUringError),
#[error("Could not write to the dbus socket")]
WriteError(#[source] IoUringError),
#[error("Could not read from the dbus socket")]
ReadError(#[source] IoUringError),
#[error("timeout")]
IoUringError(#[source] Box<IoUringError>),
#[error("Server did not send auth challenge")]
NoChallenge,
#[error("Server did not accept our authentication")]
Auth,
#[error("Array length is not a multiple of the element size")]
PodArrayLength,
#[error("Peer did not send enough fds")]
TooFewFds,
#[error("Variant signature is not a single type")]
TrailingVariantSignature,
#[error("Dict signature does not contain a terminating '}}'")]
UnterminatedDict,
#[error("Struct signature does not contain a terminating '}}'")]
UnterminatedStruct,
#[error("Dict signature contains trailing types")]
DictTrailing,
#[error("String does not contain valid UTF-8")]
InvalidUtf8,
#[error("Unexpected end of message")]
UnexpectedEof,
#[error("Boolean value was not 0 or 1")]
InvalidBoolValue,
#[error("Signature is empty")]
EmptySignature,
#[error("The session bus address is not set")]
SessionBusAddressNotSet,
#[error("Server does not support FD passing")]
UnixFd,
#[error("Server message has a different endianess than ourselves")]
InvalidEndianess,
#[error("Server speaks an unexpected protocol version")]
InvalidProtocol,
#[error("Signature contains an invalid type")]
InvalidSignatureType,
#[error("The signal already has a handler")]
AlreadyHandled,
#[error(transparent)]
BufIoError(#[from] BufIoError),
#[error(transparent)]
DbusError(Rc<DbusError>),
}
impl From<IoUringError> for DbusError {
fn from(e: IoUringError) -> Self {
DbusError::IoUringError(Box::new(e))
}
}
const TY_BYTE: u8 = b'y';
const TY_BOOLEAN: u8 = b'b';
const TY_INT16: u8 = b'n';
const TY_UINT16: u8 = b'q';
const TY_INT32: u8 = b'i';
const TY_UINT32: u8 = b'u';
const TY_INT64: u8 = b'x';
const TY_UINT64: u8 = b't';
const TY_DOUBLE: u8 = b'd';
const TY_STRING: u8 = b's';
const TY_OBJECT_PATH: u8 = b'o';
const TY_SIGNATURE: u8 = b'g';
const TY_ARRAY: u8 = b'a';
const TY_VARIANT: u8 = b'v';
const TY_UNIX_FD: u8 = b'h';
#[derive(Clone, Debug)]
pub enum DynamicType {
U8,
Bool,
I16,
U16,
I32,
U32,
I64,
U64,
F64,
String,
ObjectPath,
Signature,
Variant,
Fd,
Array(Box<DynamicType>),
DictEntry(Box<DynamicType>, Box<DynamicType>),
Struct(Vec<DynamicType>),
}
pub struct Parser<'a> {
pub(crate) buf: &'a [u8],
pub(crate) pos: usize,
pub(crate) fds: &'a [Rc<OwnedFd>],
}
pub struct Formatter<'a> {
fds: &'a mut Vec<Rc<OwnedFd>>,
buf: &'a mut DynamicBuf,
}
pub unsafe trait Message<'a>: Sized + 'a {
const SIGNATURE: &'static str;
const INTERFACE: &'static str;
const MEMBER: &'static str;
type Generic<'b>: Message<'b>;
fn marshal(&self, w: &mut Formatter);
fn unmarshal(p: &mut Parser<'a>) -> Result<Self, DbusError>;
fn num_fds(&self) -> u32;
}
pub struct ErrorMessage<'a> {
pub msg: Cow<'a, str>,
}
unsafe impl<'a> Message<'a> for ErrorMessage<'a> {
const SIGNATURE: &'static str = "s";
const INTERFACE: &'static str = "";
const MEMBER: &'static str = "";
type Generic<'b> = ErrorMessage<'b>;
fn marshal(&self, w: &mut Formatter) {
self.msg.marshal(w)
}
fn unmarshal(p: &mut Parser<'a>) -> Result<Self, DbusError> {
Ok(Self {
msg: p.unmarshal()?,
})
}
fn num_fds(&self) -> u32 {
0
}
}
pub trait Property {
const INTERFACE: &'static str;
const PROPERTY: &'static str;
type Type: DbusType<'static>;
}
pub trait Signal<'a>: Message<'a> {}
pub trait MethodCall<'a>: Message<'a> {
type Reply: Message<'static>;
}
pub unsafe trait DbusType<'a>: Clone + 'a {
const ALIGNMENT: usize;
const IS_POD: bool;
type Generic<'b>: DbusType<'b> + 'b;
fn consume_signature(s: &mut &[u8]) -> Result<(), DbusError>;
#[allow(clippy::allow_attributes, dead_code)]
fn write_signature(w: &mut Vec<u8>);
fn marshal(&self, fmt: &mut Formatter);
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError>;
fn num_fds(&self) -> u32 {
0
}
}
pub mod prelude {
pub use {
super::{
DbusError, DbusType, Formatter, Message, MethodCall, Parser, Property, Signal,
types::{Bool, DictEntry, ObjectPath, Variant},
},
std::{borrow::Cow, rc::Rc},
uapi::OwnedFd,
};
}

View file

@ -0,0 +1,143 @@
use {
crate::{
types::{Bool, ObjectPath, Signature, Variant, FALSE, TRUE},
DbusError, DbusType, DynamicType, Parser,
},
bstr::ByteSlice,
std::{borrow::Cow, rc::Rc},
uapi::{OwnedFd, Pod},
};
impl<'a> Parser<'a> {
pub fn new(buf: &'a [u8], fds: &'a [Rc<OwnedFd>]) -> Self {
Self::new_at(buf, 0, fds)
}
pub fn new_at(buf: &'a [u8], pos: usize, fds: &'a [Rc<OwnedFd>]) -> Self {
Self { buf, pos, fds }
}
pub fn eof(&self) -> bool {
self.pos == self.buf.len()
}
pub fn unmarshal<T: DbusType<'a>>(&mut self) -> Result<T, DbusError> {
T::unmarshal(self)
}
pub fn align_to(&mut self, n: usize) -> Result<(), DbusError> {
let new = self.pos + (self.pos.wrapping_neg() & (n - 1));
if new > self.buf.len() {
return Err(DbusError::UnexpectedEof);
}
self.pos = new;
Ok(())
}
pub fn read_fd(&mut self) -> Result<Rc<OwnedFd>, DbusError> {
let idx: u32 = self.read_pod()?;
let idx = idx as usize;
if idx >= self.fds.len() {
return Err(DbusError::OobFds);
}
Ok(self.fds[idx].clone())
}
pub fn read_pod<'b, T: DbusType<'b> + Pod>(&mut self) -> Result<T, DbusError> {
self.align_to(T::ALIGNMENT)?;
match uapi::pod_read_init(&self.buf[self.pos..]) {
Ok(v) => {
self.pos += size_of::<T>();
Ok(v)
}
_ => Err(DbusError::UnexpectedEof),
}
}
pub fn read_bool(&mut self) -> Result<Bool, DbusError> {
let v: u32 = self.read_pod()?;
match v {
0 => Ok(FALSE),
1 => Ok(TRUE),
_ => Err(DbusError::InvalidBoolValue),
}
}
pub fn read_object_path(&mut self) -> Result<ObjectPath<'a>, DbusError> {
self.read_string().map(ObjectPath)
}
pub fn read_string(&mut self) -> Result<Cow<'a, str>, DbusError> {
let len: u32 = self.read_pod()?;
let s = self.read_string_(len as usize)?;
Ok(Cow::Borrowed(s))
}
pub fn read_signature(&mut self) -> Result<Signature<'a>, DbusError> {
let len: u8 = self.read_pod()?;
let s = self.read_string_(len as usize)?;
Ok(Signature(Cow::Borrowed(s)))
}
fn read_string_(&mut self, len: usize) -> Result<&'a str, DbusError> {
if len == usize::MAX || self.buf.len() - self.pos < len + 1 {
return Err(DbusError::UnexpectedEof);
}
let s = &self.buf[self.pos..self.pos + len];
self.pos += len + 1;
match s.to_str() {
Ok(s) => Ok(s),
_ => Err(DbusError::InvalidUtf8),
}
}
pub fn read_array<T: DbusType<'a>>(&mut self) -> Result<Cow<'a, [T]>, DbusError> {
let len: u32 = self.read_pod()?;
let len = len as usize;
self.align_to(T::ALIGNMENT)?;
if self.buf.len() - self.pos < len {
return Err(DbusError::UnexpectedEof);
}
if T::IS_POD {
if len % size_of::<T>() != 0 {
return Err(DbusError::PodArrayLength);
}
let slice = unsafe {
std::slice::from_raw_parts(
self.buf[self.pos..].as_ptr() as *const T,
len / size_of::<T>(),
)
};
self.pos += len;
Ok(Cow::Borrowed(slice))
} else {
let mut parser = Parser::new_at(&self.buf[..self.pos + len], self.pos, self.fds);
self.pos += len;
let mut res = vec![];
while !parser.eof() {
res.push(T::unmarshal(&mut parser)?);
}
Ok(Cow::Owned(res))
}
}
pub fn read_variant(&mut self) -> Result<Variant<'a>, DbusError> {
let sig = self.read_signature()?;
let sig = sig.0.as_bytes();
let (parser, rem) = DynamicType::from_signature(sig)?;
if rem.len() > 0 {
return Err(DbusError::TrailingVariantSignature);
}
parser.parse(self)
}
pub fn read_variant_as<T: DbusType<'a>>(&mut self) -> Result<T, DbusError> {
let sig = self.read_signature()?;
let mut sig = sig.0.as_bytes();
T::consume_signature(&mut sig)?;
if sig.len() > 0 {
return Err(DbusError::TrailingVariantSignature);
}
T::unmarshal(self)
}
}

View file

@ -0,0 +1,67 @@
use {
crate::{DbusError, DbusType, Formatter, Message, MethodCall, Parser},
std::{borrow::Cow, marker::PhantomData},
};
#[derive(Debug)]
pub struct Get<'a, T: DbusType<'static>> {
pub interface_name: Cow<'a, str>,
pub property_name: Cow<'a, str>,
pub _phantom: PhantomData<T>,
}
unsafe impl<'a, T: DbusType<'static>> Message<'a> for Get<'a, T> {
const SIGNATURE: &'static str = "ss";
const INTERFACE: &'static str = "org.freedesktop.DBus.Properties";
const MEMBER: &'static str = "Get";
type Generic<'b> = Get<'b, T>;
fn marshal(&self, fmt: &mut Formatter) {
fmt.marshal(&self.interface_name);
fmt.marshal(&self.property_name);
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
Ok(Self {
interface_name: parser.unmarshal()?,
property_name: parser.unmarshal()?,
_phantom: Default::default(),
})
}
fn num_fds(&self) -> u32 {
0
}
}
impl<'a, T: DbusType<'static>> MethodCall<'a> for Get<'a, T> {
type Reply = GetReply<'static, T>;
}
#[derive(Debug)]
pub struct GetReply<'a, T: DbusType<'a>> {
pub value: T,
pub _phantom: PhantomData<&'a ()>,
}
unsafe impl<'a, T: DbusType<'a>> Message<'a> for GetReply<'a, T> {
const SIGNATURE: &'static str = "v";
const INTERFACE: &'static str = "org.freedesktop.DBus.Properties";
const MEMBER: &'static str = "Get";
type Generic<'b> = GetReply<'b, T::Generic<'b>>;
fn marshal(&self, _fmt: &mut Formatter) {
unimplemented!();
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
Ok(Self {
value: parser.read_variant_as()?,
_phantom: Default::default(),
})
}
fn num_fds(&self) -> u32 {
self.value.num_fds()
}
}

View file

@ -0,0 +1,540 @@
use {
crate::{
DbusError, DbusType, DynamicType, Formatter, Parser, TY_ARRAY, TY_BOOLEAN, TY_BYTE,
TY_DOUBLE, TY_INT16, TY_INT32, TY_INT64, TY_OBJECT_PATH, TY_SIGNATURE, TY_STRING,
TY_UINT16, TY_UINT32, TY_UINT64, TY_UNIX_FD, TY_VARIANT,
},
jay_utils::buf::DynamicBuf,
std::{borrow::Cow, ops::Deref, rc::Rc},
uapi::{OwnedFd, Packed, Pod},
};
macro_rules! consume_signature_body {
($s:expr, $ty:expr) => {{
if $s.is_empty() {
return Err(DbusError::EmptySignature);
}
if $s[0] != $ty {
return Err(DbusError::InvalidSignatureType);
}
*$s = &(*$s)[1..];
}};
}
macro_rules! signature {
($ty:expr) => {
fn consume_signature(s: &mut &[u8]) -> Result<(), DbusError> {
consume_signature_body!(s, $ty);
Ok(())
}
fn write_signature(w: &mut Vec<u8>) {
w.push(TY_BYTE);
}
};
}
unsafe impl<'a> DbusType<'a> for u8 {
const ALIGNMENT: usize = 1;
const IS_POD: bool = true;
type Generic<'b> = Self;
signature!(TY_BYTE);
fn marshal(&self, fmt: &mut Formatter) {
fmt.write_packed(self);
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
parser.read_pod()
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct Bool(u32);
pub const FALSE: Bool = Bool(0);
pub const TRUE: Bool = Bool(1);
unsafe impl Pod for Bool {}
unsafe impl Packed for Bool {}
unsafe impl<'a> DbusType<'a> for Bool {
const ALIGNMENT: usize = 4;
const IS_POD: bool = true;
type Generic<'b> = Self;
signature!(TY_BOOLEAN);
fn marshal(&self, fmt: &mut Formatter) {
fmt.write_packed(self);
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
parser.read_bool()
}
}
unsafe impl<'a> DbusType<'a> for i16 {
const ALIGNMENT: usize = 2;
const IS_POD: bool = true;
type Generic<'b> = Self;
signature!(TY_INT16);
fn marshal(&self, fmt: &mut Formatter) {
fmt.write_packed(self);
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
parser.read_pod()
}
}
unsafe impl<'a> DbusType<'a> for u16 {
const ALIGNMENT: usize = 2;
const IS_POD: bool = true;
type Generic<'b> = Self;
signature!(TY_UINT16);
fn marshal(&self, fmt: &mut Formatter) {
fmt.write_packed(self);
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
parser.read_pod()
}
}
unsafe impl<'a> DbusType<'a> for i32 {
const ALIGNMENT: usize = 4;
const IS_POD: bool = true;
type Generic<'b> = Self;
signature!(TY_INT32);
fn marshal(&self, fmt: &mut Formatter) {
fmt.write_packed(self);
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
parser.read_pod()
}
}
unsafe impl<'a> DbusType<'a> for u32 {
const ALIGNMENT: usize = 4;
const IS_POD: bool = true;
type Generic<'b> = Self;
signature!(TY_UINT32);
fn marshal(&self, fmt: &mut Formatter) {
fmt.write_packed(self);
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
parser.read_pod()
}
}
unsafe impl<'a> DbusType<'a> for i64 {
const ALIGNMENT: usize = 8;
const IS_POD: bool = true;
type Generic<'b> = Self;
signature!(TY_INT64);
fn marshal(&self, fmt: &mut Formatter) {
fmt.write_packed(self);
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
parser.read_pod()
}
}
unsafe impl<'a> DbusType<'a> for u64 {
const ALIGNMENT: usize = 8;
const IS_POD: bool = true;
type Generic<'b> = Self;
signature!(TY_UINT64);
fn marshal(&self, fmt: &mut Formatter) {
fmt.write_packed(self);
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
parser.read_pod()
}
}
unsafe impl<'a> DbusType<'a> for f64 {
const ALIGNMENT: usize = 8;
const IS_POD: bool = true;
type Generic<'b> = Self;
signature!(TY_DOUBLE);
fn marshal(&self, fmt: &mut Formatter) {
fmt.write_packed(&self.to_bits());
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
Ok(f64::from_bits(parser.read_pod()?))
}
}
unsafe impl<'a> DbusType<'a> for Cow<'a, str> {
const ALIGNMENT: usize = 4;
const IS_POD: bool = false;
type Generic<'b> = Cow<'b, str>;
signature!(TY_STRING);
fn marshal(&self, fmt: &mut Formatter) {
fmt.write_str(self);
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
parser.read_string()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Signature<'a>(pub Cow<'a, str>);
impl<'a> Deref for Signature<'a> {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
unsafe impl<'a> DbusType<'a> for Signature<'a> {
const ALIGNMENT: usize = 1;
const IS_POD: bool = false;
type Generic<'b> = Signature<'b>;
signature!(TY_SIGNATURE);
fn marshal(&self, fmt: &mut Formatter) {
fmt.write_signature(self.0.as_bytes());
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
parser.read_signature()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ObjectPath<'a>(pub Cow<'a, str>);
impl<'a> Deref for ObjectPath<'a> {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
unsafe impl<'a> DbusType<'a> for ObjectPath<'a> {
const ALIGNMENT: usize = 4;
const IS_POD: bool = false;
type Generic<'b> = ObjectPath<'b>;
signature!(TY_OBJECT_PATH);
fn marshal(&self, fmt: &mut Formatter) {
fmt.write_str(&self.0);
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
parser.read_object_path()
}
}
unsafe impl<'a> DbusType<'a> for Rc<OwnedFd> {
const ALIGNMENT: usize = 4;
const IS_POD: bool = false;
type Generic<'b> = Self;
signature!(TY_UNIX_FD);
fn marshal(&self, fmt: &mut Formatter) {
fmt.write_fd(self)
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
parser.read_fd()
}
fn num_fds(&self) -> u32 {
1
}
}
unsafe impl<'a, T: DbusType<'a>> DbusType<'a> for Cow<'a, [T]> {
const ALIGNMENT: usize = 4;
const IS_POD: bool = false;
type Generic<'b> = Cow<'b, [T::Generic<'b>]>;
fn consume_signature(s: &mut &[u8]) -> Result<(), DbusError> {
consume_signature_body!(s, TY_ARRAY);
T::consume_signature(s)
}
fn write_signature(w: &mut Vec<u8>) {
w.push(TY_ARRAY);
T::write_signature(w);
}
fn marshal(&self, fmt: &mut Formatter) {
fmt.write_array(self);
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
parser.read_array()
}
fn num_fds(&self) -> u32 {
let mut res = 0;
for t in self.deref() {
res += t.num_fds();
}
res
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DictEntry<K, V> {
pub key: K,
pub value: V,
}
unsafe impl<'a, K: DbusType<'a>, V: DbusType<'a>> DbusType<'a> for DictEntry<K, V> {
const ALIGNMENT: usize = 8;
const IS_POD: bool = false;
type Generic<'b> = DictEntry<K::Generic<'b>, V::Generic<'b>>;
fn consume_signature(s: &mut &[u8]) -> Result<(), DbusError> {
consume_signature_body!(s, b'{');
K::consume_signature(s)?;
V::consume_signature(s)?;
consume_signature_body!(s, b'}');
Ok(())
}
fn write_signature(w: &mut Vec<u8>) {
w.push(b'{');
K::write_signature(w);
V::write_signature(w);
w.push(b'}');
}
fn marshal(&self, fmt: &mut Formatter) {
fmt.pad_to(8);
self.key.marshal(fmt);
self.value.marshal(fmt);
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
parser.align_to(8)?;
Ok(Self {
key: K::unmarshal(parser)?,
value: V::unmarshal(parser)?,
})
}
}
macro_rules! tuple {
($($p:ident),*) => {
#[expect(non_snake_case)]
unsafe impl<'a, $($p: DbusType<'a>),*> DbusType<'a> for ($($p,)*) {
const ALIGNMENT: usize = 8;
const IS_POD: bool = false;
type Generic<'b> = ($($p::Generic<'b>,)*);
fn consume_signature(s: &mut &[u8]) -> Result<(), DbusError> {
consume_signature_body!(s, b'(');
$(
$p::consume_signature(s)?;
)*
consume_signature_body!(s, b')');
Ok(())
}
fn write_signature(w: &mut Vec<u8>) {
w.push(b'(');
$(
$p::write_signature(w);
)*
w.push(b')');
}
fn marshal(&self, fmt: &mut Formatter) {
let ($($p,)*) = self;
fmt.pad_to(8);
$(
$p.marshal(fmt);
)*
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
parser.align_to(8)?;
Ok(($($p::unmarshal(parser)?,)*))
}
fn num_fds(&self) -> u32 {
let mut res = 0;
let ($($p,)*) = self;
$(
res += $p.num_fds();
)*
res
}
}
}
}
tuple!(A);
tuple!(A, B);
tuple!(A, B, C);
tuple!(A, B, C, D);
tuple!(A, B, C, D, E);
tuple!(A, B, C, D, E, F);
tuple!(A, B, C, D, E, F, G);
tuple!(A, B, C, D, E, F, G, H);
#[derive(Clone, Debug)]
pub enum Variant<'a> {
U8(u8),
Bool(Bool),
I16(i16),
U16(u16),
I32(i32),
U32(u32),
I64(i64),
U64(u64),
F64(f64),
String(Cow<'a, str>),
ObjectPath(ObjectPath<'a>),
Signature(Signature<'a>),
Variant(Box<Variant<'a>>),
Fd(Rc<OwnedFd>),
Array(DynamicType, Vec<Variant<'a>>),
DictEntry(Box<Variant<'a>>, Box<Variant<'a>>),
Struct(Vec<Variant<'a>>),
}
impl<'a> Variant<'a> {
pub fn into_string(self) -> Result<Cow<'a, str>, DbusError> {
match self {
Variant::String(s) => Ok(s),
_ => Err(DbusError::InvalidVariantType),
}
}
pub fn into_object_path(self) -> Result<ObjectPath<'a>, DbusError> {
match self {
Variant::ObjectPath(s) => Ok(s),
_ => Err(DbusError::InvalidVariantType),
}
}
pub fn into_signature(self) -> Result<Signature<'a>, DbusError> {
match self {
Variant::Signature(s) => Ok(s),
_ => Err(DbusError::InvalidVariantType),
}
}
pub fn into_u32(self) -> Result<u32, DbusError> {
match self {
Variant::U32(s) => Ok(s),
_ => Err(DbusError::InvalidVariantType),
}
}
pub fn write_signature(&self, w: &mut DynamicBuf) {
let c = match self {
Variant::U8(..) => TY_BYTE,
Variant::Bool(..) => TY_BOOLEAN,
Variant::I16(..) => TY_INT16,
Variant::U16(..) => TY_UINT16,
Variant::I32(..) => TY_INT32,
Variant::U32(..) => TY_UINT32,
Variant::I64(..) => TY_INT64,
Variant::U64(..) => TY_UINT64,
Variant::F64(..) => TY_DOUBLE,
Variant::String(..) => TY_STRING,
Variant::ObjectPath(..) => TY_OBJECT_PATH,
Variant::Signature(..) => TY_SIGNATURE,
Variant::Variant(..) => TY_VARIANT,
Variant::Fd(..) => TY_UNIX_FD,
Variant::Array(el, _) => {
w.push(TY_ARRAY);
el.write_signature(w);
return;
}
Variant::DictEntry(k, v) => {
w.push(b'{');
k.write_signature(w);
v.write_signature(w);
w.push(b'}');
return;
}
Variant::Struct(f) => {
w.push(b'(');
for f in f {
f.write_signature(w);
}
w.push(b')');
return;
}
};
w.push(c);
}
}
unsafe impl<'a> DbusType<'a> for Variant<'a> {
const ALIGNMENT: usize = 1;
const IS_POD: bool = false;
type Generic<'b> = Variant<'b>;
signature!(TY_VARIANT);
fn marshal(&self, fmt: &mut Formatter) {
fmt.write_variant(self);
}
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError> {
parser.read_variant()
}
fn num_fds(&self) -> u32 {
match self {
Variant::U8(_) => 0,
Variant::Bool(_) => 0,
Variant::I16(_) => 0,
Variant::U16(_) => 0,
Variant::I32(_) => 0,
Variant::U32(_) => 0,
Variant::I64(_) => 0,
Variant::U64(_) => 0,
Variant::F64(_) => 0,
Variant::String(_) => 0,
Variant::ObjectPath(_) => 0,
Variant::Signature(_) => 0,
Variant::Variant(v) => v.num_fds(),
Variant::Array(_, a) => a.iter().map(|e| e.num_fds()).sum(),
Variant::DictEntry(k, v) => k.num_fds() + v.num_fds(),
Variant::Struct(f) => f.iter().map(|f| f.num_fds()).sum(),
Variant::Fd(_) => 1,
}
}
}

View file

@ -0,0 +1,13 @@
[package]
name = "jay-drm-feedback"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-video-types = { version = "0.1.0", path = "../video-types" }
ahash = "0.8.7"
byteorder = "1.5.0"
thiserror = "2.0.11"
uapi = "0.2.13"

View file

@ -0,0 +1,149 @@
use {
ahash::AHashMap,
byteorder::{NativeEndian, WriteBytesExt},
jay_video_types::Modifier,
std::{io::Write, rc::Rc},
thiserror::Error,
uapi::{OwnedFd, c},
};
#[derive(Debug)]
pub struct DrmFeedbackIds {
next: std::cell::Cell<u64>,
}
impl Default for DrmFeedbackIds {
fn default() -> Self {
Self {
next: std::cell::Cell::new(1),
}
}
}
impl DrmFeedbackIds {
pub fn next(&self) -> DrmFeedbackId {
let id = self.next.get();
self.next.set(id + 1);
DrmFeedbackId(id)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct DrmFeedbackId(u64);
#[derive(Debug)]
pub struct DrmFeedbackShared {
pub fd: Rc<OwnedFd>,
pub size: usize,
pub main_device: c::dev_t,
pub indices: AHashMap<(u32, Modifier), u16>,
}
#[derive(Debug)]
pub struct DrmFeedback {
pub id: DrmFeedbackId,
pub shared: Rc<DrmFeedbackShared>,
pub tranches: Vec<DrmFeedbackTranche>,
}
#[derive(Clone, Debug)]
pub struct DrmFeedbackTranche {
pub device: c::dev_t,
pub indices: Vec<u16>,
pub scanout: bool,
}
impl DrmFeedback {
pub fn new<C: DrmFeedbackContext + ?Sized>(
ids: &DrmFeedbackIds,
render_ctx: &C,
) -> Result<Self, DrmFeedbackError> {
let main_device = match render_ctx.main_device() {
Some(dev) => dev,
_ => return Err(DrmFeedbackError::NoDrmDevice),
};
let (data, index_map) = create_fd_data(render_ctx);
let mut memfd =
uapi::memfd_create("drm_feedback", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING).unwrap();
memfd.write_all(&data).unwrap();
uapi::lseek(memfd.raw(), 0, c::SEEK_SET).unwrap();
uapi::fcntl_add_seals(
memfd.raw(),
c::F_SEAL_SEAL | c::F_SEAL_GROW | c::F_SEAL_SHRINK | c::F_SEAL_WRITE,
)
.unwrap();
Ok(Self {
id: ids.next(),
tranches: vec![DrmFeedbackTranche {
device: main_device,
indices: (0..index_map.len()).map(|v| v as u16).collect(),
scanout: false,
}],
shared: Rc::new(DrmFeedbackShared {
fd: Rc::new(memfd),
size: data.len(),
main_device,
indices: index_map,
}),
})
}
pub fn for_scanout(
&self,
ids: &DrmFeedbackIds,
devnum: c::dev_t,
formats: &[(u32, Modifier)],
) -> Result<Option<Self>, DrmFeedbackError> {
let mut tranches = vec![];
{
let mut indices = vec![];
for (format, modifier) in formats {
if let Some(idx) = self.shared.indices.get(&(*format, *modifier)) {
indices.push(*idx);
}
}
if indices.len() > 0 {
tranches.push(DrmFeedbackTranche {
device: devnum,
indices,
scanout: true,
});
} else {
return Ok(None);
}
}
tranches.extend(self.tranches.iter().cloned());
Ok(Some(Self {
id: ids.next(),
shared: self.shared.clone(),
tranches,
}))
}
}
pub trait DrmFeedbackContext {
fn main_device(&self) -> Option<c::dev_t>;
fn for_each_read_format(&self, f: &mut dyn FnMut(u32, Modifier));
}
fn create_fd_data<C: DrmFeedbackContext + ?Sized>(
ctx: &C,
) -> (Vec<u8>, AHashMap<(u32, Modifier), u16>) {
let mut vec = vec![];
let mut map = AHashMap::new();
let mut pos = 0;
ctx.for_each_read_format(&mut |format, modifier| {
vec.write_u32::<NativeEndian>(format).unwrap();
vec.write_u32::<NativeEndian>(0).unwrap();
vec.write_u64::<NativeEndian>(modifier).unwrap();
map.insert((format, modifier), pos);
pos += 1;
});
(vec, map)
}
#[derive(Debug, Error)]
pub enum DrmFeedbackError {
#[error("Graphics API does not have a DRM device")]
NoDrmDevice,
}

11
crates/edid/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "jay-edid"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
description = "EDID parsing for Jay"
repository = "https://github.com/mahkoh/jay"
[dependencies]
bstr = { version = "1.9.0", default-features = false, features = ["std"] }
thiserror = "2.0.11"

1312
crates/edid/src/lib.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,14 @@
[package]
name = "jay-eventfd-cache"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-async-engine = { version = "0.1.0", path = "../async-engine" }
jay-io-uring = { version = "0.1.0", path = "../io-uring" }
jay-utils = { version = "0.1.0", path = "../utils" }
log = { version = "0.4.20", features = ["std"] }
thiserror = "2.0.11"
uapi = "0.2.13"

View file

@ -0,0 +1,157 @@
use {
jay_async_engine::{AsyncEngine, SpawnedFuture},
jay_io_uring::{IoUring, IoUringError},
jay_utils::{
buf::Buf,
errorfmt::ErrorFmt,
oserror::{OsError, OsErrorExt, OsErrorExt2},
queue::AsyncQueue,
stack::Stack,
},
std::{cell::Cell, future::poll_fn, pin::Pin, rc::Rc, slice, task::Poll},
thiserror::Error,
uapi::{OwnedFd, c},
};
#[cfg(test)]
mod tests;
#[derive(Debug, Error)]
pub enum EventfdError {
#[error("Could not create an eventfd")]
CreateEventfd(#[source] OsError),
}
pub struct EventfdCache {
inner: Rc<Inner>,
_task: SpawnedFuture<()>,
}
struct Inner {
ring: Rc<IoUring>,
fds: Stack<Rc<OwnedFd>>,
recycle: AsyncQueue<Rc<OwnedFd>>,
}
pub struct Eventfd {
cache: Rc<Inner>,
pub fd: Rc<OwnedFd>,
signaled: Cell<bool>,
}
impl EventfdCache {
pub fn new(ring: &Rc<IoUring>, eng: &Rc<AsyncEngine>) -> Rc<Self> {
let inner = Rc::new(Inner {
ring: ring.clone(),
fds: Default::default(),
recycle: Default::default(),
});
let task = eng.spawn("eventfd-cache", inner.clone().recycle());
Rc::new(Self { inner, _task: task })
}
pub fn acquire(&self) -> Result<Eventfd, EventfdError> {
let fd = match self.inner.fds.pop() {
Some(fd) => fd,
_ => uapi::eventfd(0, c::EFD_CLOEXEC)
.map(Rc::new)
.map_os_err(EventfdError::CreateEventfd)?,
};
Ok(Eventfd {
cache: self.inner.clone(),
fd,
signaled: Default::default(),
})
}
}
impl Eventfd {
pub fn is_signaled(&self) -> bool {
self.signaled.get()
}
pub async fn signaled(&self) -> Result<(), IoUringError> {
if self.signaled.get() {
return Ok(());
}
self.cache.ring.readable(&self.fd).await?;
self.signaled.set(true);
Ok(())
}
pub fn signaled_blocking(&self) -> Result<(), OsError> {
if self.signaled.get() {
return Ok(());
}
let mut pollfd = c::pollfd {
fd: self.fd.raw(),
events: c::POLLIN,
revents: 0,
};
uapi::poll(slice::from_mut(&mut pollfd), -1).to_os_error()?;
self.signaled.set(true);
Ok(())
}
}
impl Inner {
async fn recycle(self: Rc<Self>) {
let slf = &*self;
let mut fds = vec![];
let mut bufs = vec![];
let mut tasks = vec![];
let mut todo = vec![];
loop {
fds.clear();
tasks.clear();
todo.clear();
slf.recycle.non_empty().await;
while let Some(fd) = slf.recycle.try_pop() {
fds.push(fd);
}
for (idx, fd) in fds.iter().enumerate() {
if idx >= bufs.len() {
bufs.push(Buf::new(size_of::<u64>()));
}
let fd = fd.clone();
let buf = bufs[idx].clone();
tasks.push(async move { slf.ring.read(&fd, buf).await });
todo.push(idx);
}
poll_fn(|ctx| {
let mut i = 0;
while i < todo.len() {
let idx = todo[i];
let task = unsafe { Pin::new_unchecked(&mut tasks[idx]) };
if let Poll::Ready(res) = task.poll(ctx) {
todo.swap_remove(i);
match res {
Ok(_) => {
self.fds.push(fds[idx].clone());
}
Err(e) => {
log::error!("Could not read from eventfd: {}", ErrorFmt(e));
}
}
} else {
i += 1;
}
}
if todo.is_empty() {
Poll::Ready(())
} else {
Poll::Pending
}
})
.await;
}
}
}
impl Drop for Eventfd {
fn drop(&mut self) {
if self.signaled.get() {
self.cache.recycle.push(self.fd.clone());
}
}
}

View file

@ -0,0 +1,67 @@
use {
crate::EventfdCache,
jay_async_engine::AsyncEngine,
jay_io_uring::IoUring,
jay_utils::array,
std::{rc::Rc, slice},
uapi::c,
};
#[test]
fn test() {
let eng = AsyncEngine::new();
let ring = IoUring::new(&eng, 32).unwrap();
let cache = Rc::new(EventfdCache::new(&ring, &eng));
const TOTAL: usize = 5;
let signaled = 3;
let fd1: [_; TOTAL] = array::from_fn(|_| cache.acquire().unwrap());
let fd2: [_; TOTAL] = array::from_fn(|_| cache.acquire().unwrap());
for fd in fd1.iter().chain(fd2.iter()) {
uapi::eventfd_write(fd.fd.raw(), 1).unwrap();
let mut poll = c::pollfd {
fd: fd.fd.raw(),
events: c::POLLIN,
revents: 0,
};
uapi::poll(slice::from_mut(&mut poll), 0).unwrap();
assert_eq!(poll.revents, c::POLLIN);
}
assert_eq!(cache.inner.fds.len(), 0);
let ring2 = ring.clone();
let cache2 = cache.clone();
let _fut1 = eng.spawn("", async move {
for i in 0..signaled {
fd1[i].signaled().await.unwrap();
}
drop(fd1);
let debouncer = ring2.debouncer(0);
while cache2.inner.fds.len() != signaled {
debouncer.debounce().await;
}
for i in 0..signaled {
fd2[i].signaled().await.unwrap();
}
drop(fd2);
while cache2.inner.fds.len() != 2 * signaled {
debouncer.debounce().await;
}
ring2.stop();
});
let now_nsec = eng.now().nsec();
let ring2 = ring.clone();
let _fut2 = eng.spawn("", async move {
ring2.timeout(now_nsec + 1_000_000_000).await.unwrap();
ring2.stop();
});
ring.run().unwrap();
assert_eq!(cache.inner.fds.len(), 2 * signaled);
for fd in cache.inner.fds.take() {
let mut poll = c::pollfd {
fd: fd.raw(),
events: c::POLLIN,
revents: 0,
};
uapi::poll(slice::from_mut(&mut poll), 0).unwrap();
assert_eq!(poll.revents, 0);
}
}

13
crates/formats/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "jay-formats"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
description = "Pixel format tables for Jay"
repository = "https://github.com/mahkoh/jay"
[dependencies]
ahash = "0.8.7"
ash = { package = "jay-ash", version = "0.3.0" }
clap = { version = "4.4.18", features = ["derive", "wrap_help"] }
jay-config = { version = "1.10.0", path = "../jay-config" }

559
crates/formats/src/lib.rs Normal file
View file

@ -0,0 +1,559 @@
use {
ahash::AHashMap,
ash::vk,
clap::{ValueEnum, builder::PossibleValue},
jay_config::video::Format as ConfigFormat,
std::{
fmt::{self, Debug, Write},
sync::LazyLock,
},
};
pub type GLenum = u32;
pub type GLint = i32;
const GL_RGBA: GLint = 0x1908;
const GL_RGBA8: GLenum = 0x8058;
const GL_BGRA_EXT: GLint = 0x80E1;
const GL_UNSIGNED_BYTE: GLint = 0x1401;
#[derive(Copy, Clone, Debug)]
pub struct FormatShmInfo {
pub gl_format: GLint,
pub gl_internal_format: GLenum,
pub gl_type: GLint,
}
#[derive(Copy, Clone, Debug)]
pub struct Format {
pub name: &'static str,
pub vk_format: vk::Format,
pub drm: u32,
pub wl_id: Option<u32>,
pub external_only_guess: bool,
pub has_alpha: bool,
pub opaque: Option<&'static Format>,
pub shm_info: Option<FormatShmInfo>,
pub config: ConfigFormat,
pub bpp: u32,
}
const fn default(config: ConfigFormat) -> Format {
Format {
name: "",
vk_format: vk::Format::UNDEFINED,
drm: 0,
wl_id: None,
external_only_guess: false,
has_alpha: false,
opaque: None,
shm_info: None,
config,
bpp: 4,
}
}
impl PartialEq for Format {
fn eq(&self, other: &Self) -> bool {
self.drm == other.drm
}
}
impl Eq for Format {}
impl ValueEnum for &'static Format {
fn value_variants<'a>() -> &'a [Self] {
ref_formats()
}
fn to_possible_value(&self) -> Option<PossibleValue> {
Some(PossibleValue::new(self.name))
}
}
static FORMATS_MAP: LazyLock<AHashMap<u32, &'static Format>> = LazyLock::new(|| {
let mut map = AHashMap::new();
for format in FORMATS {
assert!(map.insert(format.drm, format).is_none());
}
map
});
static FORMATS_REFS: LazyLock<Vec<&'static Format>> = LazyLock::new(|| FORMATS.iter().collect());
static FORMATS_NAMES: LazyLock<AHashMap<&'static str, &'static Format>> = LazyLock::new(|| {
let mut map = AHashMap::new();
for format in FORMATS {
assert!(map.insert(format.name, format).is_none());
}
map
});
static FORMATS_CONFIG: LazyLock<AHashMap<ConfigFormat, &'static Format>> = LazyLock::new(|| {
let mut map = AHashMap::new();
for format in FORMATS {
assert!(map.insert(format.config, format).is_none());
}
map
});
#[test]
fn formats_dont_panic() {
formats();
named_formats();
config_formats();
}
pub fn formats() -> &'static AHashMap<u32, &'static Format> {
&FORMATS_MAP
}
pub fn ref_formats() -> &'static [&'static Format] {
&FORMATS_REFS
}
pub fn named_formats() -> &'static AHashMap<&'static str, &'static Format> {
&FORMATS_NAMES
}
pub fn config_formats() -> &'static AHashMap<ConfigFormat, &'static Format> {
&FORMATS_CONFIG
}
const fn fourcc_code(a: char, b: char, c: char, d: char) -> u32 {
(a as u32) | ((b as u32) << 8) | ((c as u32) << 16) | ((d as u32) << 24)
}
pub fn debug(fourcc: u32) -> impl Debug {
fmt::from_fn(move |fmt| {
fmt.write_char(fourcc as u8 as char)?;
fmt.write_char((fourcc >> 8) as u8 as char)?;
fmt.write_char((fourcc >> 16) as u8 as char)?;
fmt.write_char((fourcc >> 24) as u8 as char)?;
Ok(())
})
}
const ARGB8888_ID: u32 = 0;
const ARGB8888_DRM: u32 = fourcc_code('A', 'R', '2', '4');
const XRGB8888_ID: u32 = 1;
const XRGB8888_DRM: u32 = fourcc_code('X', 'R', '2', '4');
pub fn map_wayland_format_id(id: u32) -> u32 {
match id {
ARGB8888_ID => ARGB8888_DRM,
XRGB8888_ID => XRGB8888_DRM,
_ => id,
}
}
pub static ARGB8888: &Format = &Format {
name: "argb8888",
shm_info: Some(FormatShmInfo {
gl_format: GL_BGRA_EXT,
gl_internal_format: GL_RGBA8,
gl_type: GL_UNSIGNED_BYTE,
}),
vk_format: vk::Format::B8G8R8A8_UNORM,
bpp: 4,
drm: ARGB8888_DRM,
wl_id: Some(ARGB8888_ID),
external_only_guess: false,
has_alpha: true,
opaque: Some(XRGB8888),
config: ConfigFormat::ARGB8888,
};
pub static XRGB8888: &Format = &Format {
name: "xrgb8888",
shm_info: Some(FormatShmInfo {
gl_format: GL_BGRA_EXT,
gl_internal_format: GL_RGBA8,
gl_type: GL_UNSIGNED_BYTE,
}),
vk_format: vk::Format::B8G8R8A8_UNORM,
bpp: 4,
drm: XRGB8888_DRM,
wl_id: Some(XRGB8888_ID),
external_only_guess: false,
has_alpha: false,
opaque: None,
config: ConfigFormat::XRGB8888,
};
static ABGR8888: &Format = &Format {
name: "abgr8888",
shm_info: Some(FormatShmInfo {
gl_format: GL_RGBA,
gl_internal_format: GL_RGBA8,
gl_type: GL_UNSIGNED_BYTE,
}),
vk_format: vk::Format::R8G8B8A8_UNORM,
bpp: 4,
drm: fourcc_code('A', 'B', '2', '4'),
wl_id: None,
external_only_guess: false,
has_alpha: true,
opaque: Some(XBGR8888),
config: ConfigFormat::ABGR8888,
};
static XBGR8888: &Format = &Format {
name: "xbgr8888",
shm_info: Some(FormatShmInfo {
gl_format: GL_RGBA,
gl_internal_format: GL_RGBA8,
gl_type: GL_UNSIGNED_BYTE,
}),
vk_format: vk::Format::R8G8B8A8_UNORM,
bpp: 4,
drm: fourcc_code('X', 'B', '2', '4'),
wl_id: None,
external_only_guess: false,
has_alpha: false,
opaque: None,
config: ConfigFormat::XBGR8888,
};
static R8: &Format = &Format {
name: "r8",
vk_format: vk::Format::R8_UNORM,
bpp: 1,
drm: fourcc_code('R', '8', ' ', ' '),
..default(ConfigFormat::R8)
};
static GR88: &Format = &Format {
name: "gr88",
vk_format: vk::Format::R8G8_UNORM,
bpp: 2,
drm: fourcc_code('G', 'R', '8', '8'),
..default(ConfigFormat::GR88)
};
static RGB888: &Format = &Format {
name: "rgb888",
vk_format: vk::Format::B8G8R8_UNORM,
bpp: 3,
drm: fourcc_code('R', 'G', '2', '4'),
..default(ConfigFormat::RGB888)
};
static BGR888: &Format = &Format {
name: "bgr888",
vk_format: vk::Format::R8G8B8_UNORM,
bpp: 3,
drm: fourcc_code('B', 'G', '2', '4'),
..default(ConfigFormat::BGR888)
};
static RGBA4444: &Format = &Format {
name: "rgba4444",
vk_format: vk::Format::R4G4B4A4_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'A', '1', '2'),
has_alpha: true,
opaque: Some(RGBX4444),
..default(ConfigFormat::RGBA4444)
};
static RGBX4444: &Format = &Format {
name: "rgbx4444",
vk_format: vk::Format::R4G4B4A4_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'X', '1', '2'),
..default(ConfigFormat::RGBX4444)
};
static BGRA4444: &Format = &Format {
name: "bgra4444",
vk_format: vk::Format::B4G4R4A4_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'A', '1', '2'),
has_alpha: true,
opaque: Some(BGRX4444),
..default(ConfigFormat::BGRA4444)
};
static BGRX4444: &Format = &Format {
name: "bgrx4444",
vk_format: vk::Format::B4G4R4A4_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'X', '1', '2'),
..default(ConfigFormat::BGRX4444)
};
static RGB565: &Format = &Format {
name: "rgb565",
vk_format: vk::Format::R5G6B5_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'G', '1', '6'),
..default(ConfigFormat::RGB565)
};
static BGR565: &Format = &Format {
name: "bgr565",
vk_format: vk::Format::B5G6R5_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'G', '1', '6'),
..default(ConfigFormat::BGR565)
};
static RGBA5551: &Format = &Format {
name: "rgba5551",
vk_format: vk::Format::R5G5B5A1_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'A', '1', '5'),
has_alpha: true,
opaque: Some(RGBX5551),
..default(ConfigFormat::RGBA5551)
};
static RGBX5551: &Format = &Format {
name: "rgbx5551",
vk_format: vk::Format::R5G5B5A1_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'X', '1', '5'),
..default(ConfigFormat::RGBX5551)
};
static BGRA5551: &Format = &Format {
name: "bgra5551",
vk_format: vk::Format::B5G5R5A1_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'A', '1', '5'),
has_alpha: true,
opaque: Some(BGRX5551),
..default(ConfigFormat::BGRA5551)
};
static BGRX5551: &Format = &Format {
name: "bgrx5551",
vk_format: vk::Format::B5G5R5A1_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'X', '1', '5'),
..default(ConfigFormat::BGRX5551)
};
static ARGB1555: &Format = &Format {
name: "argb1555",
vk_format: vk::Format::A1R5G5B5_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('A', 'R', '1', '5'),
has_alpha: true,
opaque: Some(XRGB1555),
..default(ConfigFormat::ARGB1555)
};
static XRGB1555: &Format = &Format {
name: "xrgb1555",
vk_format: vk::Format::A1R5G5B5_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('X', 'R', '1', '5'),
..default(ConfigFormat::XRGB1555)
};
static ARGB2101010: &Format = &Format {
name: "argb2101010",
vk_format: vk::Format::A2R10G10B10_UNORM_PACK32,
bpp: 4,
drm: fourcc_code('A', 'R', '3', '0'),
has_alpha: true,
opaque: Some(XRGB2101010),
..default(ConfigFormat::ARGB2101010)
};
static XRGB2101010: &Format = &Format {
name: "xrgb2101010",
vk_format: vk::Format::A2R10G10B10_UNORM_PACK32,
bpp: 4,
drm: fourcc_code('X', 'R', '3', '0'),
..default(ConfigFormat::XRGB2101010)
};
static ABGR2101010: &Format = &Format {
name: "abgr2101010",
vk_format: vk::Format::A2B10G10R10_UNORM_PACK32,
bpp: 4,
drm: fourcc_code('A', 'B', '3', '0'),
has_alpha: true,
opaque: Some(XBGR2101010),
..default(ConfigFormat::ABGR2101010)
};
static XBGR2101010: &Format = &Format {
name: "xbgr2101010",
vk_format: vk::Format::A2B10G10R10_UNORM_PACK32,
bpp: 4,
drm: fourcc_code('X', 'B', '3', '0'),
..default(ConfigFormat::XBGR2101010)
};
static ABGR16161616: &Format = &Format {
name: "abgr16161616",
vk_format: vk::Format::R16G16B16A16_UNORM,
bpp: 8,
drm: fourcc_code('A', 'B', '4', '8'),
has_alpha: true,
opaque: Some(XBGR16161616),
..default(ConfigFormat::ABGR16161616)
};
static XBGR16161616: &Format = &Format {
name: "xbgr16161616",
vk_format: vk::Format::R16G16B16A16_UNORM,
bpp: 8,
drm: fourcc_code('X', 'B', '4', '8'),
..default(ConfigFormat::XBGR16161616)
};
pub static ABGR16161616F: &Format = &Format {
name: "abgr16161616f",
vk_format: vk::Format::R16G16B16A16_SFLOAT,
bpp: 8,
drm: fourcc_code('A', 'B', '4', 'H'),
has_alpha: true,
opaque: Some(XBGR16161616F),
..default(ConfigFormat::ABGR16161616F)
};
static XBGR16161616F: &Format = &Format {
name: "xbgr16161616f",
vk_format: vk::Format::R16G16B16A16_SFLOAT,
bpp: 8,
drm: fourcc_code('X', 'B', '4', 'H'),
..default(ConfigFormat::XBGR16161616F)
};
static BGR161616: &Format = &Format {
name: "bgr161616",
vk_format: vk::Format::R16G16B16_UNORM,
bpp: 6,
drm: fourcc_code('B', 'G', '4', '8'),
..default(ConfigFormat::BGR161616)
};
static R16F: &Format = &Format {
name: "r16f",
vk_format: vk::Format::R16_SFLOAT,
bpp: 2,
drm: fourcc_code('R', ' ', ' ', 'H'),
..default(ConfigFormat::R16F)
};
static GR1616F: &Format = &Format {
name: "gr1616f",
vk_format: vk::Format::R16G16_SFLOAT,
bpp: 4,
drm: fourcc_code('G', 'R', ' ', 'H'),
..default(ConfigFormat::GR1616F)
};
static BGR161616F: &Format = &Format {
name: "bgr161616f",
vk_format: vk::Format::R16G16B16_SFLOAT,
bpp: 6,
drm: fourcc_code('B', 'G', 'R', 'H'),
..default(ConfigFormat::BGR161616F)
};
static R32F: &Format = &Format {
name: "r32f",
vk_format: vk::Format::R32_SFLOAT,
bpp: 4,
drm: fourcc_code('R', ' ', ' ', 'F'),
..default(ConfigFormat::R32F)
};
static GR3232F: &Format = &Format {
name: "gr3232f",
vk_format: vk::Format::R32G32_SFLOAT,
bpp: 8,
drm: fourcc_code('G', 'R', ' ', 'F'),
..default(ConfigFormat::GR3232F)
};
static BGR323232F: &Format = &Format {
name: "bgr323232f",
vk_format: vk::Format::R32G32B32_SFLOAT,
bpp: 12,
drm: fourcc_code('B', 'G', 'R', 'F'),
..default(ConfigFormat::BGR323232F)
};
static ABGR32323232F: &Format = &Format {
name: "abgr32323232f",
vk_format: vk::Format::R32G32B32A32_SFLOAT,
bpp: 16,
drm: fourcc_code('A', 'B', '8', 'F'),
has_alpha: true,
..default(ConfigFormat::ABGR32323232F)
};
pub static FORMATS: &[Format] = &[
*ARGB8888,
*XRGB8888,
*ABGR8888,
*XBGR8888,
*R8,
*GR88,
*RGB888,
*BGR888,
#[cfg(target_endian = "little")]
*RGBA4444,
#[cfg(target_endian = "little")]
*RGBX4444,
#[cfg(target_endian = "little")]
*BGRA4444,
#[cfg(target_endian = "little")]
*BGRX4444,
#[cfg(target_endian = "little")]
*RGB565,
#[cfg(target_endian = "little")]
*BGR565,
#[cfg(target_endian = "little")]
*RGBA5551,
#[cfg(target_endian = "little")]
*RGBX5551,
#[cfg(target_endian = "little")]
*BGRA5551,
#[cfg(target_endian = "little")]
*BGRX5551,
#[cfg(target_endian = "little")]
*ARGB1555,
#[cfg(target_endian = "little")]
*XRGB1555,
#[cfg(target_endian = "little")]
*ARGB2101010,
#[cfg(target_endian = "little")]
*XRGB2101010,
#[cfg(target_endian = "little")]
*ABGR2101010,
#[cfg(target_endian = "little")]
*XBGR2101010,
#[cfg(target_endian = "little")]
*ABGR16161616,
#[cfg(target_endian = "little")]
*XBGR16161616,
#[cfg(target_endian = "little")]
*ABGR16161616F,
#[cfg(target_endian = "little")]
*XBGR16161616F,
#[cfg(target_endian = "little")]
*BGR161616,
#[cfg(target_endian = "little")]
*R16F,
#[cfg(target_endian = "little")]
*GR1616F,
#[cfg(target_endian = "little")]
*BGR161616F,
#[cfg(target_endian = "little")]
*R32F,
#[cfg(target_endian = "little")]
*GR3232F,
#[cfg(target_endian = "little")]
*BGR323232F,
#[cfg(target_endian = "little")]
*ABGR32323232F,
];

View file

@ -0,0 +1,11 @@
[package]
name = "jay-geometry"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
description = "Geometry primitives for Jay"
repository = "https://github.com/mahkoh/jay"
[dependencies]
jay-algorithms = { version = "0.4.0", path = "../algorithms" }
smallvec = { version = "1.11.1", features = ["const_generics", "const_new", "union"] }

365
crates/geometry/src/lib.rs Normal file
View file

@ -0,0 +1,365 @@
mod region;
#[cfg(test)]
mod tests;
pub use region::{DamageQueue, RegionBuilder};
use {
jay_algorithms::rect::{NoTag, RectRaw, Tag},
smallvec::SmallVec,
std::fmt::{Debug, Formatter},
};
#[derive(Copy, Clone, Eq, PartialEq, Default)]
#[repr(transparent)]
pub struct Rect<T = NoTag>
where
T: Tag,
{
raw: RectRaw<T>,
}
#[derive(Clone, Eq, PartialEq, Debug, Default)]
pub struct Region<T = NoTag>
where
T: Tag,
{
rects: SmallVec<[RectRaw<T>; 1]>,
extents: Rect,
}
impl Debug for Rect {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.raw, f)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
pub struct RectOverflow {
pub left: i32,
pub right: i32,
pub top: i32,
pub bottom: i32,
}
impl RectOverflow {
pub fn is_contained(&self) -> bool {
self.left <= 0 && self.right <= 0 && self.top <= 0 && self.bottom <= 0
}
pub fn x_overflow(&self) -> bool {
self.left > 0 || self.right > 0
}
pub fn y_overflow(&self) -> bool {
self.top > 0 || self.bottom > 0
}
}
impl<T> Rect<T>
where
T: Tag,
{
pub fn untag(&self) -> Rect {
Rect {
raw: RectRaw {
x1: self.raw.x1,
y1: self.raw.y1,
x2: self.raw.x2,
y2: self.raw.y2,
tag: NoTag,
},
}
}
}
impl Rect {
pub fn new_empty(x: i32, y: i32) -> Self {
Self {
raw: RectRaw {
x1: x,
y1: y,
x2: x,
y2: y,
tag: NoTag,
},
}
}
pub fn new(x1: i32, y1: i32, x2: i32, y2: i32) -> Option<Self> {
if x2 < x1 || y2 < y1 {
return None;
}
Some(Self {
raw: RectRaw {
x1,
y1,
x2,
y2,
tag: NoTag,
},
})
}
#[cfg_attr(not(test), expect(dead_code))]
fn new_unchecked_danger(x1: i32, y1: i32, x2: i32, y2: i32) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2,
y2,
tag: NoTag,
},
}
}
pub fn new_sized(x1: i32, y1: i32, width: i32, height: i32) -> Option<Self> {
if width < 0 || height < 0 {
return None;
}
Self::new(x1, y1, x1 + width, y1 + height)
}
pub fn new_saturating(x1: i32, y1: i32, x2: i32, y2: i32) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2: x2.max(x1),
y2: y2.max(y1),
tag: NoTag,
},
}
}
pub fn new_sized_saturating(x1: i32, y1: i32, width: i32, height: i32) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2: x1.saturating_add(width.max(0)),
y2: y1.saturating_add(height.max(0)),
tag: NoTag,
},
}
}
pub fn union(&self, other: Self) -> Self {
Self {
raw: RectRaw {
x1: self.raw.x1.min(other.raw.x1),
y1: self.raw.y1.min(other.raw.y1),
x2: self.raw.x2.max(other.raw.x2),
y2: self.raw.y2.max(other.raw.y2),
tag: NoTag,
},
}
}
pub fn intersect(&self, other: Self) -> Self {
let x1 = self.raw.x1.max(other.raw.x1);
let y1 = self.raw.y1.max(other.raw.y1);
let x2 = self.raw.x2.min(other.raw.x2).max(x1);
let y2 = self.raw.y2.min(other.raw.y2).max(y1);
Self {
raw: RectRaw {
x1,
y1,
x2,
y2,
tag: NoTag,
},
}
}
pub fn with_size_saturating(&self, width: i32, height: i32) -> Self {
Self::new_sized_saturating(self.raw.x1, self.raw.y1, width, height)
}
pub fn with_tag(&self, tag: u32) -> Rect<u32> {
Rect {
raw: RectRaw {
x1: self.raw.x1,
y1: self.raw.y1,
x2: self.raw.x2,
y2: self.raw.y2,
tag,
},
}
}
}
impl<T> Rect<T>
where
T: Tag,
{
#[cfg_attr(not(test), expect(dead_code))]
fn new_unchecked_danger_tagged(x1: i32, y1: i32, x2: i32, y2: i32, tag: T) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2,
y2,
tag,
},
}
}
pub fn intersects(&self, other: &Self) -> bool {
self.raw.x1 < other.raw.x2
&& other.raw.x1 < self.raw.x2
&& self.raw.y1 < other.raw.y2
&& other.raw.y1 < self.raw.y2
}
pub fn contains(&self, x: i32, y: i32) -> bool {
self.raw.x1 <= x && self.raw.y1 <= y && self.raw.x2 > x && self.raw.y2 > y
}
pub fn not_contains(&self, x: i32, y: i32) -> bool {
!self.contains(x, y)
}
pub fn dist_squared(&self, x: i32, y: i32) -> i128 {
let x = x as i64;
let y = y as i64;
let x1 = self.raw.x1 as i64;
let x2 = self.raw.x2 as i64;
let y1 = self.raw.y1 as i64;
let y2 = self.raw.y2 as i64;
let mut dx = 0;
if x1 > x {
dx = x1 - x;
} else if x2 < x {
dx = x - x2;
}
let mut dy = 0;
if y1 > y {
dy = y1 - y;
} else if y2 < y {
dy = y - y2;
}
let dx = dx as i128;
let dy = dy as i128;
dx * dx + dy * dy
}
pub fn contains_rect<U>(&self, rect: &Rect<U>) -> bool
where
U: Tag,
{
self.raw.x1 <= rect.raw.x1
&& self.raw.y1 <= rect.raw.x1
&& rect.raw.x2 <= self.raw.x2
&& rect.raw.y2 <= self.raw.y2
}
pub fn get_overflow<U>(&self, child: &Rect<U>) -> RectOverflow
where
U: Tag,
{
RectOverflow {
left: self.raw.x1 - child.raw.x1,
right: child.raw.x2 - self.raw.x2,
top: self.raw.y1 - child.raw.y1,
bottom: child.raw.y2 - self.raw.y2,
}
}
pub fn is_empty(&self) -> bool {
self.raw.x1 == self.raw.x2 || self.raw.y1 == self.raw.y2
}
pub fn is_not_empty(&self) -> bool {
!self.is_empty()
}
pub fn to_origin(&self) -> Self {
Self {
raw: RectRaw {
x1: 0,
y1: 0,
x2: self.raw.x2 - self.raw.x1,
y2: self.raw.y2 - self.raw.y1,
tag: self.raw.tag,
},
}
}
pub fn move_(&self, dx: i32, dy: i32) -> Self {
Self {
raw: RectRaw {
x1: self.raw.x1.saturating_add(dx),
y1: self.raw.y1.saturating_add(dy),
x2: self.raw.x2.saturating_add(dx),
y2: self.raw.y2.saturating_add(dy),
tag: self.raw.tag,
},
}
}
pub fn at_point(&self, x1: i32, y1: i32) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2: x1 + self.raw.x2 - self.raw.x1,
y2: y1 + self.raw.y2 - self.raw.y1,
tag: self.raw.tag,
},
}
}
pub fn translate(&self, x: i32, y: i32) -> (i32, i32) {
(x.wrapping_sub(self.raw.x1), y.wrapping_sub(self.raw.y1))
}
pub fn translate_inv(&self, x: i32, y: i32) -> (i32, i32) {
(x.wrapping_add(self.raw.x1), y.wrapping_add(self.raw.y1))
}
pub fn x1(&self) -> i32 {
self.raw.x1
}
pub fn x2(&self) -> i32 {
self.raw.x2
}
pub fn y1(&self) -> i32 {
self.raw.y1
}
pub fn y2(&self) -> i32 {
self.raw.y2
}
pub fn width(&self) -> i32 {
self.raw.x2 - self.raw.x1
}
pub fn height(&self) -> i32 {
self.raw.y2 - self.raw.y1
}
pub fn position(&self) -> (i32, i32) {
(self.raw.x1, self.raw.y1)
}
pub fn size(&self) -> (i32, i32) {
(self.width(), self.height())
}
pub fn center(&self) -> (i32, i32) {
(
self.raw.x1 + self.width() / 2,
self.raw.y1 + self.height() / 2,
)
}
pub fn tag(&self) -> T {
self.raw.tag
}
}

View file

@ -0,0 +1,339 @@
use {
crate::{Rect, Region},
jay_algorithms::rect::{
RectRaw, Tag,
region::{
extents, intersect, intersect_tagged, rects_to_bands, rects_to_bands_tagged, subtract,
union,
},
},
smallvec::SmallVec,
std::{
array,
borrow::Cow,
cell::UnsafeCell,
fmt::{Debug, Formatter},
mem,
ops::Deref,
rc::Rc,
},
};
thread_local! {
static EMPTY: Rc<Region> =
Rc::new(Region {
rects: Default::default(),
extents: Default::default(),
});
}
impl Region {
pub fn empty() -> Rc<Self> {
EMPTY.with(|e| e.clone())
}
pub fn from_rects(rects: &[Rect]) -> Rc<Self> {
if rects.is_empty() {
return Self::empty();
}
Rc::new(Self::from_rects2(rects))
}
pub fn from_rects2(rects: &[Rect]) -> Self {
if rects.is_empty() {
return Self::default();
}
if rects.len() == 1 {
return Self::new(rects[0]);
}
let rects = rects_to_bands(unsafe { mem::transmute::<&[Rect], &[RectRaw]>(rects) });
Self {
extents: Rect {
raw: extents(&rects),
},
rects,
}
}
pub fn union(self: &Rc<Self>, other: &Rc<Self>) -> Rc<Self> {
if self.extents.is_empty() {
return other.clone();
}
if other.extents.is_empty() {
return self.clone();
}
let rects = union(&self.rects, &other.rects);
Rc::new(Self {
rects,
extents: self.extents.union(other.extents),
})
}
pub fn union_cow<'a>(&'a self, other: &'a Self) -> Cow<'a, Region> {
if self.extents.is_empty() {
return Cow::Borrowed(other);
}
if other.extents.is_empty() {
return Cow::Borrowed(self);
}
let rects = union(&self.rects, &other.rects);
Cow::Owned(Self {
rects,
extents: self.extents.union(other.extents),
})
}
pub fn subtract(self: &Rc<Self>, other: &Rc<Self>) -> Rc<Self> {
if self.extents.is_empty() || other.extents.is_empty() {
return self.clone();
}
let rects = subtract(&self.rects, &other.rects);
Rc::new(Self {
extents: Rect {
raw: extents(&rects),
},
rects,
})
}
pub fn subtract_cow<'a>(&'a self, other: &Self) -> Cow<'a, Self> {
if self.extents.is_empty() || other.extents.is_empty() {
return Cow::Borrowed(self);
}
let rects = subtract(&self.rects, &other.rects);
Cow::Owned(Self {
extents: Rect {
raw: extents(&rects),
},
rects,
})
}
pub fn intersect(&self, other: &Region) -> Self {
if self.is_empty() || other.is_empty() {
return Self::default();
}
let rects = intersect(&self.rects, &other.rects);
Self {
extents: Rect {
raw: extents(&rects),
},
rects,
}
}
}
impl Region<u32> {
pub fn from_rects_tagged(rects: &[Rect<u32>]) -> Self {
if rects.is_empty() {
return Self::default();
}
if rects.len() == 1 {
let mut rect = rects[0];
rect.raw.tag = rect.raw.tag.constrain();
return Self::new(rect);
}
let rects = rects_to_bands_tagged(unsafe {
mem::transmute::<&[Rect<u32>], &[RectRaw<u32>]>(rects)
});
Self {
extents: Rect {
raw: extents(&rects),
},
rects,
}
}
pub fn intersect_tagged(&self, other: &Region) -> Self {
if self.is_empty() || other.is_empty() {
return Self::default();
}
let rects = intersect_tagged(&self.rects, &other.rects);
Self {
extents: Rect {
raw: extents(&rects),
},
rects,
}
}
}
impl<T> Region<T>
where
T: Tag,
{
pub fn new(rect: Rect<T>) -> Self {
let mut rects = SmallVec::new();
rects.push(rect.raw);
Self {
rects,
extents: rect.untag(),
}
}
pub fn extents(&self) -> Rect {
self.extents
}
pub fn rects(&self) -> &[Rect<T>] {
unsafe { mem::transmute::<&[RectRaw<T>], &[Rect<T>]>(&self.rects[..]) }
}
pub fn contains(&self, x: i32, y: i32) -> bool {
if !self.extents.contains(x, y) {
return false;
}
for r in self.deref() {
if r.contains(x, y) {
return true;
}
}
false
}
pub fn contains_rect(&self, rect: &Rect) -> bool {
self.contains_rect2(rect, |r| *r)
}
pub fn contains_rect2(&self, rect: &Rect, map: impl Fn(&Rect<T>) -> Rect<T>) -> bool {
if rect.is_empty() {
return true;
}
let mut y1 = rect.y1();
for r in self.rects() {
let r = map(r);
if r.y2() <= y1 || r.x2() <= rect.x1() {
continue;
}
if r.y1() > y1 || r.x1() > rect.x1() || r.x2() < rect.x2() {
return false;
}
y1 = r.y2();
if y1 >= rect.y2() {
return true;
}
}
false
}
}
impl<T> Deref for Region<T>
where
T: Tag,
{
type Target = [Rect<T>];
fn deref(&self) -> &Self::Target {
unsafe { mem::transmute::<&[RectRaw<T>], _>(&self.rects) }
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
enum BuilderOp {
#[default]
Add,
Sub,
}
#[derive(Debug)]
pub struct RegionBuilder {
base: Rc<Region>,
op: BuilderOp,
pending: Vec<Rect>,
}
impl Default for RegionBuilder {
fn default() -> Self {
Self {
base: Region::empty(),
op: Default::default(),
pending: Default::default(),
}
}
}
impl RegionBuilder {
pub fn add(&mut self, rect: Rect) {
self.set_op(BuilderOp::Add);
self.pending.push(rect);
}
pub fn sub(&mut self, rect: Rect) {
self.set_op(BuilderOp::Sub);
self.pending.push(rect);
}
pub fn get(&mut self) -> Rc<Region> {
self.flush();
self.base.clone()
}
pub fn clear(&mut self) {
self.pending.clear();
self.base = Region::empty();
}
fn set_op(&mut self, op: BuilderOp) {
if self.op != op {
self.flush();
self.op = op;
}
}
fn flush(&mut self) {
if self.pending.is_empty() {
return;
}
let region = Region::from_rects(&self.pending);
self.base = match self.op {
BuilderOp::Add => self.base.union(&region),
BuilderOp::Sub => self.base.subtract(&region),
};
self.pending.clear();
}
}
pub struct DamageQueue {
this: usize,
datas: Rc<UnsafeCell<Vec<Vec<Rect>>>>,
}
impl Debug for DamageQueue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DamageQueue").finish_non_exhaustive()
}
}
impl DamageQueue {
pub fn new<const N: usize>() -> [DamageQueue; N] {
let datas = Rc::new(UnsafeCell::new(vec![vec!(); N]));
array::from_fn(|this| DamageQueue {
this,
datas: datas.clone(),
})
}
pub fn damage(&self, rects: &[Rect]) {
let datas = unsafe { &mut *self.datas.get() };
for data in datas {
data.extend(rects);
}
}
pub fn clear(&self) {
let data = unsafe { &mut (&mut *self.datas.get())[self.this] };
data.clear();
}
pub fn clear_all(&self) {
let datas = unsafe { &mut *self.datas.get() };
for data in datas {
data.clear();
}
}
pub fn get(&self) -> Region {
let data = unsafe { &(&*self.datas.get())[self.this] };
Region::from_rects2(data)
}
}

View file

@ -0,0 +1,715 @@
use {
crate::{Rect, Region},
jay_algorithms::rect::{NoTag, RectRaw},
};
#[test]
fn union1() {
let r1 = Region::new(Rect::new(0, 0, 10, 10).unwrap());
let r2_ = Region::new(Rect::new(5, 5, 15, 15).unwrap());
let r2 = Region::new(Rect::new(10, 10, 20, 20).unwrap());
let r3 = r1.union_cow(&r2);
let r3 = r3.union_cow(&r2_);
assert_eq!(r3.extents, Rect::new(0, 0, 20, 20).unwrap());
assert_eq!(
&r3.rects[..],
&[
Rect::new(0, 0, 10, 5).unwrap().raw,
Rect::new(0, 5, 15, 10).unwrap().raw,
Rect::new(5, 10, 20, 15).unwrap().raw,
Rect::new(10, 15, 20, 20).unwrap().raw,
]
);
}
#[test]
fn union2() {
let r1 = Region::new(Rect::new(0, 0, 10, 10).unwrap());
let r2 = Region::new(Rect::new(0, 10, 10, 20).unwrap());
let r3 = r1.union_cow(&r2);
assert_eq!(r3.extents, Rect::new(0, 0, 10, 20).unwrap());
assert_eq!(&r3.rects[..], &[Rect::new(0, 0, 10, 20).unwrap().raw,]);
}
#[test]
fn subtract1() {
let r1 = Region::new(Rect::new(0, 0, 20, 20).unwrap());
let r2 = Region::new(Rect::new(5, 5, 15, 15).unwrap());
let r3 = r1.subtract_cow(&r2);
assert_eq!(r3.extents, Rect::new(0, 0, 20, 20).unwrap());
assert_eq!(
&r3.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 20,
y2: 5,
tag: NoTag,
},
RectRaw {
x1: 0,
y1: 5,
x2: 5,
y2: 15,
tag: NoTag,
},
RectRaw {
x1: 15,
y1: 5,
x2: 20,
y2: 15,
tag: NoTag,
},
RectRaw {
x1: 0,
y1: 15,
x2: 20,
y2: 20,
tag: NoTag,
},
]
);
}
#[test]
fn rects_to_bands() {
let rects = [
Rect::new_unchecked_danger(0, 0, 10, 10),
Rect::new_unchecked_danger(5, 0, 30, 10),
Rect::new_unchecked_danger(30, 5, 50, 15),
];
let r = Region::from_rects(&rects[..]);
// println!("{:#?}", r.rects);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 30,
y2: 5,
tag: NoTag,
},
RectRaw {
x1: 0,
y1: 5,
x2: 50,
y2: 10,
tag: NoTag,
},
RectRaw {
x1: 30,
y1: 10,
x2: 50,
y2: 15,
tag: NoTag,
},
]
);
}
#[test]
fn rects_to_bands2() {
let rects = [
Rect::new_unchecked_danger(0, 0, 10, 10),
Rect::new_unchecked_danger(0, 10, 10, 20),
];
let r = Region::from_rects(&rects[..]);
// println!("{:#?}", r.rects);
assert_eq!(&r.rects[..], &[Rect::new(0, 0, 10, 20).unwrap().raw,]);
}
#[test]
fn rects_to_bands_tagged1() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 200, 1),
Rect::new_unchecked_danger_tagged(50, 50, 150, 150, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 200,
y2: 50,
tag: 1,
},
RectRaw {
x1: 0,
y1: 50,
x2: 50,
y2: 150,
tag: 1,
},
RectRaw {
x1: 50,
y1: 50,
x2: 150,
y2: 150,
tag: 0,
},
RectRaw {
x1: 150,
y1: 50,
x2: 200,
y2: 150,
tag: 1,
},
RectRaw {
x1: 0,
y1: 150,
x2: 200,
y2: 200,
tag: 1,
},
],
);
}
#[test]
fn rects_to_bands_tagged2() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 200, 1),
Rect::new_unchecked_danger_tagged(50, 50, 150, 150, 0),
Rect::new_unchecked_danger_tagged(60, 60, 140, 140, 2),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 200,
y2: 50,
tag: 1,
},
RectRaw {
x1: 0,
y1: 50,
x2: 50,
y2: 150,
tag: 1,
},
RectRaw {
x1: 50,
y1: 50,
x2: 150,
y2: 150,
tag: 0,
},
RectRaw {
x1: 150,
y1: 50,
x2: 200,
y2: 150,
tag: 1,
},
RectRaw {
x1: 0,
y1: 150,
x2: 200,
y2: 200,
tag: 1,
},
],
);
}
#[test]
fn rects_to_bands_tagged3() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 200, 2),
Rect::new_unchecked_danger_tagged(50, 50, 150, 150, 1),
Rect::new_unchecked_danger_tagged(60, 60, 140, 140, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 200,
y2: 50,
tag: 0,
},
RectRaw {
x1: 0,
y1: 50,
x2: 50,
y2: 60,
tag: 0,
},
RectRaw {
x1: 50,
y1: 50,
x2: 150,
y2: 60,
tag: 1,
},
RectRaw {
x1: 150,
y1: 50,
x2: 200,
y2: 60,
tag: 0,
},
RectRaw {
x1: 0,
y1: 60,
x2: 50,
y2: 140,
tag: 0,
},
RectRaw {
x1: 50,
y1: 60,
x2: 60,
y2: 140,
tag: 1,
},
RectRaw {
x1: 60,
y1: 60,
x2: 140,
y2: 140,
tag: 0,
},
RectRaw {
x1: 140,
y1: 60,
x2: 150,
y2: 140,
tag: 1,
},
RectRaw {
x1: 150,
y1: 60,
x2: 200,
y2: 140,
tag: 0,
},
RectRaw {
x1: 0,
y1: 140,
x2: 50,
y2: 150,
tag: 0,
},
RectRaw {
x1: 50,
y1: 140,
x2: 150,
y2: 150,
tag: 1,
},
RectRaw {
x1: 150,
y1: 140,
x2: 200,
y2: 150,
tag: 0,
},
RectRaw {
x1: 0,
y1: 150,
x2: 200,
y2: 200,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged4() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(100, 0, 200, 200, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 100,
y1: 0,
x2: 200,
y2: 100,
tag: 0,
},
RectRaw {
x1: 100,
y1: 100,
x2: 200,
y2: 200,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged5() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 100, 1),
Rect::new_unchecked_danger_tagged(100, 0, 200, 100, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 100,
y1: 0,
x2: 200,
y2: 100,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged6() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 100, 1),
Rect::new_unchecked_danger_tagged(100, 0, 300, 100, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 100,
y1: 0,
x2: 300,
y2: 100,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged7() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 100, 0),
Rect::new_unchecked_danger_tagged(100, 0, 300, 200, 1),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 200,
y2: 100,
tag: 0,
},
RectRaw {
x1: 200,
y1: 0,
x2: 300,
y2: 100,
tag: 1,
},
RectRaw {
x1: 100,
y1: 100,
x2: 300,
y2: 200,
tag: 1,
},
],
);
}
#[test]
fn rects_to_bands_tagged8() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(100, 0, 200, 100, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 100,
y1: 0,
x2: 200,
y2: 100,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged9() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(100, 0, 200, 100, 1),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[RectRaw {
x1: 0,
y1: 0,
x2: 200,
y2: 100,
tag: 1,
},],
);
}
#[test]
fn rects_to_bands_tagged10() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(0, 100, 100, 200, 1),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 200,
tag: 1,
},],
);
}
#[test]
fn rects_to_bands_tagged11() {
let rects = [Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 11)];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},],
);
}
#[test]
fn rects_to_bands_tagged12() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 11),
Rect::new_unchecked_danger_tagged(200, 0, 300, 100, 10),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 200,
y1: 0,
x2: 300,
y2: 100,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged13() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(0, 100, 100, 200, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 0,
y1: 100,
x2: 100,
y2: 200,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged14() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 0,
},],
);
}
#[test]
fn intersect1() {
let rects = [Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 0)];
let r1 = Region::from_rects_tagged(&rects[..]);
let rects = [Rect::new_unchecked_danger(100, 100, 200, 200)];
let r2 = Region::from_rects2(&rects[..]);
let r3 = r1.intersect_tagged(&r2);
assert_eq!(&r3.rects[..], &[],);
}
#[test]
fn intersect2() {
let rects = [Rect::new_unchecked_danger_tagged(0, 0, 200, 200, 0)];
let r1 = Region::from_rects_tagged(&rects[..]);
let rects = [Rect::new_unchecked_danger(50, 50, 150, 150)];
let r2 = Region::from_rects2(&rects[..]);
let r3 = r1.intersect_tagged(&r2);
assert_eq!(
&r3.rects[..],
&[RectRaw {
x1: 50,
y1: 50,
x2: 150,
y2: 150,
tag: 0,
}],
);
}
#[test]
fn intersect3() {
macro_rules! t {
($l:expr, $r:expr, $t:expr) => {
Rect::new_unchecked_danger_tagged($l, 0, $r, 1, $t)
};
}
macro_rules! u {
($l:expr, $r:expr) => {
Rect::new_unchecked_danger($l, 0, $r, 1)
};
}
macro_rules! r {
($l:expr, $r:expr, $t:expr) => {
RectRaw {
x1: $l,
y1: 0,
x2: $r,
y2: 1,
tag: $t,
}
};
}
let rects = [
t!(0, 100, 0),
t!(110, 130, 1),
t!(140, 160, 2),
t!(170, 180, 0),
];
let r1 = Region::from_rects_tagged(&rects[..]);
let rects = [
u!(10, 20),
u!(50, 60),
u!(70, 100),
u!(120, 150),
u!(170, 180),
];
let r2 = Region::from_rects2(&rects[..]);
let r3 = r1.intersect_tagged(&r2);
assert_eq!(
&r3.rects[..],
&[
r!(10, 20, 0),
r!(50, 60, 0),
r!(70, 100, 0),
r!(120, 130, 1),
r!(140, 150, 0),
r!(170, 180, 0),
],
);
}
#[test]
fn new_saturating() {
let r1 = Rect::new_sized_saturating(10, 10, -5, -5);
assert!(r1.is_empty());
assert_eq!(r1.x1(), 10);
assert_eq!(r1.x2(), 10);
let r2 = Rect::new_saturating(100, 100, 50, 50);
assert!(r2.is_empty());
assert_eq!(r2.x1(), 100);
assert_eq!(r2.x2(), 100);
let r3 = Rect::new_sized_saturating(i32::MAX - 10, 0, 100, 10);
assert_eq!(r3.x2(), i32::MAX);
}
#[test]
fn dist_squared() {
let r1 = Rect::new_sized_saturating(i32::MIN, i32::MIN, 0, 0);
assert_eq!(
r1.dist_squared(i32::MAX, i32::MAX),
(1 << 65) + 2 - (1 << 34),
);
}

View file

@ -0,0 +1,8 @@
[package]
name = "jay-gfx-types"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
uapi = "0.2.13"

View file

@ -0,0 +1,38 @@
use {
std::{cell::Cell, error::Error, rc::Rc},
uapi::OwnedFd,
};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
pub enum AlphaMode {
#[default]
PremultipliedElectrical,
PremultipliedOptical,
Straight,
}
pub trait ShmMemory {
fn len(&self) -> usize;
fn safe_access(&self) -> ShmMemoryBacking;
fn access(&self, f: &mut dyn FnMut(&[Cell<u8>])) -> Result<(), Box<dyn Error + Sync + Send>>;
}
pub enum ShmMemoryBacking {
Ptr(*const [Cell<u8>]),
Fd(Rc<OwnedFd>, usize),
}
impl ShmMemory for Vec<Cell<u8>> {
fn len(&self) -> usize {
self.len()
}
fn safe_access(&self) -> ShmMemoryBacking {
ShmMemoryBacking::Ptr(&**self)
}
fn access(&self, f: &mut dyn FnMut(&[Cell<u8>])) -> Result<(), Box<dyn Error + Sync + Send>> {
f(self);
Ok(())
}
}

View file

@ -0,0 +1,14 @@
[package]
name = "jay-input-types"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
description = "Input data types for the Jay compositor"
repository = "https://github.com/mahkoh/jay"
[dependencies]
jay-output-types = { version = "0.1.0", path = "../output-types" }
jay-units = { version = "0.1.0", path = "../units" }
jay-utils = { version = "0.1.0", path = "../utils" }
linearize = { version = "0.1.3", features = ["derive"] }

View file

@ -0,0 +1,482 @@
use {
jay_output_types::ConnectorId,
jay_units::Fixed,
jay_utils::{numcell::NumCell, static_text::StaticText},
linearize::Linearize,
std::{
fmt::{Display, Formatter},
ops::{BitOr, BitOrAssign},
},
};
macro_rules! linear_ids {
($ids:ident, $id:ident $(,)?) => {
linear_ids!($ids, $id, u32);
};
($ids:ident, $id:ident, $ty:ty $(,)?) => {
#[derive(Debug)]
pub struct $ids {
next: NumCell<$ty>,
}
impl Default for $ids {
fn default() -> Self {
Self {
next: NumCell::new(1),
}
}
}
impl $ids {
pub fn next(&self) -> $id {
$id(self.next.fetch_add(1))
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct $id($ty);
impl $id {
pub fn raw(&self) -> $ty {
self.0
}
pub fn from_raw(id: $ty) -> Self {
Self(id)
}
}
impl Display for $id {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
};
}
#[derive(Debug, Copy, Clone, PartialEq, Linearize)]
pub enum InputDeviceAccelProfile {
Flat,
Adaptive,
}
impl StaticText for InputDeviceAccelProfile {
fn text(&self) -> &'static str {
match self {
InputDeviceAccelProfile::Flat => "Flat",
InputDeviceAccelProfile::Adaptive => "Adaptive",
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Linearize)]
pub enum InputDeviceClickMethod {
None,
ButtonAreas,
Clickfinger,
}
impl StaticText for InputDeviceClickMethod {
fn text(&self) -> &'static str {
match self {
InputDeviceClickMethod::None => "none",
InputDeviceClickMethod::ButtonAreas => "button-areas",
InputDeviceClickMethod::Clickfinger => "clickfinger",
}
}
}
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Linearize)]
pub enum InputDeviceCapability {
Keyboard,
Pointer,
Touch,
TabletTool,
TabletPad,
Gesture,
Switch,
}
impl StaticText for InputDeviceCapability {
fn text(&self) -> &'static str {
match self {
InputDeviceCapability::Keyboard => "keyboard",
InputDeviceCapability::Pointer => "pointer",
InputDeviceCapability::Touch => "touch",
InputDeviceCapability::TabletTool => "tablet tool",
InputDeviceCapability::TabletPad => "tablet pad",
InputDeviceCapability::Gesture => "gesture",
InputDeviceCapability::Switch => "switch",
}
}
}
linear_ids!(InputDeviceGroupIds, InputDeviceGroupId, usize);
linear_ids!(InputDeviceIds, InputDeviceId);
pub type TransformMatrix = [[f64; 2]; 2];
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum KeyState {
Released,
Pressed,
Repeated,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ButtonState {
Released,
Pressed,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Linearize)]
pub enum ScrollAxis {
Vertical = 0,
Horizontal = 1,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum AxisSource {
Wheel,
Finger,
Continuous,
}
pub const AXIS_120: i32 = 120;
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
pub struct Leds(pub u32);
pub const LED_NUM_LOCK: Leds = Leds(1 << 0);
pub const LED_CAPS_LOCK: Leds = Leds(1 << 1);
pub const LED_SCROLL_LOCK: Leds = Leds(1 << 2);
pub const LED_COMPOSE: Leds = Leds(1 << 3);
pub const LED_KANA: Leds = Leds(1 << 4);
impl Leds {
pub const fn none() -> Self {
Self(0)
}
pub const fn raw(self) -> u32 {
self.0
}
}
impl BitOr for Leds {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
impl BitOrAssign for Leds {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
linear_ids!(TabletIds, TabletId);
#[derive(Debug, Clone)]
pub struct TabletInit {
pub id: TabletId,
pub group: InputDeviceGroupId,
pub name: String,
pub pid: u32,
pub vid: u32,
pub bustype: Option<u32>,
pub path: String,
}
linear_ids!(TabletToolIds, TabletToolId, usize);
#[derive(Debug, Clone)]
pub struct TabletToolInit {
pub tablet_id: TabletId,
pub id: TabletToolId,
pub type_: TabletToolType,
pub hardware_serial: u64,
pub hardware_id_wacom: u64,
pub capabilities: Vec<TabletToolCapability>,
}
linear_ids!(TabletPadIds, TabletPadId);
#[derive(Debug, Clone)]
pub struct TabletPadInit {
pub id: TabletPadId,
pub group: InputDeviceGroupId,
pub path: String,
pub buttons: u32,
pub strips: u32,
pub rings: u32,
pub dials: u32,
pub groups: Vec<TabletPadGroupInit>,
}
#[derive(Debug, Clone)]
pub struct TabletPadGroupInit {
pub buttons: Vec<u32>,
pub rings: Vec<u32>,
pub strips: Vec<u32>,
pub dials: Vec<u32>,
pub modes: u32,
pub mode: u32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PadButtonState {
Released,
Pressed,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ToolButtonState {
Released,
Pressed,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum TabletToolType {
Pen,
Eraser,
Brush,
Pencil,
Airbrush,
Finger,
Mouse,
Lens,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum TabletToolCapability {
Tilt,
Pressure,
Distance,
Rotation,
Slider,
Wheel,
}
#[derive(Copy, Clone, Debug)]
pub enum TabletRingEventSource {
Finger,
}
#[derive(Copy, Clone, Debug)]
pub enum TabletStripEventSource {
Finger,
}
#[derive(Debug, Default)]
pub struct TabletToolChanges {
pub down: Option<bool>,
pub pos: Option<TabletTool2dChange<TabletToolPositionChange>>,
pub pressure: Option<f64>,
pub distance: Option<f64>,
pub tilt: Option<TabletTool2dChange<f64>>,
pub rotation: Option<f64>,
pub slider: Option<f64>,
pub wheel: Option<TabletToolWheelChange>,
}
#[derive(Copy, Clone, Debug)]
pub struct TabletTool2dChange<T> {
pub x: T,
pub y: T,
}
#[derive(Copy, Clone, Debug)]
pub struct TabletToolPositionChange {
pub x: f64,
pub dx: f64,
}
#[derive(Copy, Clone, Debug)]
pub struct TabletToolWheelChange {
pub degrees: f64,
pub clicks: i32,
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum SwitchEvent {
LidOpened,
LidClosed,
ConvertedToLaptop,
ConvertedToTablet,
}
#[derive(Debug)]
pub enum InputEvent {
Key {
time_usec: u64,
key: u32,
state: KeyState,
},
ConnectorPosition {
time_usec: u64,
connector: ConnectorId,
x: Fixed,
y: Fixed,
},
Motion {
time_usec: u64,
dx: Fixed,
dy: Fixed,
dx_unaccelerated: Fixed,
dy_unaccelerated: Fixed,
},
MotionAbsolute {
time_usec: u64,
x_normed: f32,
y_normed: f32,
},
Button {
time_usec: u64,
button: u32,
state: ButtonState,
},
AxisPx {
dist: Fixed,
axis: ScrollAxis,
inverted: bool,
},
AxisSource {
source: AxisSource,
},
AxisStop {
axis: ScrollAxis,
},
Axis120 {
dist: i32,
axis: ScrollAxis,
inverted: bool,
},
AxisFrame {
time_usec: u64,
},
SwipeBegin {
time_usec: u64,
finger_count: u32,
},
SwipeUpdate {
time_usec: u64,
dx: Fixed,
dy: Fixed,
dx_unaccelerated: Fixed,
dy_unaccelerated: Fixed,
},
SwipeEnd {
time_usec: u64,
cancelled: bool,
},
PinchBegin {
time_usec: u64,
finger_count: u32,
},
PinchUpdate {
time_usec: u64,
dx: Fixed,
dy: Fixed,
dx_unaccelerated: Fixed,
dy_unaccelerated: Fixed,
scale: Fixed,
rotation: Fixed,
},
PinchEnd {
time_usec: u64,
cancelled: bool,
},
HoldBegin {
time_usec: u64,
finger_count: u32,
},
HoldEnd {
time_usec: u64,
cancelled: bool,
},
SwitchEvent {
time_usec: u64,
event: SwitchEvent,
},
TabletToolAdded {
time_usec: u64,
init: Box<TabletToolInit>,
},
TabletToolChanged {
time_usec: u64,
id: TabletToolId,
changes: Box<TabletToolChanges>,
},
TabletToolButton {
time_usec: u64,
id: TabletToolId,
button: u32,
state: ToolButtonState,
},
TabletToolRemoved {
time_usec: u64,
id: TabletToolId,
},
TabletPadButton {
time_usec: u64,
id: TabletPadId,
button: u32,
state: PadButtonState,
},
TabletPadModeSwitch {
time_usec: u64,
pad: TabletPadId,
group: u32,
mode: u32,
},
TabletPadRing {
time_usec: u64,
pad: TabletPadId,
ring: u32,
source: Option<TabletRingEventSource>,
angle: Option<f64>,
},
TabletPadStrip {
time_usec: u64,
pad: TabletPadId,
strip: u32,
source: Option<TabletStripEventSource>,
position: Option<f64>,
},
TabletPadDial {
time_usec: u64,
pad: TabletPadId,
dial: u32,
value120: i32,
},
TouchDown {
time_usec: u64,
id: i32,
x_normed: Fixed,
y_normed: Fixed,
},
TouchUp {
time_usec: u64,
id: i32,
},
TouchMotion {
time_usec: u64,
id: i32,
x_normed: Fixed,
y_normed: Fixed,
},
TouchCancel {
time_usec: u64,
id: i32,
},
TouchFrame {
time_usec: u64,
},
}

View file

@ -0,0 +1,15 @@
[package]
name = "jay-io-uring"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-async-engine = { version = "0.1.0", path = "../async-engine" }
jay-time = { version = "0.1.0", path = "../time" }
jay-utils = { version = "0.1.0", path = "../utils" }
log = { version = "0.4.20", features = ["std"] }
run-on-drop = "1.0.0"
thiserror = "2.0.11"
uapi = "0.2.13"

View file

@ -0,0 +1,33 @@
use {
crate::IoUringData,
jay_utils::numcell::NumCell,
std::{cell::Cell, future::poll_fn, rc::Rc, task::Poll},
};
pub struct Debouncer {
pub(super) cur: NumCell<u64>,
pub(super) max: u64,
pub(super) iteration: Cell<u64>,
pub(super) ring: Rc<IoUringData>,
}
impl Debouncer {
pub async fn debounce(&self) {
let iteration = self.ring.iteration.get();
if self.iteration.replace(iteration) != iteration {
self.cur.set(0);
}
if self.cur.fetch_add(1) > self.max {
poll_fn(|ctx| {
if self.ring.iteration.get() > iteration {
Poll::Ready(())
} else {
self.ring.yields.push(ctx.waker().clone());
Poll::Pending
}
})
.await;
self.cur.set(0);
}
}
}

590
crates/io-uring/src/lib.rs Normal file
View file

@ -0,0 +1,590 @@
pub use ops::{
TaskResultExt,
poll_external::{PendingPoll, PollCallback},
timeout_external::{PendingTimeout, TimeoutCallback},
};
use {
crate::{
debounce::Debouncer,
ops::{
accept::AcceptTask, async_cancel::AsyncCancelTask, connect::ConnectTask,
poll::PollTask, poll_external::PollExternalTask, read_write::ReadWriteTask,
read_write_no_cancel::ReadWriteNoCancelTask, recvmsg::RecvmsgTask,
sendmsg::SendmsgTask, timeout::TimeoutTask, timeout_external::TimeoutExternalTask,
timeout_link::TimeoutLinkTask,
},
pending_result::PendingResults,
sys::{
IORING_ENTER_GETEVENTS, IORING_FEAT_NODROP, IORING_OFF_CQ_RING, IORING_OFF_SQ_RING,
IORING_OFF_SQES, IORING_SETUP_COOP_TASKRUN, IORING_SETUP_DEFER_TASKRUN,
IORING_SETUP_SINGLE_ISSUER, IORING_SETUP_SUBMIT_ALL, IOSQE_IO_LINK, io_uring_cqe,
io_uring_enter, io_uring_params, io_uring_setup, io_uring_sqe,
},
},
jay_async_engine::AsyncEngine,
jay_utils::{
asyncevent::AsyncEvent,
bitflags::BitflagsExt,
buf::Buf,
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
mmap::{Mmapped, mmap},
numcell::NumCell,
oserror::OsError,
ptr_ext::{MutPtrExt, PtrExt},
stack::Stack,
syncqueue::SyncQueue,
},
std::{
cell::{Cell, RefCell, UnsafeCell},
rc::Rc,
sync::atomic::{
AtomicU32,
Ordering::{Acquire, Relaxed, Release},
},
task::Waker,
},
thiserror::Error,
uapi::{
OwnedFd,
c::{self},
},
};
macro_rules! map_err {
($n:expr) => {{
let n = $n;
if n < 0 {
Err(jay_utils::oserror::OsError::from(-n as uapi::c::c_int))
} else {
Ok(n)
}
}};
}
mod debounce;
pub mod line_logger;
pub mod object_drop_queue;
mod ops;
mod pending_result;
mod sys;
pub mod timer;
#[derive(Debug, Error)]
pub enum IoUringError {
#[error(transparent)]
OsError(#[from] OsError),
#[error("Could not create an io-uring")]
CreateUring(#[source] OsError),
#[error("The kernel does not support the IORING_FEAT_NODROP feature")]
NoDrop,
#[error("Could not map the submission queue ring")]
MapSqRing(#[source] OsError),
#[error("Could not map the submission queue entries")]
MapSqEntries(#[source] OsError),
#[error("Could not map the completion queue ring")]
MapCqRing(#[source] OsError),
#[error("The io-uring has already been destroyed")]
Destroyed,
#[error("io_uring_enter failed")]
Enter(#[source] OsError),
#[error("Kernel sent invalid cmsg data")]
InvalidCmsgData,
}
pub struct IoUring {
ring: Rc<IoUringData>,
}
impl Drop for IoUring {
fn drop(&mut self) {
self.ring.kill();
}
}
impl IoUring {
pub fn new(eng: &Rc<AsyncEngine>, entries: u32) -> Result<Rc<Self>, IoUringError> {
let feature_levels = [
IORING_SETUP_SUBMIT_ALL, // 5.18
IORING_SETUP_COOP_TASKRUN, // 5.19
IORING_SETUP_SINGLE_ISSUER, // 6.0
IORING_SETUP_DEFER_TASKRUN, // 6.1
];
let mut feature_levels = &feature_levels[..];
let mut params;
let fd = loop {
params = io_uring_params::default();
for &flags in feature_levels {
params.flags |= flags;
}
match io_uring_setup(entries, &mut params) {
Ok(f) => break f,
Err(e) => {
if let Some((_, levels)) = feature_levels.split_last() {
feature_levels = levels;
} else {
return Err(IoUringError::CreateUring(e));
}
}
}
};
if !params.features.contains(IORING_FEAT_NODROP) {
return Err(IoUringError::NoDrop);
}
let sqmap_map = mmap(
(params.sq_off.array + params.sq_entries * 4) as _,
c::PROT_READ | c::PROT_WRITE,
c::MAP_SHARED | c::MAP_POPULATE,
fd.raw(),
IORING_OFF_SQ_RING as _,
);
let sqmap_map = match sqmap_map {
Ok(map) => map,
Err(e) => return Err(IoUringError::MapSqRing(e)),
};
let sqesmap_map = mmap(
params.sq_entries as usize * size_of::<io_uring_sqe>(),
c::PROT_READ | c::PROT_WRITE,
c::MAP_SHARED | c::MAP_POPULATE,
fd.raw(),
IORING_OFF_SQES as _,
);
let sqesmap_map = match sqesmap_map {
Ok(map) => map,
Err(e) => return Err(IoUringError::MapSqEntries(e)),
};
let cqmap_map = mmap(
params.cq_off.cqes as usize + params.cq_entries as usize * size_of::<io_uring_cqe>(),
c::PROT_READ | c::PROT_WRITE,
c::MAP_SHARED | c::MAP_POPULATE,
fd.raw(),
IORING_OFF_CQ_RING as _,
);
let cqmap_map = match cqmap_map {
Ok(map) => map,
Err(e) => return Err(IoUringError::MapCqRing(e)),
};
let sqmask = unsafe {
*(sqmap_map.ptr as *const u8)
.add(params.sq_off.ring_mask as _)
.cast()
};
let sqhead = unsafe {
(sqmap_map.ptr as *const u8)
.add(params.sq_off.head as _)
.cast()
};
let sqtail = unsafe {
(sqmap_map.ptr as *const u8)
.add(params.sq_off.tail as _)
.cast()
};
let sqmap = unsafe {
let base = (sqmap_map.ptr as *const u8)
.add(params.sq_off.array as _)
.cast();
std::slice::from_raw_parts(base, params.sq_entries as _)
};
let sqesmap = unsafe {
let base = (sqesmap_map.ptr as *const u8).cast();
std::slice::from_raw_parts(base, params.sq_entries as _)
};
let cqmask = unsafe {
*(cqmap_map.ptr as *const u8)
.add(params.cq_off.ring_mask as _)
.cast()
};
let cqhead = unsafe {
(cqmap_map.ptr as *const u8)
.add(params.cq_off.head as _)
.cast()
};
let cqtail = unsafe {
(cqmap_map.ptr as *const u8)
.add(params.cq_off.tail as _)
.cast()
};
let cqmap = unsafe {
let base = (cqmap_map.ptr as *const u8)
.add(params.cq_off.cqes as _)
.cast();
std::slice::from_raw_parts(base, params.cq_entries as _)
};
let data = Rc::new(IoUringData {
destroyed: Cell::new(false),
fd,
eng: eng.clone(),
_sqesmap_map: sqesmap_map,
_sqmap_map: sqmap_map,
sqmask,
sqlen: params.sq_entries,
sqhead,
sqtail,
sqmap,
sqesmap,
_cqmap_map: cqmap_map,
cqmask,
cqhead,
cqtail,
cqmap,
cqes_consumed: Default::default(),
next: Default::default(),
to_encode: Default::default(),
pending_in_kernel: Default::default(),
tasks: Default::default(),
pending_results: Default::default(),
cached_read_writes: Default::default(),
cached_read_writes_no_cancel: Default::default(),
cached_cancels: Default::default(),
cached_polls: Default::default(),
cached_polls_external: Default::default(),
cached_sendmsg: Default::default(),
cached_recvmsg: Default::default(),
cached_timeouts: Default::default(),
cached_timeouts_external: Default::default(),
cached_timeout_links: Default::default(),
cached_cmsg_bufs: Default::default(),
cached_connects: Default::default(),
cached_accepts: Default::default(),
fd_ids_scratch: Default::default(),
iteration: Default::default(),
yields: Default::default(),
});
Ok(Rc::new(Self { ring: data }))
}
pub fn stop(&self) {
self.ring.kill();
}
pub fn run(&self) -> Result<(), IoUringError> {
let res = self.ring.run();
self.ring.kill();
res
}
pub fn cancel(&self, id: IoUringTaskId) {
self.ring.cancel_task(id);
}
pub fn debouncer(&self, max: u64) -> Debouncer {
Debouncer {
cur: Default::default(),
max,
iteration: Cell::new(self.ring.iteration.get()),
ring: self.ring.clone(),
}
}
}
struct IoUringData {
destroyed: Cell<bool>,
fd: OwnedFd,
eng: Rc<AsyncEngine>,
_sqesmap_map: Mmapped,
_sqmap_map: Mmapped,
sqmask: u32,
sqlen: u32,
sqhead: *const AtomicU32,
sqtail: *const AtomicU32,
sqmap: *const [Cell<c::c_uint>],
sqesmap: *const [UnsafeCell<io_uring_sqe>],
_cqmap_map: Mmapped,
cqmask: u32,
cqhead: *const AtomicU32,
cqtail: *const AtomicU32,
cqmap: *const [Cell<io_uring_cqe>],
cqes_consumed: AsyncEvent,
next: IoUringTaskIds,
to_encode: SyncQueue<IoUringTaskId>,
pending_in_kernel: CopyHashMap<IoUringTaskId, ()>,
tasks: CopyHashMap<IoUringTaskId, Box<dyn Task>>,
pending_results: PendingResults,
cached_read_writes: Stack<Box<ReadWriteTask>>,
cached_read_writes_no_cancel: Stack<Box<ReadWriteNoCancelTask>>,
cached_cancels: Stack<Box<AsyncCancelTask>>,
cached_polls: Stack<Box<PollTask>>,
cached_polls_external: Stack<Box<PollExternalTask>>,
cached_sendmsg: Stack<Box<SendmsgTask>>,
cached_recvmsg: Stack<Box<RecvmsgTask>>,
cached_timeouts: Stack<Box<TimeoutTask>>,
cached_timeouts_external: Stack<Box<TimeoutExternalTask>>,
cached_timeout_links: Stack<Box<TimeoutLinkTask>>,
cached_cmsg_bufs: Stack<Buf>,
cached_connects: Stack<Box<ConnectTask>>,
cached_accepts: Stack<Box<AcceptTask>>,
fd_ids_scratch: RefCell<Vec<c::c_int>>,
iteration: NumCell<u64>,
yields: SyncQueue<Waker>,
}
unsafe trait Task {
fn id(&self) -> IoUringTaskId;
fn complete(self: Box<Self>, ring: &IoUringData, res: i32);
fn encode(&self, sqe: &mut io_uring_sqe);
fn is_cancel(&self) -> bool {
false
}
fn has_timeout(&self) -> bool {
false
}
}
impl IoUringData {
fn run(&self) -> Result<(), IoUringError> {
let mut to_submit = 0;
loop {
self.iteration.fetch_add(1);
while let Some(ev) = self.yields.pop() {
ev.wake();
}
loop {
self.eng.dispatch();
if self.destroyed.get() {
return Ok(());
}
if !self.dispatch_completions() {
break;
}
}
to_submit += self.encode();
let res = {
let (to_submit, mut min_complete, flags) = if to_submit == 0 {
(0, 1, IORING_ENTER_GETEVENTS)
} else if self.to_encode.is_empty() {
(to_submit as _, 1, IORING_ENTER_GETEVENTS)
} else {
(!0, 0, 0)
};
if self.yields.is_not_empty() {
min_complete = 0;
}
io_uring_enter(self.fd.raw(), to_submit, min_complete, flags)
};
let mut submitted_any = false;
match res {
Ok(n) => {
if n > 0 {
submitted_any = true;
}
to_submit -= n;
}
Err(e) => {
if !matches!(e.0, c::EAGAIN | c::EBUSY | c::EINTR) {
return Err(IoUringError::Enter(e));
}
}
}
if to_submit > 0 && !submitted_any {
let res = io_uring_enter(self.fd.raw(), 0, 1, IORING_ENTER_GETEVENTS);
if let Err(e) = res {
if e.0 != c::EINTR {
return Err(IoUringError::Enter(e));
}
}
}
}
}
fn dispatch_completions(&self) -> bool {
unsafe {
let mut head = self.cqhead.deref().load(Relaxed);
let tail = self.cqtail.deref().load(Acquire);
if head == tail {
return false;
}
while head != tail {
let idx = (head & self.cqmask) as usize;
let entry = self.cqmap.deref()[idx].get();
head = head.wrapping_add(1);
self.cqhead.deref().store(head, Release);
let id = IoUringTaskId(entry.user_data);
if let Some(pending) = self.tasks.remove(&id) {
self.pending_in_kernel.remove(&id);
pending.complete(self, entry.res);
}
}
self.cqhead.deref().store(head, Release);
self.cqes_consumed.trigger();
true
}
}
fn encode(&self) -> usize {
let tasks = self.tasks.lock();
let mut encoded = 0;
unsafe {
let mut tail = self.sqtail.deref().load(Relaxed);
let head = self.sqhead.deref().load(Acquire);
let available = self.sqlen - tail.wrapping_sub(head);
while encoded < available {
let id = match self.to_encode.pop() {
Some(t) => t,
_ => break,
};
let task = match tasks.get(&id) {
Some(t) => t,
_ => continue,
};
let has_timeout = task.has_timeout();
if has_timeout && (available - encoded) < 2 {
self.to_encode.push_front(id);
break;
}
self.pending_in_kernel.set(id, ());
let idx = (tail & self.sqmask) as usize;
let sqe = self.sqesmap.deref()[idx].get().deref_mut();
self.sqmap.deref()[idx].set(idx as _);
*sqe = Default::default();
sqe.user_data = id.raw();
task.encode(sqe);
if has_timeout {
sqe.flags |= IOSQE_IO_LINK;
}
tail = tail.wrapping_add(1);
encoded += 1;
}
self.sqtail.deref().store(tail, Release);
}
encoded as usize
}
fn id(&self) -> Cancellable<'_> {
Cancellable {
id: self.id_raw(),
data: self,
}
}
fn id_raw(&self) -> IoUringTaskId {
self.next.next()
}
fn cancel_task(&self, id: IoUringTaskId) {
if !self.tasks.contains(&id) {
return;
}
if !self.pending_in_kernel.contains(&id) {
self.tasks
.remove(&id)
.unwrap()
.complete(self, -c::ECANCELED);
return;
}
self.cancel_task_in_kernel(id);
}
fn schedule(&self, t: Box<dyn Task>) {
assert!(!self.destroyed.get());
self.to_encode.push(t.id());
self.tasks.set(t.id(), t);
}
fn check_destroyed(&self) -> Result<(), IoUringError> {
if self.destroyed.get() {
Err(IoUringError::Destroyed)
} else {
Ok(())
}
}
fn kill(&self) {
self.eng.stop();
let mut to_cancel = vec![];
for task in self.tasks.lock().values() {
if !task.is_cancel() {
to_cancel.push(task.id());
}
}
for task in to_cancel {
self.cancel_task(task);
}
self.destroyed.set(true);
while !self.tasks.is_empty() {
self.encode();
let _ = io_uring_enter(self.fd.raw(), u32::MAX, 0, 0);
let res = io_uring_enter(self.fd.raw(), 0, 1, IORING_ENTER_GETEVENTS);
if let Err(e) = res {
panic!("Could not wait for io_uring to drain: {}", ErrorFmt(e));
}
while self.dispatch_completions() {
// nothing
}
}
}
fn cmsg_buf(&self) -> Buf {
self.cached_cmsg_bufs
.pop()
.unwrap_or_else(|| Buf::new(1024))
}
}
#[derive(Debug)]
struct IoUringTaskIds {
next: NumCell<u64>,
}
impl Default for IoUringTaskIds {
fn default() -> Self {
Self {
next: NumCell::new(1),
}
}
}
impl IoUringTaskIds {
fn next(&self) -> IoUringTaskId {
IoUringTaskId(self.next.fetch_add(1))
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct IoUringTaskId(u64);
impl IoUringTaskId {
#[allow(clippy::allow_attributes, dead_code)]
pub fn raw(&self) -> u64 {
self.0
}
#[allow(clippy::allow_attributes, dead_code)]
pub fn from_raw(id: u64) -> Self {
Self(id)
}
}
impl std::fmt::Display for IoUringTaskId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
#[expect(clippy::derivable_impls)]
impl Default for IoUringTaskId {
fn default() -> Self {
Self(0)
}
}
struct Cancellable<'a> {
id: IoUringTaskId,
data: &'a IoUringData,
}
impl<'a> Drop for Cancellable<'a> {
fn drop(&mut self) {
self.data.cancel_task(self.id);
}
}

View file

@ -0,0 +1,33 @@
use {
crate::{IoUring, IoUringError},
jay_utils::{buf::Buf, vecdeque_ext::VecDequeExt},
std::{collections::VecDeque, rc::Rc},
uapi::OwnedFd,
};
pub async fn log_lines(
ring: &IoUring,
fd: &Rc<OwnedFd>,
mut f: impl FnMut(&[u8], &[u8]),
) -> Result<(), IoUringError> {
let mut buf = VecDeque::<u8>::new();
let mut buf2 = Buf::new(1024);
let mut done = false;
while !done {
let n = ring.read(fd, buf2.clone()).await?;
buf.extend(&buf2[..n]);
if n == 0 {
done = true;
}
while let Some(pos) = buf.iter().position(|b| b == &b'\n') {
let (left, right) = buf.get_slices(..pos);
f(left, right);
buf.drain(..=pos);
}
}
if !buf.is_empty() {
let (left, right) = buf.as_slices();
f(left, right);
}
Ok(())
}

View file

@ -0,0 +1,83 @@
use {
crate::{IoUring, PendingPoll, PollCallback},
jay_utils::{errorfmt::ErrorFmt, oserror::OsError, stack::Stack},
std::{
cell::{Cell, RefCell},
rc::Rc,
},
uapi::{OwnedFd, c::c_short},
};
pub struct ObjectDropQueue<T> {
#[allow(dead_code)]
ring: Rc<IoUring>,
killed: Cell<bool>,
pending: RefCell<Vec<Option<(T, PendingPoll)>>>,
stack: Stack<Rc<Pollable<T>>>,
}
struct Pollable<T> {
queue: Rc<ObjectDropQueue<T>>,
idx: usize,
}
impl<T> ObjectDropQueue<T> {
pub fn new(ring: &Rc<IoUring>) -> Self {
Self {
ring: ring.clone(),
killed: Default::default(),
pending: Default::default(),
stack: Default::default(),
}
}
#[allow(dead_code)]
pub fn push(self: &Rc<Self>, fd: &Rc<OwnedFd>, t: T)
where
T: 'static,
{
if self.killed.get() {
return;
}
let pending = &mut *self.pending.borrow_mut();
let pollable = match self.stack.pop() {
Some(p) => p,
None => {
let pollable = Rc::new(Pollable {
queue: self.clone(),
idx: pending.len(),
});
pending.push(None);
pollable
}
};
let idx = pollable.idx;
match self.ring.readable_external(fd, pollable) {
Ok(p) => {
pending[idx] = Some((t, p));
}
Err(e) => {
log::error!("Could not register object: {}", ErrorFmt(e));
}
}
}
pub fn kill(&self) {
self.killed.set(true);
self.pending.take();
self.stack.take();
}
}
impl<T> PollCallback for Pollable<T> {
fn completed(self: Rc<Self>, res: Result<c_short, OsError>) {
if let Err(e) = res {
log::error!("Could not wait for fd to become readable: {}", ErrorFmt(e));
}
let q = &self.queue;
if !q.killed.get() {
q.pending.borrow_mut()[self.idx] = None;
q.stack.push(self.clone());
}
}
}

View file

@ -0,0 +1,30 @@
use {crate::IoUringError, jay_utils::oserror::OsError};
pub mod accept;
pub mod async_cancel;
pub mod connect;
pub mod poll;
pub mod poll_external;
pub mod read_write;
pub mod read_write_no_cancel;
pub mod recvmsg;
pub mod sendmsg;
pub mod timeout;
pub mod timeout_external;
pub mod timeout_link;
pub type TaskResult<T> = Result<Result<T, OsError>, IoUringError>;
pub trait TaskResultExt<T> {
fn merge(self) -> Result<T, IoUringError>;
}
impl<T> TaskResultExt<T> for TaskResult<T> {
fn merge(self) -> Result<T, IoUringError> {
match self {
Ok(Ok(t)) => Ok(t),
Ok(Err(e)) => Err(IoUringError::OsError(e)),
Err(e) => Err(e),
}
}
}

View file

@ -0,0 +1,67 @@
use {
crate::{
IoUring, IoUringData, IoUringError, IoUringTaskId, Task, TaskResultExt,
pending_result::PendingResult,
sys::{IORING_OP_ACCEPT, io_uring_sqe},
},
std::rc::Rc,
uapi::{OwnedFd, c},
};
impl IoUring {
pub async fn accept(
&self,
fd: &Rc<OwnedFd>,
flags: c::c_int,
) -> Result<Rc<OwnedFd>, IoUringError> {
self.ring.check_destroyed()?;
let id = self.ring.id();
let pr = self.ring.pending_results.acquire();
{
let mut pw = self.ring.cached_accepts.pop().unwrap_or_default();
pw.id = id.id;
pw.fd = fd.raw() as _;
pw.flags = flags as _;
pw.data = Some(Data {
pr: pr.clone(),
_fd: fd.clone(),
});
self.ring.schedule(pw);
}
Ok(pr.await.map(OwnedFd::new).map(Rc::new)).merge()
}
}
struct Data {
pr: PendingResult,
_fd: Rc<OwnedFd>,
}
#[derive(Default)]
pub struct AcceptTask {
id: IoUringTaskId,
fd: i32,
flags: u32,
data: Option<Data>,
}
unsafe impl Task for AcceptTask {
fn id(&self) -> IoUringTaskId {
self.id
}
fn complete(mut self: Box<Self>, ring: &IoUringData, res: i32) {
if let Some(data) = self.data.take() {
data.pr.complete(res);
}
ring.cached_accepts.push(self);
}
fn encode(&self, sqe: &mut io_uring_sqe) {
sqe.opcode = IORING_OP_ACCEPT;
sqe.fd = self.fd;
sqe.u2.addr = 0;
sqe.u1.addr2 = 0;
sqe.u3.accept_flags = self.flags;
}
}

View file

@ -0,0 +1,48 @@
use {
crate::{
IoUringData, IoUringTaskId, Task,
sys::{IORING_OP_ASYNC_CANCEL, io_uring_sqe},
},
jay_utils::errorfmt::ErrorFmt,
uapi::c,
};
#[derive(Default)]
pub struct AsyncCancelTask {
id: IoUringTaskId,
target: IoUringTaskId,
}
impl IoUringData {
pub fn cancel_task_in_kernel(&self, target: IoUringTaskId) {
let id = self.id_raw();
let mut task = self.cached_cancels.pop().unwrap_or_default();
task.id = id;
task.target = target;
self.schedule(task);
}
}
unsafe impl Task for AsyncCancelTask {
fn id(&self) -> IoUringTaskId {
self.id
}
fn complete(self: Box<Self>, ring: &IoUringData, res: i32) {
if let Err(e) = map_err!(res) {
if e.0 != c::ENOENT {
log::debug!("Could not cancel task: {}", ErrorFmt(e));
}
}
ring.cached_cancels.push(self);
}
fn encode(&self, sqe: &mut io_uring_sqe) {
sqe.opcode = IORING_OP_ASYNC_CANCEL;
sqe.u2.addr = self.target.raw();
}
fn is_cancel(&self) -> bool {
true
}
}

View file

@ -0,0 +1,77 @@
use {
crate::{
IoUring, IoUringData, IoUringError, IoUringTaskId, Task, TaskResultExt,
pending_result::PendingResult,
sys::{IORING_OP_CONNECT, io_uring_sqe},
},
std::{ptr, rc::Rc},
uapi::{OwnedFd, SockAddr, c},
};
impl IoUring {
pub async fn connect<T: SockAddr>(&self, fd: &Rc<OwnedFd>, t: &T) -> Result<(), IoUringError> {
self.ring.check_destroyed()?;
let id = self.ring.id();
let pr = self.ring.pending_results.acquire();
{
let mut pw = self.ring.cached_connects.pop().unwrap_or_default();
pw.id = id.id;
pw.fd = fd.raw() as _;
unsafe {
ptr::copy_nonoverlapping(t, &mut pw.sockaddr as *mut _ as *mut _, 1);
}
pw.addrlen = size_of::<T>() as _;
pw.data = Some(Data {
pr: pr.clone(),
_fd: fd.clone(),
});
self.ring.schedule(pw);
}
Ok(pr.await.map(drop)).merge()
}
}
struct Data {
pr: PendingResult,
_fd: Rc<OwnedFd>,
}
pub struct ConnectTask {
id: IoUringTaskId,
fd: i32,
sockaddr: c::sockaddr_storage,
addrlen: u64,
data: Option<Data>,
}
impl Default for ConnectTask {
fn default() -> Self {
Self {
id: Default::default(),
fd: 0,
sockaddr: uapi::pod_zeroed(),
addrlen: 0,
data: None,
}
}
}
unsafe impl Task for ConnectTask {
fn id(&self) -> IoUringTaskId {
self.id
}
fn complete(mut self: Box<Self>, ring: &IoUringData, res: i32) {
if let Some(data) = self.data.take() {
data.pr.complete(res);
}
ring.cached_connects.push(self);
}
fn encode(&self, sqe: &mut io_uring_sqe) {
sqe.opcode = IORING_OP_CONNECT;
sqe.fd = self.fd;
sqe.u2.addr = &self.sockaddr as *const _ as _;
sqe.u1.off = self.addrlen;
}
}

View file

@ -0,0 +1,70 @@
use {
crate::{
IoUring, IoUringData, IoUringError, IoUringTaskId, Task, TaskResultExt,
ops::TaskResult,
pending_result::PendingResult,
sys::{IORING_OP_POLL_ADD, io_uring_sqe},
},
std::rc::Rc,
uapi::{OwnedFd, c},
};
impl IoUring {
pub async fn poll(&self, fd: &Rc<OwnedFd>, events: c::c_short) -> TaskResult<c::c_short> {
self.ring.check_destroyed()?;
let id = self.ring.id();
let pr = self.ring.pending_results.acquire();
{
let mut pw = self.ring.cached_polls.pop().unwrap_or_default();
pw.id = id.id;
pw.fd = fd.raw() as _;
pw.events = events as _;
pw.data = Some(Data {
pr: pr.clone(),
_fd: fd.clone(),
});
self.ring.schedule(pw);
}
Ok(pr.await.map(|v| v as c::c_short))
}
pub async fn readable(&self, fd: &Rc<OwnedFd>) -> Result<c::c_short, IoUringError> {
self.poll(fd, c::POLLIN).await.merge()
}
pub async fn writable(&self, fd: &Rc<OwnedFd>) -> Result<c::c_short, IoUringError> {
self.poll(fd, c::POLLOUT).await.merge()
}
}
struct Data {
pr: PendingResult,
_fd: Rc<OwnedFd>,
}
#[derive(Default)]
pub struct PollTask {
id: IoUringTaskId,
events: u16,
fd: i32,
data: Option<Data>,
}
unsafe impl Task for PollTask {
fn id(&self) -> IoUringTaskId {
self.id
}
fn complete(mut self: Box<Self>, ring: &IoUringData, res: i32) {
if let Some(data) = self.data.take() {
data.pr.complete(res);
}
ring.cached_polls.push(self);
}
fn encode(&self, sqe: &mut io_uring_sqe) {
sqe.opcode = IORING_OP_POLL_ADD;
sqe.fd = self.fd;
sqe.u3.poll_events = self.events;
}
}

View file

@ -0,0 +1,113 @@
use {
crate::{
IoUring, IoUringData, IoUringError, IoUringTaskId, Task,
sys::{IORING_OP_POLL_ADD, io_uring_sqe},
},
jay_utils::oserror::OsError,
std::{cell::Cell, rc::Rc},
uapi::{OwnedFd, c},
};
pub trait PollCallback {
fn completed(self: Rc<Self>, res: Result<c::c_short, OsError>);
}
pub struct PendingPoll {
data: Rc<IoUringData>,
shared: Rc<PollExternalTaskShared>,
id: IoUringTaskId,
}
impl Drop for PendingPoll {
fn drop(&mut self) {
if self.shared.id.get() != self.id {
return;
}
self.shared.callback.take();
self.data.cancel_task(self.id);
}
}
impl IoUring {
pub fn poll_external(
&self,
fd: &Rc<OwnedFd>,
events: c::c_short,
callback: Rc<dyn PollCallback>,
) -> Result<PendingPoll, IoUringError> {
self.ring.check_destroyed()?;
let mut pw = self.ring.cached_polls_external.pop().unwrap_or_default();
pw.shared.id.set(self.ring.id_raw());
pw.shared.callback.set(Some(callback));
pw.fd = fd.raw() as _;
pw.events = events as _;
pw.data = Some(Data { _fd: fd.clone() });
let pending = PendingPoll {
data: self.ring.clone(),
shared: pw.shared.clone(),
id: pw.shared.id.get(),
};
self.ring.schedule(pw);
Ok(pending)
}
pub fn readable_external(
&self,
fd: &Rc<OwnedFd>,
callback: Rc<dyn PollCallback>,
) -> Result<PendingPoll, IoUringError> {
self.poll_external(fd, c::POLLIN, callback)
}
pub fn writable_external(
&self,
fd: &Rc<OwnedFd>,
callback: Rc<dyn PollCallback>,
) -> Result<PendingPoll, IoUringError> {
self.poll_external(fd, c::POLLOUT, callback)
}
}
struct Data {
_fd: Rc<OwnedFd>,
}
#[derive(Default)]
struct PollExternalTaskShared {
id: Cell<IoUringTaskId>,
callback: Cell<Option<Rc<dyn PollCallback>>>,
}
#[derive(Default)]
pub struct PollExternalTask {
shared: Rc<PollExternalTaskShared>,
events: u16,
fd: i32,
data: Option<Data>,
}
unsafe impl Task for PollExternalTask {
fn id(&self) -> IoUringTaskId {
self.shared.id.get()
}
fn complete(mut self: Box<Self>, ring: &IoUringData, res: i32) {
self.data.take();
self.shared.id.set(Default::default());
if let Some(cb) = self.shared.callback.take() {
let res = if res < 0 {
Err(OsError::from(-res as c::c_int))
} else {
Ok(res as _)
};
cb.completed(res)
}
ring.cached_polls_external.push(self);
}
fn encode(&self, sqe: &mut io_uring_sqe) {
sqe.opcode = IORING_OP_POLL_ADD;
sqe.fd = self.fd;
sqe.u3.poll_events = self.events;
}
}

View file

@ -0,0 +1,100 @@
use {
crate::{
IoUring, IoUringData, IoUringError, IoUringTaskId, Task, TaskResultExt,
pending_result::PendingResult,
sys::{IORING_OP_READ, IORING_OP_WRITE, io_uring_sqe},
},
jay_time::Time,
jay_utils::buf::Buf,
std::rc::Rc,
uapi::{OwnedFd, c},
};
impl IoUring {
pub async fn read(&self, fd: &Rc<OwnedFd>, buf: Buf) -> Result<usize, IoUringError> {
self.perform(fd, buf, None, IORING_OP_READ).await
}
pub async fn write(
&self,
fd: &Rc<OwnedFd>,
buf: Buf,
timeout: Option<Time>,
) -> Result<usize, IoUringError> {
self.perform(fd, buf, timeout, IORING_OP_WRITE).await
}
async fn perform(
&self,
fd: &Rc<OwnedFd>,
buf: Buf,
timeout: Option<Time>,
opcode: u8,
) -> Result<usize, IoUringError> {
self.ring.check_destroyed()?;
let id = self.ring.id();
let pr = self.ring.pending_results.acquire();
{
let mut pw = self.ring.cached_read_writes.pop().unwrap_or_default();
pw.opcode = opcode;
pw.id = id.id;
pw.has_timeout = timeout.is_some();
pw.fd = fd.raw();
pw.buf = buf.as_ptr() as _;
pw.len = buf.len();
pw.data = Some(ReadWriteTaskData {
_fd: fd.clone(),
_buf: buf,
res: pr.clone(),
});
self.ring.schedule(pw);
if let Some(time) = timeout {
self.schedule_timeout_link(time);
}
}
Ok(pr.await.map(|v| v as usize)).merge()
}
}
struct ReadWriteTaskData {
_fd: Rc<OwnedFd>,
_buf: Buf,
res: PendingResult,
}
#[derive(Default)]
pub struct ReadWriteTask {
id: IoUringTaskId,
has_timeout: bool,
fd: c::c_int,
buf: usize,
len: usize,
data: Option<ReadWriteTaskData>,
opcode: u8,
}
unsafe impl Task for ReadWriteTask {
fn id(&self) -> IoUringTaskId {
self.id
}
fn complete(mut self: Box<Self>, ring: &IoUringData, res: i32) {
if let Some(data) = self.data.take() {
data.res.complete(res);
}
ring.cached_read_writes.push(self);
}
fn encode(&self, sqe: &mut io_uring_sqe) {
sqe.opcode = self.opcode;
sqe.fd = self.fd as _;
sqe.u1.off = !0;
sqe.u2.addr = self.buf as _;
sqe.u3.rw_flags = 0;
sqe.len = self.len as _;
}
fn has_timeout(&self) -> bool {
self.has_timeout
}
}

View file

@ -0,0 +1,135 @@
#[cfg(test)]
mod tests;
use {
crate::{
IoUring, IoUringData, IoUringError, IoUringTaskId, Task, TaskResultExt,
pending_result::PendingResult,
sys::{IORING_OP_READ, IORING_OP_WRITE, io_uring_sqe},
},
jay_time::Time,
run_on_drop::on_drop,
uapi::{Fd, c},
};
impl IoUring {
pub async fn read_no_cancel(
&self,
fd: Fd,
offset: usize,
buf: &mut [u8],
cancel: impl FnOnce(IoUringTaskId),
) -> Result<usize, IoUringError> {
self.perform_no_cancel(
fd,
offset,
buf.as_mut_ptr(),
buf.len(),
None,
IORING_OP_READ,
cancel,
)
.await
}
pub async fn write_no_cancel(
&self,
fd: Fd,
offset: usize,
buf: &[u8],
timeout: Option<Time>,
cancel: impl FnOnce(IoUringTaskId),
) -> Result<usize, IoUringError> {
self.perform_no_cancel(
fd,
offset,
buf.as_ptr() as _,
buf.len(),
timeout,
IORING_OP_WRITE,
cancel,
)
.await
}
async fn perform_no_cancel(
&self,
fd: Fd,
offset: usize,
buf: *mut u8,
len: usize,
timeout: Option<Time>,
opcode: u8,
cancel: impl FnOnce(IoUringTaskId),
) -> Result<usize, IoUringError> {
self.ring.check_destroyed()?;
let id = self.ring.id();
let pr = self.ring.pending_results.acquire();
{
let mut pw = self
.ring
.cached_read_writes_no_cancel
.pop()
.unwrap_or_default();
pw.opcode = opcode;
pw.id = id.id;
pw.has_timeout = timeout.is_some();
pw.fd = fd.raw();
pw.offset = offset;
pw.buf = buf as _;
pw.len = len;
pw.data = Some(ReadWriteTaskData { res: pr.clone() });
self.ring.schedule(pw);
if let Some(time) = timeout {
self.schedule_timeout_link(time);
}
}
let panic = on_drop(|| panic!("Operation cannot be cancelled from userspace"));
cancel(id.id);
let res = Ok(pr.await.map(|v| v as usize)).merge();
panic.forget();
res
}
}
struct ReadWriteTaskData {
res: PendingResult,
}
#[derive(Default)]
pub struct ReadWriteNoCancelTask {
id: IoUringTaskId,
has_timeout: bool,
fd: c::c_int,
offset: usize,
buf: usize,
len: usize,
data: Option<ReadWriteTaskData>,
opcode: u8,
}
unsafe impl Task for ReadWriteNoCancelTask {
fn id(&self) -> IoUringTaskId {
self.id
}
fn complete(mut self: Box<Self>, ring: &IoUringData, res: i32) {
if let Some(data) = self.data.take() {
data.res.complete(res);
}
ring.cached_read_writes_no_cancel.push(self);
}
fn encode(&self, sqe: &mut io_uring_sqe) {
sqe.opcode = self.opcode;
sqe.fd = self.fd as _;
sqe.u1.off = self.offset as _;
sqe.u2.addr = self.buf as _;
sqe.u3.rw_flags = 0;
sqe.len = self.len as _;
}
fn has_timeout(&self) -> bool {
self.has_timeout
}
}

View file

@ -0,0 +1,46 @@
use {
crate::{IoUring, IoUringError},
jay_async_engine::AsyncEngine,
jay_utils::{oserror::OsError, queue::AsyncQueue},
std::rc::Rc,
uapi::c::ECANCELED,
};
fn cancel(timeout: bool) {
let eng = AsyncEngine::new();
let ring = IoUring::new(&eng, 32).unwrap();
let ring2 = ring.clone();
let ring3 = ring.clone();
let queue = Rc::new(AsyncQueue::new());
let queue2 = queue.clone();
let _fut1 = eng.spawn("", async move {
let (read, _write) = uapi::pipe().unwrap();
let mut buf = [10];
let res = ring
.read_no_cancel(read.borrow(), !0, &mut buf, |id| queue.push(id))
.await;
assert!(matches!(
res.unwrap_err(),
IoUringError::OsError(OsError(ECANCELED))
));
ring.stop();
});
let _fut2 = eng.spawn("", async move {
let id = queue2.pop().await;
if timeout {
ring2.timeout(1).await.unwrap();
}
ring2.cancel(id);
});
ring3.run().unwrap();
}
#[test]
fn cancel_in_kernel() {
cancel(true);
}
#[test]
fn cancel_in_userspace() {
cancel(true);
}

View file

@ -0,0 +1,129 @@
use {
crate::{
IoUring, IoUringData, IoUringError, IoUringTaskId, Task,
pending_result::PendingResult,
sys::{IORING_OP_RECVMSG, io_uring_sqe},
},
jay_utils::buf::Buf,
std::{cell::Cell, collections::VecDeque, mem::MaybeUninit, rc::Rc},
uapi::{OwnedFd, c},
};
impl IoUring {
pub async fn recvmsg(
&self,
fd: &Rc<OwnedFd>,
bufs: &mut [Buf],
fds: &mut VecDeque<Rc<OwnedFd>>,
) -> Result<usize, IoUringError> {
self.ring.check_destroyed()?;
let id = self.ring.id();
let pr = self.ring.pending_results.acquire();
let mut cmsg = self.ring.cmsg_buf();
let cmsg_len;
{
let mut rm = self.ring.cached_recvmsg.pop().unwrap_or_default();
rm.iovecs.clear();
for buf in bufs {
rm.bufs.push(buf.clone());
rm.iovecs.push(c::iovec {
iov_base: buf.as_ptr() as _,
iov_len: buf.len() as _,
});
}
rm.id = id.id;
rm.fd = fd.raw();
rm.msghdr.msg_control = cmsg.as_ptr() as _;
rm.msghdr.msg_controllen = cmsg.len() as _;
rm.msghdr.msg_iov = rm.iovecs.as_mut_ptr();
rm.msghdr.msg_iovlen = rm.iovecs.len() as _;
rm.data = Some(Data {
_cmsg: cmsg.clone(),
_fd: fd.clone(),
pr: pr.clone(),
});
cmsg_len = rm.cmsg_len.clone();
self.ring.schedule(rm);
}
macro_rules! return_cmsg {
() => {
self.ring.cached_cmsg_bufs.push(cmsg);
};
}
match pr.await {
Ok(n) => {
let mut cmsg_data = &cmsg[..cmsg_len.get()];
while cmsg_data.len() > 0 {
let (_, hdr, data) = match uapi::cmsg_read(&mut cmsg_data) {
Ok(m) => m,
Err(_) => {
return_cmsg!();
return Err(IoUringError::InvalidCmsgData);
}
};
if (hdr.cmsg_level, hdr.cmsg_type) == (c::SOL_SOCKET, c::SCM_RIGHTS) {
fds.extend(uapi::pod_iter(data).unwrap().map(Rc::new));
}
}
return_cmsg!();
Ok(n as _)
}
Err(e) => {
return_cmsg!();
Err(IoUringError::OsError(e))
}
}
}
}
struct Data {
_cmsg: Buf,
_fd: Rc<OwnedFd>,
pr: PendingResult,
}
pub struct RecvmsgTask {
id: IoUringTaskId,
fd: c::c_int,
bufs: Vec<Buf>,
iovecs: Vec<c::iovec>,
msghdr: c::msghdr,
cmsg_len: Rc<Cell<usize>>,
data: Option<Data>,
}
impl Default for RecvmsgTask {
fn default() -> Self {
RecvmsgTask {
id: Default::default(),
fd: 0,
bufs: vec![],
iovecs: vec![],
msghdr: unsafe { MaybeUninit::zeroed().assume_init() },
cmsg_len: Rc::new(Cell::new(0)),
data: None,
}
}
}
unsafe impl Task for RecvmsgTask {
fn id(&self) -> IoUringTaskId {
self.id
}
fn complete(mut self: Box<Self>, ring: &IoUringData, res: i32) {
self.cmsg_len.set(self.msghdr.msg_controllen as _);
self.bufs.clear();
if let Some(data) = self.data.take() {
data.pr.complete(res);
}
ring.cached_recvmsg.push(self);
}
fn encode(&self, sqe: &mut io_uring_sqe) {
sqe.opcode = IORING_OP_RECVMSG;
sqe.fd = self.fd as _;
sqe.u2.addr = &self.msghdr as *const _ as _;
sqe.u3.msg_flags = c::MSG_CMSG_CLOEXEC as _;
}
}

View file

@ -0,0 +1,139 @@
use {
crate::{
IoUring, IoUringData, IoUringError, IoUringTaskId, Task,
pending_result::PendingResult,
sys::{IORING_OP_SENDMSG, io_uring_sqe},
},
jay_time::Time,
jay_utils::{buf::Buf, compat::IovLength, vec_ext::UninitVecExt},
std::{mem::MaybeUninit, ptr, rc::Rc},
uapi::{OwnedFd, c},
};
impl IoUring {
pub async fn sendmsg_one(
&self,
fd: &Rc<OwnedFd>,
buf: Buf,
fds: Vec<Rc<OwnedFd>>,
timeout: Option<Time>,
) -> Result<usize, IoUringError> {
self.sendmsg(fd, &mut [buf], fds, timeout).await
}
pub async fn sendmsg(
&self,
fd: &Rc<OwnedFd>,
bufs: &mut [Buf],
fds: Vec<Rc<OwnedFd>>,
timeout: Option<Time>,
) -> Result<usize, IoUringError> {
self.ring.check_destroyed()?;
let id = self.ring.id();
let pr = self.ring.pending_results.acquire();
{
let mut st = self.ring.cached_sendmsg.pop().unwrap_or_default();
st.fds = fds;
if st.fds.len() > 0 {
let mut fd_ids = self.ring.fd_ids_scratch.borrow_mut();
fd_ids.clear();
fd_ids.extend(st.fds.iter().map(|f| f.raw()));
let space = uapi::cmsg_space(size_of_val(&fd_ids[..]));
st.cmsg.clear();
st.cmsg.reserve(space);
st.cmsg.set_len_safe(space);
let mut hdr: c::cmsghdr = uapi::pod_zeroed();
hdr.cmsg_level = c::SOL_SOCKET;
hdr.cmsg_type = c::SCM_RIGHTS;
uapi::cmsg_write(&mut &mut st.cmsg[..], hdr, &fd_ids[..]).unwrap();
st.msghdr.msg_control = st.cmsg.as_ptr() as _;
st.msghdr.msg_controllen = st.cmsg.len() as _;
} else {
st.msghdr.msg_control = ptr::null_mut();
st.msghdr.msg_controllen = 0;
}
st.id = id.id;
st.fd = fd.raw();
st.bufs.clear();
st.bufs.extend(bufs.iter_mut().map(|b| b.clone()));
st.iovecs.clear();
st.iovecs.extend(bufs.iter().map(|b| c::iovec {
iov_base: b.as_ptr() as _,
iov_len: b.len(),
}));
st.msghdr.msg_iov = st.iovecs.as_ptr() as _;
st.msghdr.msg_iovlen = st.iovecs.len() as IovLength;
st.data = Some(SendmsgTaskData {
_fd: fd.clone(),
res: pr.clone(),
});
st.has_timeout = timeout.is_some();
self.ring.schedule(st);
if let Some(timeout) = timeout {
self.schedule_timeout_link(timeout);
}
}
Ok(pr.await? as _)
}
}
struct SendmsgTaskData {
_fd: Rc<OwnedFd>,
res: PendingResult,
}
pub struct SendmsgTask {
id: IoUringTaskId,
iovecs: Vec<c::iovec>,
msghdr: c::msghdr,
bufs: Vec<Buf>,
fd: i32,
has_timeout: bool,
fds: Vec<Rc<OwnedFd>>,
cmsg: Vec<MaybeUninit<u8>>,
data: Option<SendmsgTaskData>,
}
impl Default for SendmsgTask {
fn default() -> Self {
unsafe {
SendmsgTask {
id: Default::default(),
iovecs: vec![],
msghdr: MaybeUninit::zeroed().assume_init(),
bufs: vec![],
fd: 0,
has_timeout: false,
fds: vec![],
cmsg: vec![],
data: None,
}
}
}
}
unsafe impl Task for SendmsgTask {
fn id(&self) -> IoUringTaskId {
self.id
}
fn complete(mut self: Box<Self>, ring: &IoUringData, res: i32) {
self.fds.clear();
self.bufs.clear();
if let Some(data) = self.data.take() {
data.res.complete(res);
}
ring.cached_sendmsg.push(self);
}
fn encode(&self, sqe: &mut io_uring_sqe) {
sqe.opcode = IORING_OP_SENDMSG;
sqe.fd = self.fd;
sqe.u2.addr = &self.msghdr as *const _ as _;
sqe.u3.msg_flags = c::MSG_NOSIGNAL as _;
}
fn has_timeout(&self) -> bool {
self.has_timeout
}
}

View file

@ -0,0 +1,63 @@
use {
crate::{
IoUring, IoUringData, IoUringError, IoUringTaskId, Task,
pending_result::PendingResult,
sys::{IORING_OP_TIMEOUT, IORING_TIMEOUT_ABS, io_uring_sqe},
},
uapi::c,
};
#[repr(C)]
#[derive(Default)]
pub(super) struct timespec64 {
pub tv_sec: i64,
pub tv_nsec: c::c_long,
}
#[derive(Default)]
pub struct TimeoutTask {
id: IoUringTaskId,
timespec: timespec64,
pr: Option<PendingResult>,
}
impl IoUring {
pub async fn timeout(&self, timeout_nsec: u64) -> Result<(), IoUringError> {
self.ring.check_destroyed()?;
let id = self.ring.id();
let pr = self.ring.pending_results.acquire();
{
let mut pw = self.ring.cached_timeouts.pop().unwrap_or_default();
pw.id = id.id;
pw.timespec = timespec64 {
tv_sec: (timeout_nsec / 1_000_000_000) as _,
tv_nsec: (timeout_nsec % 1_000_000_000) as _,
};
pw.pr = Some(pr.clone());
self.ring.schedule(pw);
}
let _ = pr.await;
Ok(())
}
}
unsafe impl Task for TimeoutTask {
fn id(&self) -> IoUringTaskId {
self.id
}
fn complete(mut self: Box<Self>, ring: &IoUringData, res: i32) {
if let Some(pr) = self.pr.take() {
pr.complete(res);
}
ring.cached_timeouts.push(self);
}
fn encode(&self, sqe: &mut io_uring_sqe) {
sqe.opcode = IORING_OP_TIMEOUT;
sqe.u2.addr = &self.timespec as *const _ as _;
sqe.len = 1;
sqe.u3.timeout_flags = IORING_TIMEOUT_ABS;
sqe.u1.off = 0;
}
}

View file

@ -0,0 +1,94 @@
use {
crate::{
IoUring, IoUringData, IoUringError, IoUringTaskId, Task, ops::timeout::timespec64,
sys::{IORING_OP_TIMEOUT, IORING_TIMEOUT_ABS, io_uring_sqe},
},
jay_utils::oserror::OsError,
std::{cell::Cell, rc::Rc},
uapi::c,
};
pub trait TimeoutCallback {
fn completed(self: Rc<Self>, res: Result<(), OsError>, data: u64);
}
pub struct PendingTimeout {
data: Rc<IoUringData>,
shared: Rc<TimeoutExternalTaskShared>,
id: IoUringTaskId,
}
impl Drop for PendingTimeout {
fn drop(&mut self) {
if self.shared.id.get() != self.id {
return;
}
self.shared.callback.take();
self.data.cancel_task(self.id);
}
}
#[derive(Default)]
struct TimeoutExternalTaskShared {
id: Cell<IoUringTaskId>,
callback: Cell<Option<Rc<dyn TimeoutCallback>>>,
}
#[derive(Default)]
pub struct TimeoutExternalTask {
timespec: timespec64,
shared: Rc<TimeoutExternalTaskShared>,
data: u64,
}
impl IoUring {
pub fn timeout_external(
&self,
timeout_nsec: u64,
callback: Rc<dyn TimeoutCallback>,
data: u64,
) -> Result<PendingTimeout, IoUringError> {
self.ring.check_destroyed()?;
let mut pw = self.ring.cached_timeouts_external.pop().unwrap_or_default();
pw.shared.id.set(self.ring.id_raw());
pw.shared.callback.set(Some(callback));
pw.timespec = timespec64 {
tv_sec: (timeout_nsec / 1_000_000_000) as _,
tv_nsec: (timeout_nsec % 1_000_000_000) as _,
};
pw.data = data;
let pending = PendingTimeout {
data: self.ring.clone(),
shared: pw.shared.clone(),
id: pw.shared.id.get(),
};
self.ring.schedule(pw);
Ok(pending)
}
}
unsafe impl Task for TimeoutExternalTask {
fn id(&self) -> IoUringTaskId {
self.shared.id.get()
}
fn complete(self: Box<Self>, ring: &IoUringData, res: i32) {
if let Some(pr) = self.shared.callback.take() {
let res = if res == -c::ETIME {
Ok(())
} else {
map_err!(res).map(drop)
};
pr.completed(res, self.data);
}
ring.cached_timeouts_external.push(self);
}
fn encode(&self, sqe: &mut io_uring_sqe) {
sqe.opcode = IORING_OP_TIMEOUT;
sqe.u2.addr = &self.timespec as *const _ as _;
sqe.len = 1;
sqe.u3.timeout_flags = IORING_TIMEOUT_ABS;
sqe.u1.off = 0;
}
}

View file

@ -0,0 +1,41 @@
use crate::{
IoUring, IoUringData, IoUringTaskId, Task, ops::timeout::timespec64,
sys::{IORING_OP_LINK_TIMEOUT, IORING_TIMEOUT_ABS, io_uring_sqe},
};
use jay_time::Time;
#[derive(Default)]
pub struct TimeoutLinkTask {
id: IoUringTaskId,
timespec: timespec64,
}
impl IoUring {
pub(super) fn schedule_timeout_link(&self, timeout: Time) {
let id = self.ring.id_raw();
{
let mut to = self.ring.cached_timeout_links.pop().unwrap_or_default();
to.id = id;
to.timespec.tv_sec = timeout.0.tv_sec as _;
to.timespec.tv_nsec = timeout.0.tv_nsec as _;
self.ring.schedule(to);
}
}
}
unsafe impl Task for TimeoutLinkTask {
fn id(&self) -> IoUringTaskId {
self.id
}
fn complete(self: Box<Self>, ring: &IoUringData, _res: i32) {
ring.cached_timeout_links.push(self);
}
fn encode(&self, sqe: &mut io_uring_sqe) {
sqe.opcode = IORING_OP_LINK_TIMEOUT;
sqe.u2.addr = &self.timespec as *const _ as _;
sqe.len = 1;
sqe.u3.timeout_flags = IORING_TIMEOUT_ABS;
}
}

View file

@ -0,0 +1,120 @@
use {
jay_utils::{numcell::NumCell, oserror::OsError, ptr_ext::PtrExt, stack::Stack},
std::{
cell::Cell,
future::Future,
pin::Pin,
rc::{Rc, Weak},
task::{Context, Poll, Waker},
},
uapi::c,
};
#[derive(Default)]
pub struct PendingResults {
data: Rc<PendingResultsData>,
}
impl PendingResults {
pub fn acquire(&self) -> PendingResult {
let pr = self.data.unused.pop().unwrap_or_else(|| {
Box::into_raw(Box::new(PendingResultData {
rc: NumCell::new(0),
base: Rc::downgrade(&self.data),
waker: Cell::new(None),
res: Cell::new(None),
}))
});
unsafe {
let prr = pr.deref();
debug_assert_eq!(prr.rc.get(), 0);
prr.rc.fetch_add(1);
PendingResult { pr }
}
}
}
#[derive(Default)]
struct PendingResultsData {
unused: Stack<*mut PendingResultData>,
}
impl Drop for PendingResultsData {
fn drop(&mut self) {
while let Some(pr) = self.unused.pop() {
unsafe {
drop(Box::from_raw(pr));
}
}
}
}
struct PendingResultData {
rc: NumCell<u32>,
base: Weak<PendingResultsData>,
waker: Cell<Option<Waker>>,
res: Cell<Option<i32>>,
}
pub struct PendingResult {
pr: *mut PendingResultData,
}
impl PendingResult {
pub fn complete(&self, res: i32) {
unsafe {
let pr = self.pr.deref();
pr.res.set(Some(res));
if let Some(waker) = pr.waker.take() {
waker.wake();
}
}
}
}
impl Drop for PendingResult {
fn drop(&mut self) {
{
let pr = unsafe { self.pr.deref() };
if pr.rc.fetch_sub(1) != 1 {
return;
}
if let Some(base) = pr.base.upgrade() {
pr.waker.set(None);
pr.res.set(None);
base.unused.push(self.pr);
return;
}
}
unsafe {
drop(Box::from_raw(self.pr));
}
}
}
impl Clone for PendingResult {
fn clone(&self) -> Self {
let pr = unsafe { self.pr.deref() };
pr.rc.fetch_add(1);
Self { pr: self.pr }
}
}
impl Future for PendingResult {
type Output = Result<i32, OsError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let pr = unsafe { self.pr.deref() };
if let Some(res) = pr.res.take() {
let res = if res < 0 {
Err(OsError::from(-res as c::c_int))
} else {
Ok(res)
};
Poll::Ready(res)
} else {
pr.waker.set(Some(cx.waker().clone()));
Poll::Pending
}
}
}

415
crates/io-uring/src/sys.rs Normal file
View file

@ -0,0 +1,415 @@
#![allow(non_camel_case_types, dead_code)]
use {
jay_utils::oserror::OsError,
std::mem::MaybeUninit,
uapi::{OwnedFd, c},
};
#[repr(C)]
#[derive(Copy, Clone)]
pub struct io_uring_sqe {
pub opcode: u8,
pub flags: u8,
pub ioprio: u16,
pub fd: i32,
pub u1: io_uring_sqe_union1,
pub u2: io_uring_sqe_union2,
pub len: u32,
pub u3: io_uring_sqe_union3,
pub user_data: u64,
pub u4: io_uring_sqe_union4,
pub personality: u16,
pub u5: io_uring_sqe_union5,
pub __pad2: [u64; 2],
}
impl Default for io_uring_sqe {
fn default() -> Self {
unsafe { MaybeUninit::zeroed().assume_init() }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union io_uring_sqe_union1 {
pub off: u64,
pub addr2: u64,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union io_uring_sqe_union2 {
pub addr: u64,
pub splice_off_in: u64,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union io_uring_sqe_union3 {
pub rw_flags: c::c_int,
pub fsync_flags: u32,
pub poll_events: u16,
pub poll32_events: u32,
pub sync_range_flags: u32,
pub msg_flags: u32,
pub timeout_flags: u32,
pub accept_flags: u32,
pub cancel_flags: u32,
pub open_flags: u32,
pub statx_flags: u32,
pub fadvise_advice: u32,
pub splice_flags: u32,
pub rename_flags: u32,
pub unlink_flags: u32,
pub hardlink_flags: u32,
}
#[repr(C, packed)]
#[derive(Copy, Clone)]
pub union io_uring_sqe_union4 {
pub buf_index: u16,
pub buf_group: u16,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union io_uring_sqe_union5 {
pub splice_fd_in: i32,
pub file_index: u32,
}
pub const IOSQE_FIXED_FILE_BIT: u8 = 0;
pub const IOSQE_IO_DRAIN_BIT: u8 = 1;
pub const IOSQE_IO_LINK_BIT: u8 = 2;
pub const IOSQE_IO_HARDLINK_BIT: u8 = 3;
pub const IOSQE_ASYNC_BIT: u8 = 4;
pub const IOSQE_BUFFER_SELECT_BIT: u8 = 5;
pub const IOSQE_CQE_SKIP_SUCCESS_BIT: u8 = 6;
pub const IOSQE_FIXED_FILE: u8 = 1 << IOSQE_FIXED_FILE_BIT;
pub const IOSQE_IO_DRAIN: u8 = 1 << IOSQE_IO_DRAIN_BIT;
pub const IOSQE_IO_LINK: u8 = 1 << IOSQE_IO_LINK_BIT;
pub const IOSQE_IO_HARDLINK: u8 = 1 << IOSQE_IO_HARDLINK_BIT;
pub const IOSQE_ASYNC: u8 = 1 << IOSQE_ASYNC_BIT;
pub const IOSQE_BUFFER_SELECT: u8 = 1 << IOSQE_BUFFER_SELECT_BIT;
pub const IOSQE_CQE_SKIP_SUCCESS: u8 = 1 << IOSQE_CQE_SKIP_SUCCESS_BIT;
pub const IORING_SETUP_IOPOLL: u32 = 1 << 0;
pub const IORING_SETUP_SQPOLL: u32 = 1 << 1;
pub const IORING_SETUP_SQ_AFF: u32 = 1 << 2;
pub const IORING_SETUP_CQSIZE: u32 = 1 << 3;
pub const IORING_SETUP_CLAMP: u32 = 1 << 4;
pub const IORING_SETUP_ATTACH_WQ: u32 = 1 << 5;
pub const IORING_SETUP_R_DISABLED: u32 = 1 << 6;
pub const IORING_SETUP_SUBMIT_ALL: u32 = 1 << 7;
pub const IORING_SETUP_COOP_TASKRUN: u32 = 1 << 8;
pub const IORING_SETUP_TASKRUN_FLAG: u32 = 1 << 9;
pub const IORING_SETUP_SQE128: u32 = 1 << 10;
pub const IORING_SETUP_CQE32: u32 = 1 << 11;
pub const IORING_SETUP_SINGLE_ISSUER: u32 = 1 << 12;
pub const IORING_SETUP_DEFER_TASKRUN: u32 = 1 << 13;
pub const IORING_SETUP_NO_MMAP: u32 = 1 << 14;
pub const IORING_SETUP_REGISTERED_FD_ONLY: u32 = 1 << 15;
pub const IORING_SETUP_NO_SQARRAY: u32 = 1 << 16;
pub const IORING_SETUP_HYBRID_IOPOLL: u32 = 1 << 17;
pub const IORING_OP_NOP: u8 = 0;
pub const IORING_OP_READV: u8 = 1;
pub const IORING_OP_WRITEV: u8 = 2;
pub const IORING_OP_FSYNC: u8 = 3;
pub const IORING_OP_READ_FIXED: u8 = 4;
pub const IORING_OP_WRITE_FIXED: u8 = 5;
pub const IORING_OP_POLL_ADD: u8 = 6;
pub const IORING_OP_POLL_REMOVE: u8 = 7;
pub const IORING_OP_SYNC_FILE_RANGE: u8 = 8;
pub const IORING_OP_SENDMSG: u8 = 9;
pub const IORING_OP_RECVMSG: u8 = 10;
pub const IORING_OP_TIMEOUT: u8 = 11;
pub const IORING_OP_TIMEOUT_REMOVE: u8 = 12;
pub const IORING_OP_ACCEPT: u8 = 13;
pub const IORING_OP_ASYNC_CANCEL: u8 = 14;
pub const IORING_OP_LINK_TIMEOUT: u8 = 15;
pub const IORING_OP_CONNECT: u8 = 16;
pub const IORING_OP_FALLOCATE: u8 = 17;
pub const IORING_OP_OPENAT: u8 = 18;
pub const IORING_OP_CLOSE: u8 = 19;
pub const IORING_OP_FILES_UPDATE: u8 = 20;
pub const IORING_OP_STATX: u8 = 21;
pub const IORING_OP_READ: u8 = 22;
pub const IORING_OP_WRITE: u8 = 23;
pub const IORING_OP_FADVISE: u8 = 24;
pub const IORING_OP_MADVISE: u8 = 25;
pub const IORING_OP_SEND: u8 = 26;
pub const IORING_OP_RECV: u8 = 27;
pub const IORING_OP_OPENAT2: u8 = 28;
pub const IORING_OP_EPOLL_CTL: u8 = 29;
pub const IORING_OP_SPLICE: u8 = 30;
pub const IORING_OP_PROVIDE_BUFFERS: u8 = 31;
pub const IORING_OP_REMOVE_BUFFERS: u8 = 32;
pub const IORING_OP_TEE: u8 = 33;
pub const IORING_OP_SHUTDOWN: u8 = 34;
pub const IORING_OP_RENAMEAT: u8 = 35;
pub const IORING_OP_UNLINKAT: u8 = 36;
pub const IORING_OP_MKDIRAT: u8 = 37;
pub const IORING_OP_SYMLINKAT: u8 = 38;
pub const IORING_OP_LINKAT: u8 = 39;
pub const IORING_OP_LAST: u8 = 40;
pub const IORING_FSYNC_DATASYNC: u32 = 1 << 0;
pub const IORING_TIMEOUT_ABS: u32 = 1 << 0;
pub const IORING_TIMEOUT_UPDATE: u32 = 1 << 1;
pub const IORING_TIMEOUT_BOOTTIME: u32 = 1 << 2;
pub const IORING_TIMEOUT_REALTIME: u32 = 1 << 3;
pub const IORING_LINK_TIMEOUT_UPDATE: u32 = 1 << 4;
pub const IORING_TIMEOUT_ETIME_SUCCESS: u32 = 1 << 5;
pub const IORING_TIMEOUT_CLOCK_MASK: u32 = IORING_TIMEOUT_BOOTTIME | IORING_TIMEOUT_REALTIME;
pub const IORING_TIMEOUT_UPDATE_MASK: u32 = IORING_TIMEOUT_UPDATE | IORING_LINK_TIMEOUT_UPDATE;
pub const SPLICE_F_FD_IN_FIXED: u32 = 1 << 31;
pub const IORING_POLL_ADD_MULTI: u32 = 1 << 0;
pub const IORING_POLL_UPDATE_EVENTS: u32 = 1 << 1;
pub const IORING_POLL_UPDATE_USER_DATA: u32 = 1 << 2;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct io_uring_cqe {
pub user_data: u64,
pub res: i32,
pub flags: u32,
}
pub const IORING_CQE_F_BUFFER: u32 = 1 << 0;
pub const IORING_CQE_F_MORE: u32 = 1 << 1;
pub const IORING_CQE_BUFFER_SHIFT: u32 = 16;
pub const IORING_OFF_SQ_RING: u64 = 0;
pub const IORING_OFF_CQ_RING: u64 = 0x8000000;
pub const IORING_OFF_SQES: u64 = 0x10000000;
#[repr(C)]
#[derive(Debug, Copy, Clone, Default)]
pub struct io_sqring_offsets {
pub head: u32,
pub tail: u32,
pub ring_mask: u32,
pub ring_entries: u32,
pub flags: u32,
pub dropped: u32,
pub array: u32,
pub resv1: u32,
pub resv2: u64,
}
#[repr(C)]
#[derive(Debug, Copy, Clone, Default)]
pub struct io_cqring_offsets {
pub head: u32,
pub tail: u32,
pub ring_mask: u32,
pub ring_entries: u32,
pub overflow: u32,
pub cqes: u32,
pub flags: u32,
pub resv1: u32,
pub resv2: u64,
}
#[repr(C)]
#[derive(Debug, Copy, Clone, Default)]
pub struct io_uring_params {
pub sq_entries: u32,
pub cq_entries: u32,
pub flags: u32,
pub sq_thread_cpu: u32,
pub sq_thread_idle: u32,
pub features: u32,
pub wq_fd: u32,
pub resv: [u32; 3],
pub sq_off: io_sqring_offsets,
pub cq_off: io_cqring_offsets,
}
pub const IORING_SQ_NEED_WAKEUP: u32 = 1 << 0;
pub const IORING_SQ_CQ_OVERFLOW: u32 = 1 << 1;
pub const IORING_CQ_EVENTFD_DISABLED: u32 = 1 << 0;
pub const IORING_ENTER_GETEVENTS: c::c_uint = 1 << 0;
pub const IORING_ENTER_SQ_WAKEUP: c::c_uint = 1 << 1;
pub const IORING_ENTER_SQ_WAIT: c::c_uint = 1 << 2;
pub const IORING_ENTER_EXT_ARG: c::c_uint = 1 << 3;
pub const IORING_FEAT_SINGLE_MMAP: u32 = 1 << 0;
pub const IORING_FEAT_NODROP: u32 = 1 << 1;
pub const IORING_FEAT_SUBMIT_STABLE: u32 = 1 << 2;
pub const IORING_FEAT_RW_CUR_POS: u32 = 1 << 3;
pub const IORING_FEAT_CUR_PERSONALITY: u32 = 1 << 4;
pub const IORING_FEAT_FAST_POLL: u32 = 1 << 5;
pub const IORING_FEAT_POLL_32BITS: u32 = 1 << 6;
pub const IORING_FEAT_SQPOLL_NONFIXED: u32 = 1 << 7;
pub const IORING_FEAT_EXT_ARG: u32 = 1 << 8;
pub const IORING_FEAT_NATIVE_WORKERS: u32 = 1 << 9;
pub const IORING_FEAT_RSRC_TAGS: u32 = 1 << 10;
pub const IORING_FEAT_CQE_SKIP: u32 = 1 << 11;
pub const IORING_REGISTER_BUFFERS: c::c_uint = 0;
pub const IORING_UNREGISTER_BUFFERS: c::c_uint = 1;
pub const IORING_REGISTER_FILES: c::c_uint = 2;
pub const IORING_UNREGISTER_FILES: c::c_uint = 3;
pub const IORING_REGISTER_EVENTFD: c::c_uint = 4;
pub const IORING_UNREGISTER_EVENTFD: c::c_uint = 5;
pub const IORING_REGISTER_FILES_UPDATE: c::c_uint = 6;
pub const IORING_REGISTER_EVENTFD_ASYNC: c::c_uint = 7;
pub const IORING_REGISTER_PROBE: c::c_uint = 8;
pub const IORING_REGISTER_PERSONALITY: c::c_uint = 9;
pub const IORING_UNREGISTER_PERSONALITY: c::c_uint = 10;
pub const IORING_REGISTER_RESTRICTIONS: c::c_uint = 11;
pub const IORING_REGISTER_ENABLE_RINGS: c::c_uint = 12;
pub const IORING_REGISTER_FILES2: c::c_uint = 13;
pub const IORING_REGISTER_FILES_UPDATE2: c::c_uint = 14;
pub const IORING_REGISTER_BUFFERS2: c::c_uint = 15;
pub const IORING_REGISTER_BUFFERS_UPDATE: c::c_uint = 16;
pub const IORING_REGISTER_IOWQ_AFF: c::c_uint = 17;
pub const IORING_UNREGISTER_IOWQ_AFF: c::c_uint = 18;
pub const IORING_REGISTER_IOWQ_MAX_WORKERS: c::c_uint = 19;
pub const IO_WQ_BOUND: u32 = 0;
pub const IO_WQ_UNBOUND: u32 = 1;
#[repr(C, align(8))]
#[derive(Debug, Copy, Clone)]
pub struct AlignedU64(pub u64);
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct io_uring_files_update {
pub offset: u32,
pub resv: u32,
pub fds: AlignedU64,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct io_uring_rsrc_register {
pub nr: u32,
pub resv: u32,
pub resv2: u64,
pub data: AlignedU64,
pub tags: AlignedU64,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct io_uring_rsrc_update {
pub offset: u32,
pub resv: u32,
pub data: AlignedU64,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct io_uring_rsrc_update2 {
pub offset: u32,
pub resv: u32,
pub data: AlignedU64,
pub tags: AlignedU64,
pub nr: u32,
pub resv2: u32,
}
pub const IORING_REGISTER_FILES_SKIP: i32 = -2;
pub const IO_URING_OP_SUPPORTED: u32 = 1 << 0;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct io_uring_probe_op {
pub op: u8,
pub resv: u8,
pub flags: u16,
pub resv2: u32,
}
#[repr(C)]
#[derive(Debug)]
pub struct io_uring_probe {
pub last_op: u8,
pub ops_len: u8,
pub resv: u16,
pub resv2: [u32; 3],
pub ops: [io_uring_probe_op; 0],
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct io_uring_restriction {
pub opcode: u16,
pub u1: io_uring_restriction_union1,
pub resv: u8,
pub resv2: [u32; 3],
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union io_uring_restriction_union1 {
pub register_op: u8,
pub sqe_op: u8,
pub sqe_flags: u8,
}
pub const IORING_RESTRICTION_REGISTER_OP: u16 = 0;
pub const IORING_RESTRICTION_SQE_OP: u16 = 1;
pub const IORING_RESTRICTION_SQE_FLAGS_ALLOWED: u16 = 2;
pub const IORING_RESTRICTION_SQE_FLAGS_REQUIRED: u16 = 3;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct io_uring_getevents_arg {
sigmask: u64,
sigmask_sz: u32,
pad: u32,
ts: u64,
}
pub fn io_uring_setup(entries: u32, params: &mut io_uring_params) -> Result<OwnedFd, OsError> {
let res = unsafe {
c::syscall(
c::SYS_io_uring_setup,
entries as usize,
params as *mut _ as usize,
)
};
if res < 0 {
Err(OsError::default())
} else {
Ok(OwnedFd::new(res as _))
}
}
pub fn io_uring_enter(
fd: c::c_int,
to_submit: c::c_uint,
min_complete: c::c_uint,
flags: c::c_uint,
) -> Result<usize, OsError> {
let res = unsafe {
c::syscall(
c::SYS_io_uring_enter,
fd as usize,
to_submit as usize,
min_complete as usize,
flags as usize,
0usize,
0usize,
)
};
if res < 0 {
Err(OsError::default())
} else {
Ok(res as usize)
}
}

View file

@ -0,0 +1,67 @@
use {
crate::{IoUring, IoUringError},
jay_utils::{
buf::TypedBuf,
oserror::{OsError, OsErrorExt2},
},
std::{cell::RefCell, rc::Rc, time::Duration},
thiserror::Error,
uapi::{OwnedFd, c},
};
#[derive(Debug, Error)]
pub enum TimerError {
#[error("Could not create a timer")]
CreateTimer(#[source] OsError),
#[error("Could not read from a timer")]
TimerReadError(#[source] IoUringError),
#[error("Could not set a timer")]
SetTimer(#[source] OsError),
#[error("The io-uring returned an error")]
IoUringError(#[from] IoUringError),
}
#[derive(Clone)]
pub struct TimerFd {
fd: Rc<OwnedFd>,
buf: Rc<RefCell<TypedBuf<u64>>>,
}
impl TimerFd {
pub fn new(clock_id: c::c_int) -> Result<Self, TimerError> {
let fd = uapi::timerfd_create(clock_id, c::TFD_CLOEXEC)
.map(Rc::new)
.map_os_err(TimerError::CreateTimer)?;
Ok(Self {
fd,
buf: Rc::new(RefCell::new(TypedBuf::new())),
})
}
#[expect(clippy::await_holding_refcell_ref)]
pub async fn expired(&self, ring: &IoUring) -> Result<u64, TimerError> {
let mut buf = self.buf.borrow_mut();
if let Err(e) = ring.read(&self.fd, buf.buf()).await {
return Err(TimerError::TimerReadError(e));
}
Ok(buf.t())
}
pub fn program(
&self,
initial: Option<Duration>,
periodic: Option<Duration>,
) -> Result<(), TimerError> {
let mut timerspec: c::itimerspec = uapi::pod_zeroed();
if let Some(init) = initial {
timerspec.it_value.tv_sec = init.as_secs() as _;
timerspec.it_value.tv_nsec = init.subsec_nanos() as _;
if let Some(per) = periodic {
timerspec.it_interval.tv_sec = per.as_secs() as _;
timerspec.it_interval.tv_nsec = per.subsec_nanos() as _;
}
}
uapi::timerfd_settime(self.fd.raw(), 0, &timerspec).map_os_err(TimerError::SetTimer)?;
Ok(())
}
}

View file

@ -0,0 +1,11 @@
[package]
name = "jay-config-schema"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
description = "Shared configuration schema declarations for the Jay compositor"
repository = "https://github.com/mahkoh/jay"
[dependencies]
ahash = "0.8.11"
jay-config = { version = "1.10.0", path = "../jay-config" }

View file

@ -0,0 +1,59 @@
use jay_config::{
Direction,
input::{LayerDirection, Timeline},
};
#[derive(Debug, Copy, Clone)]
pub enum SimpleCommand {
Close,
DisablePointerConstraint,
Focus(Direction),
FocusParent,
Move(Direction),
None,
Quit,
ReloadConfigToml,
ToggleFloating,
SetFloating(bool),
ToggleFullscreen,
SetFullscreen(bool),
SendToScratchpad,
ToggleScratchpad,
CycleScratchpad,
Forward(bool),
EnableWindowManagement(bool),
SetFloatAboveFullscreen(bool),
ToggleFloatAboveFullscreen,
SetFloatPinned(bool),
ToggleFloatPinned,
KillClient,
ShowBar(bool),
ToggleBar,
ShowTitles(bool),
ToggleTitles,
FloatTitles(bool),
ToggleFloatTitles,
FocusHistory(Timeline),
FocusLayerRel(LayerDirection),
FocusTiles,
ToggleFocusFloatTiled,
CreateMark,
JumpToMark,
PopMode(bool),
EnableSimpleIm(bool),
ToggleSimpleImEnabled,
ReloadSimpleIm,
EnableUnicodeInput,
WarpMouseToFocus,
ToggleTab,
MakeGroupH,
MakeGroupV,
MakeGroupTab,
ChangeGroupOpposite,
Equalize,
EqualizeRecursive,
MoveTabLeft,
MoveTabRight,
SetAutotile(bool),
ToggleAutotile,
}

Some files were not shown because too many files have changed in this diff Show more