workspace: move crates under crates
This commit is contained in:
parent
0016bc8cf0
commit
6393fdf3c0
354 changed files with 102 additions and 102 deletions
10
crates/algorithms/Cargo.toml
Normal file
10
crates/algorithms/Cargo.toml
Normal 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"] }
|
||||
10
crates/algorithms/src/lib.rs
Normal file
10
crates/algorithms/src/lib.rs
Normal 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;
|
||||
91
crates/algorithms/src/qoi.rs
Normal file
91
crates/algorithms/src/qoi.rs
Normal 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) }
|
||||
}
|
||||
76
crates/algorithms/src/rect.rs
Normal file
76
crates/algorithms/src/rect.rs
Normal 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]>;
|
||||
687
crates/algorithms/src/rect/region.rs
Normal file
687
crates/algorithms/src/rect/region.rs
Normal 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
|
||||
}
|
||||
38
crates/algorithms/src/windows.rs
Normal file
38
crates/algorithms/src/windows.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
12
crates/allocator/Cargo.toml
Normal file
12
crates/allocator/Cargo.toml
Normal 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"
|
||||
95
crates/allocator/src/lib.rs
Normal file
95
crates/allocator/src/lib.rs
Normal 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;
|
||||
}
|
||||
14
crates/async-engine/Cargo.toml
Normal file
14
crates/async-engine/Cargo.toml
Normal 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"]
|
||||
288
crates/async-engine/src/ae_task.rs
Normal file
288
crates/async-engine/src/ae_task.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
crates/async-engine/src/ae_yield.rs
Normal file
27
crates/async-engine/src/ae_yield.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
169
crates/async-engine/src/lib.rs
Normal file
169
crates/async-engine/src/lib.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
crates/async-engine/src/run_toplevel.rs
Normal file
44
crates/async-engine/src/run_toplevel.rs
Normal 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
12
crates/bufio/Cargo.toml
Normal 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
183
crates/bufio/src/lib.rs
Normal 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
8
crates/bugs/Cargo.toml
Normal 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
38
crates/bugs/src/lib.rs
Normal 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>,
|
||||
}
|
||||
18
crates/clientmem/Cargo.toml
Normal file
18
crates/clientmem/Cargo.toml
Normal 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
331
crates/clientmem/src/lib.rs
Normal 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
8
crates/cmm/Cargo.toml
Normal 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" }
|
||||
87
crates/cmm/src/cmm_description.rs
Normal file
87
crates/cmm/src/cmm_description.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
60
crates/cmm/src/cmm_eotf.rs
Normal file
60
crates/cmm/src/cmm_eotf.rs
Normal 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]
|
||||
}
|
||||
94
crates/cmm/src/cmm_luminance.rs
Normal file
94
crates/cmm/src/cmm_luminance.rs
Normal 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],
|
||||
])
|
||||
}
|
||||
251
crates/cmm/src/cmm_manager.rs
Normal file
251
crates/cmm/src/cmm_manager.rs
Normal 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
|
||||
}
|
||||
111
crates/cmm/src/cmm_primaries.rs
Normal file
111
crates/cmm/src/cmm_primaries.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
28
crates/cmm/src/cmm_render_intent.rs
Normal file
28
crates/cmm/src/cmm_render_intent.rs
Normal 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
214
crates/cmm/src/cmm_tests.rs
Normal 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],
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
235
crates/cmm/src/cmm_transform.rs
Normal file
235
crates/cmm/src/cmm_transform.rs
Normal 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
53
crates/cmm/src/lib.rs
Normal 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;
|
||||
24
crates/cpu-worker/Cargo.toml
Normal file
24
crates/cpu-worker/Cargo.toml
Normal 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"]
|
||||
2
crates/cpu-worker/src/jobs.rs
Normal file
2
crates/cpu-worker/src/jobs.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub mod img_copy;
|
||||
pub mod read_write;
|
||||
60
crates/cpu-worker/src/jobs/img_copy.rs
Normal file
60
crates/cpu-worker/src/jobs/img_copy.rs
Normal 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
|
||||
}
|
||||
}
|
||||
145
crates/cpu-worker/src/jobs/read_write.rs
Normal file
145
crates/cpu-worker/src/jobs/read_write.rs
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
509
crates/cpu-worker/src/lib.rs
Normal file
509
crates/cpu-worker/src/lib.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
111
crates/cpu-worker/src/tests.rs
Normal file
111
crates/cpu-worker/src/tests.rs
Normal 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);
|
||||
}
|
||||
12
crates/criteria/Cargo.toml
Normal file
12
crates/criteria/Cargo.toml
Normal 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"
|
||||
18
crates/criteria/src/crit_graph.rs
Normal file
18
crates/criteria/src/crit_graph.rs
Normal 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,
|
||||
},
|
||||
};
|
||||
52
crates/criteria/src/crit_graph/crit_downstream.rs
Normal file
52
crates/criteria/src/crit_graph/crit_downstream.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
111
crates/criteria/src/crit_graph/crit_middle.rs
Normal file
111
crates/criteria/src/crit_graph/crit_middle.rs
Normal 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
|
||||
}
|
||||
}
|
||||
175
crates/criteria/src/crit_graph/crit_root.rs
Normal file
175
crates/criteria/src/crit_graph/crit_root.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
crates/criteria/src/crit_graph/crit_target.rs
Normal file
46
crates/criteria/src/crit_graph/crit_target.rs
Normal 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>;
|
||||
}
|
||||
177
crates/criteria/src/crit_graph/crit_upstream.rs
Normal file
177
crates/criteria/src/crit_graph/crit_upstream.rs
Normal 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
|
||||
}
|
||||
}
|
||||
154
crates/criteria/src/crit_leaf.rs
Normal file
154
crates/criteria/src/crit_leaf.rs
Normal 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
|
||||
}
|
||||
}
|
||||
4
crates/criteria/src/crit_matchers.rs
Normal file
4
crates/criteria/src/crit_matchers.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pub mod critm_any_or_all;
|
||||
pub mod critm_constant;
|
||||
pub mod critm_exactly;
|
||||
pub mod critm_string;
|
||||
73
crates/criteria/src/crit_matchers/critm_any_or_all.rs
Normal file
73
crates/criteria/src/crit_matchers/critm_any_or_all.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
58
crates/criteria/src/crit_matchers/critm_constant.rs
Normal file
58
crates/criteria/src/crit_matchers/critm_constant.rs
Normal 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
|
||||
}
|
||||
}
|
||||
61
crates/criteria/src/crit_matchers/critm_exactly.rs
Normal file
61
crates/criteria/src/crit_matchers/critm_exactly.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
45
crates/criteria/src/crit_matchers/critm_string.rs
Normal file
45
crates/criteria/src/crit_matchers/critm_string.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
139
crates/criteria/src/crit_per_target_data.rs
Normal file
139
crates/criteria/src/crit_per_target_data.rs
Normal 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
131
crates/criteria/src/lib.rs
Normal 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
10
crates/damage/Cargo.toml
Normal 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
116
crates/damage/src/lib.rs
Normal 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 _,
|
||||
}
|
||||
}
|
||||
}
|
||||
14
crates/dbus-core/Cargo.toml
Normal file
14
crates/dbus-core/Cargo.toml
Normal 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"
|
||||
181
crates/dbus-core/src/dynamic_type.rs
Normal file
181
crates/dbus-core/src/dynamic_type.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
112
crates/dbus-core/src/formatter.rs
Normal file
112
crates/dbus-core/src/formatter.rs
Normal 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
232
crates/dbus-core/src/lib.rs
Normal 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,
|
||||
};
|
||||
}
|
||||
143
crates/dbus-core/src/parser.rs
Normal file
143
crates/dbus-core/src/parser.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
67
crates/dbus-core/src/property.rs
Normal file
67
crates/dbus-core/src/property.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
540
crates/dbus-core/src/types.rs
Normal file
540
crates/dbus-core/src/types.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
13
crates/drm-feedback/Cargo.toml
Normal file
13
crates/drm-feedback/Cargo.toml
Normal 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"
|
||||
149
crates/drm-feedback/src/lib.rs
Normal file
149
crates/drm-feedback/src/lib.rs
Normal 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
11
crates/edid/Cargo.toml
Normal 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
1312
crates/edid/src/lib.rs
Normal file
File diff suppressed because it is too large
Load diff
14
crates/eventfd-cache/Cargo.toml
Normal file
14
crates/eventfd-cache/Cargo.toml
Normal 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"
|
||||
157
crates/eventfd-cache/src/lib.rs
Normal file
157
crates/eventfd-cache/src/lib.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
67
crates/eventfd-cache/src/tests.rs
Normal file
67
crates/eventfd-cache/src/tests.rs
Normal 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
13
crates/formats/Cargo.toml
Normal 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
559
crates/formats/src/lib.rs
Normal 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,
|
||||
];
|
||||
11
crates/geometry/Cargo.toml
Normal file
11
crates/geometry/Cargo.toml
Normal 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
365
crates/geometry/src/lib.rs
Normal 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
|
||||
}
|
||||
}
|
||||
339
crates/geometry/src/region.rs
Normal file
339
crates/geometry/src/region.rs
Normal 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(®ion),
|
||||
BuilderOp::Sub => self.base.subtract(®ion),
|
||||
};
|
||||
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)
|
||||
}
|
||||
}
|
||||
715
crates/geometry/src/tests.rs
Normal file
715
crates/geometry/src/tests.rs
Normal 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),
|
||||
);
|
||||
}
|
||||
8
crates/gfx-types/Cargo.toml
Normal file
8
crates/gfx-types/Cargo.toml
Normal 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"
|
||||
38
crates/gfx-types/src/lib.rs
Normal file
38
crates/gfx-types/src/lib.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
14
crates/input-types/Cargo.toml
Normal file
14
crates/input-types/Cargo.toml
Normal 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"] }
|
||||
482
crates/input-types/src/lib.rs
Normal file
482
crates/input-types/src/lib.rs
Normal 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,
|
||||
},
|
||||
}
|
||||
15
crates/io-uring/Cargo.toml
Normal file
15
crates/io-uring/Cargo.toml
Normal 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"
|
||||
33
crates/io-uring/src/debounce.rs
Normal file
33
crates/io-uring/src/debounce.rs
Normal 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
590
crates/io-uring/src/lib.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
33
crates/io-uring/src/line_logger.rs
Normal file
33
crates/io-uring/src/line_logger.rs
Normal 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(())
|
||||
}
|
||||
83
crates/io-uring/src/object_drop_queue.rs
Normal file
83
crates/io-uring/src/object_drop_queue.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
30
crates/io-uring/src/ops.rs
Normal file
30
crates/io-uring/src/ops.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
67
crates/io-uring/src/ops/accept.rs
Normal file
67
crates/io-uring/src/ops/accept.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
48
crates/io-uring/src/ops/async_cancel.rs
Normal file
48
crates/io-uring/src/ops/async_cancel.rs
Normal 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
|
||||
}
|
||||
}
|
||||
77
crates/io-uring/src/ops/connect.rs
Normal file
77
crates/io-uring/src/ops/connect.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
70
crates/io-uring/src/ops/poll.rs
Normal file
70
crates/io-uring/src/ops/poll.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
113
crates/io-uring/src/ops/poll_external.rs
Normal file
113
crates/io-uring/src/ops/poll_external.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
100
crates/io-uring/src/ops/read_write.rs
Normal file
100
crates/io-uring/src/ops/read_write.rs
Normal 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
|
||||
}
|
||||
}
|
||||
135
crates/io-uring/src/ops/read_write_no_cancel.rs
Normal file
135
crates/io-uring/src/ops/read_write_no_cancel.rs
Normal 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
|
||||
}
|
||||
}
|
||||
46
crates/io-uring/src/ops/read_write_no_cancel/tests.rs
Normal file
46
crates/io-uring/src/ops/read_write_no_cancel/tests.rs
Normal 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);
|
||||
}
|
||||
129
crates/io-uring/src/ops/recvmsg.rs
Normal file
129
crates/io-uring/src/ops/recvmsg.rs
Normal 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 _;
|
||||
}
|
||||
}
|
||||
139
crates/io-uring/src/ops/sendmsg.rs
Normal file
139
crates/io-uring/src/ops/sendmsg.rs
Normal 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
|
||||
}
|
||||
}
|
||||
63
crates/io-uring/src/ops/timeout.rs
Normal file
63
crates/io-uring/src/ops/timeout.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
94
crates/io-uring/src/ops/timeout_external.rs
Normal file
94
crates/io-uring/src/ops/timeout_external.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
41
crates/io-uring/src/ops/timeout_link.rs
Normal file
41
crates/io-uring/src/ops/timeout_link.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
120
crates/io-uring/src/pending_result.rs
Normal file
120
crates/io-uring/src/pending_result.rs
Normal 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
415
crates/io-uring/src/sys.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
67
crates/io-uring/src/timer.rs
Normal file
67
crates/io-uring/src/timer.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
11
crates/jay-config-schema/Cargo.toml
Normal file
11
crates/jay-config-schema/Cargo.toml
Normal 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" }
|
||||
56
crates/jay-config-schema/src/action.rs
Normal file
56
crates/jay-config-schema/src/action.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
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),
|
||||
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
Loading…
Add table
Add a link
Reference in a new issue