Added proc macro to create side effect functions.

This commit is contained in:
Filipe Rodrigues 2022-02-04 20:18:14 +00:00
parent 1017a766f1
commit 36ef4213a3
8 changed files with 117 additions and 23 deletions

34
Cargo.lock generated
View File

@ -1384,6 +1384,30 @@ dependencies = [
"toml",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.36"
@ -2164,4 +2188,14 @@ dependencies = [
"wgpu",
"winit",
"x11",
"zsw-side-effect-macros",
]
[[package]]
name = "zsw-side-effect-macros"
version = "0.1.0"
dependencies = [
"proc-macro-error",
"quote",
"syn",
]

View File

@ -1,6 +1,6 @@
[workspace]
members = ["zsw"]
members = ["zsw", "zsw-side-effect-macros"]
resolver = "2"
# Compile `image` in release mode, else it's too slow to meaningfully

View File

@ -0,0 +1,14 @@
[package]
edition = "2021"
name = "zsw-side-effect-macros"
version = "0.1.0"
[lib]
proc-macro = true
[dependencies]
# Macro
proc-macro-error = "1.0.4"
quote = "1.0.15"
syn = {version = "1.0.86", features = ["full", "extra-traits"]}

View File

@ -0,0 +1,45 @@
//! Side effect macros
// Imports
use proc_macro::TokenStream;
/// Adds a side effect to a function
#[proc_macro_error::proc_macro_error]
#[proc_macro_attribute]
pub fn side_effect(attr: TokenStream, item: TokenStream) -> TokenStream {
// Get all effects as a type
let effects = syn::parse_macro_input!(attr as syn::Type);
// Then parse the function
let func = syn::parse_macro_input!(item as syn::ItemFn);
// Create the outer function by wrapping the output type
let outer_func = {
let mut outer_func = func.clone();
// Wrap the return type
let (r_arrow, return_ty) = match func.sig.output {
syn::ReturnType::Default =>
proc_macro_error::abort!(func.sig.output, "Cannot use an empty output (yet), add `-> ()`"),
syn::ReturnType::Type(r_arrow, ty) => (r_arrow, ty),
};
outer_func.sig.output = syn::ReturnType::Type(
r_arrow,
syn::parse_quote!(crate::util::WithSideEffect<#return_ty, (#effects)>),
);
// Wrap the body
// TODO: Deal with async, unsafe and what not.
let inner_fn_body = func.block;
outer_func.block = syn::parse_quote! {{
let mut __inner_fn = move || { #inner_fn_body };
crate::util::WithSideEffect::new(__inner_fn())
}};
outer_func
};
quote::quote! {
#outer_func
}
.into()
}

View File

@ -6,6 +6,9 @@ version = "0.1.0"
[dependencies]
# Zsw
zsw-side-effect-macros = {path = "../zsw-side-effect-macros"}
# Windowing
winit = "0.26.1"

View File

@ -4,19 +4,9 @@
// `egui` returns a response on every operation, but we don't use them
#![allow(unused_results)]
// Imports
use {
crate::{
paths,
util::{MightDeadlock, WithSideEffect},
Egui,
Panel,
PanelState,
Panels,
Rect,
Wgpu,
},
crate::{paths, util::MightDeadlock, Egui, Panel, PanelState, Panels, Rect, Wgpu},
cgmath::{Point2, Vector2},
crossbeam::atomic::AtomicCell,
egui::Widget,
@ -25,6 +15,7 @@ use {
dpi::{PhysicalPosition, PhysicalSize},
window::Window,
},
zsw_side_effect_macros::side_effect,
};
/// Settings window
@ -50,6 +41,7 @@ impl SettingsWindow {
/// # Deadlock
/// Cannot be called from within `Wgpu::Render`
// TODO: Not use a channel, but instead something else
#[side_effect(MightDeadlock)]
pub fn run(
mut self,
wgpu: &Wgpu,
@ -59,7 +51,7 @@ impl SettingsWindow {
paths_distributer: &paths::Distributer,
queued_settings_window_open_click: &AtomicCell<Option<PhysicalPosition<f64>>>,
paint_jobs_tx: &crossbeam::channel::Sender<Vec<egui::epaint::ClippedMesh>>,
) -> WithSideEffect<(), MightDeadlock> {
) -> () {
loop {
// Get the surface size
// TODO: This can deadlock if put inside the `egui.draw` closure.
@ -93,8 +85,6 @@ impl SettingsWindow {
break;
}
}
WithSideEffect::new(())
}
/// Draws the settings window

View File

@ -11,9 +11,10 @@ use {
super::Image,
crate::{
paths,
util::{self, extse::CrossBeamChannelReceiverSE, MightDeadlock, WithSideEffect},
util::{self, extse::CrossBeamChannelReceiverSE, MightDeadlock},
},
anyhow::Context,
zsw_side_effect_macros::side_effect,
};
/// Image loader
@ -34,7 +35,8 @@ impl ImageLoader {
/// # Deadlock
/// Deadlocks if the path distributer deadlocks in [`paths::Distributer::run`],
/// or if all receivers' deadlock in [`ImageReceiver::recv`].
pub fn run(self) -> WithSideEffect<Result<(), anyhow::Error>, MightDeadlock> {
#[side_effect(MightDeadlock)]
pub fn run(self) -> Result<(), anyhow::Error> {
// DEADLOCK: Caller ensures the paths distributer doesn't deadlock
while let Ok(path) = self.paths_rx.recv().allow::<MightDeadlock>() {
match util::measure(|| load::load_image(&path)) {
@ -57,7 +59,7 @@ impl ImageLoader {
};
}
WithSideEffect::new(Ok(()))
Ok(())
}
}
@ -73,10 +75,13 @@ impl ImageReceiver {
///
/// # Deadlock
/// Deadlocks if the image loader deadlocks in [`ImageLoader::run`]
pub fn recv(&self) -> WithSideEffect<Result<Image, anyhow::Error>, MightDeadlock> {
#[side_effect(MightDeadlock)]
pub fn recv(&self) -> Result<Image, anyhow::Error> {
// DEADLOCK: Caller ensures we don't deadlock
self.image_rx
.recv_se()
.map(|res| res.context("Unable to get image from loader thread"))
.allow::<MightDeadlock>()
.context("Unable to get image from loader thread")
}
/// Attempts to receive the image

View File

@ -3,9 +3,10 @@
// Imports
use {
super::Inner,
crate::util::{extse::CrossBeamChannelReceiverSE, MightDeadlock, WithSideEffect},
crate::util::{extse::CrossBeamChannelReceiverSE, MightDeadlock},
parking_lot::Mutex,
std::{path::PathBuf, sync::Arc},
zsw_side_effect_macros::side_effect,
};
/// A receiver
@ -23,8 +24,10 @@ impl Receiver {
///
/// # Deadlock
/// Deadlocks if the path distributer deadlocks in [`Distributer::run`](super::Distributer::run)
pub fn recv(&self) -> WithSideEffect<Result<Arc<PathBuf>, DistributerQuitError>, MightDeadlock> {
self.rx.recv_se().map(|res| res.map_err(|_| DistributerQuitError))
#[side_effect(MightDeadlock)]
pub fn recv(&self) ->Result<Arc<PathBuf>, DistributerQuitError> {
// DEADLOCK: Caller ensures we don't deadlock
self.rx.recv_se().allow::<MightDeadlock>().map_err(|_| DistributerQuitError)
}
/// Removes a path