use alloc::sync::Arc;
use alloc::vec::Vec;
use super::Nodus;
use super::backend;
#[derive(Clone, Debug)]
pub struct GpuMapChain {
pub source_len: usize,
pub operations: alloc::vec::Vec<alloc::string::String>,
}
impl GpuMapChain {
#[inline]
pub fn new(source_len: usize) -> Self {
Self {
source_len,
operations: alloc::vec::Vec::with_capacity(4),
}
}
#[inline]
pub fn push_op(&mut self, wgsl_expr: alloc::string::String) {
self.operations.push(wgsl_expr);
}
#[inline]
pub fn compose_wgsl(&self) -> alloc::string::String {
if self.operations.is_empty() {
return alloc::string::String::from("x");
}
let mut expr = alloc::string::String::from("x");
for op in &self.operations {
expr = replace_ident_x(op, &alloc::format!("({expr})"));
}
expr
}
}
#[inline]
fn is_ident_byte(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_'
}
fn replace_ident_x(op: &str, replacement: &str) -> alloc::string::String {
let bytes = op.as_bytes();
let mut out = alloc::string::String::with_capacity(op.len() + replacement.len());
let mut copied_to = 0;
for (i, &b) in bytes.iter().enumerate() {
if b == b'x'
&& (i == 0 || !is_ident_byte(bytes[i - 1]))
&& (i + 1 == bytes.len() || !is_ident_byte(bytes[i + 1]))
{
out.push_str(&op[copied_to..i]);
out.push_str(replacement);
copied_to = i + 1;
}
}
out.push_str(&op[copied_to..]);
out
}
pub(crate) struct NodusGpuMap<T> {
pub(crate) prev: Arc<dyn Nodus<Item = T>>,
pub(crate) wgsl_expr: alloc::string::String,
pub(crate) fallback: Arc<dyn Fn(T) -> T + Send + Sync>,
}
impl<T: backend::wgpu::GpuScalar + Clone> Nodus for NodusGpuMap<T> {
type Item = T;
#[inline(always)]
fn len(&self) -> usize {
self.prev.len()
}
#[inline(always)]
fn visit_scalar(&self, sink: &mut dyn FnMut(Self::Item)) {
let f = &self.fallback;
self.prev.visit_scalar_ref(&mut |a| sink(f(*a)));
}
#[inline(always)]
fn visit_scalar_ref(&self, sink: &mut dyn FnMut(&Self::Item)) {
let f = &self.fallback;
self.prev.visit_scalar_ref(&mut |a| {
let b = f(*a);
sink(&b);
});
}
#[inline(always)]
fn is_indexed(&self) -> bool {
self.prev.is_indexed()
}
#[inline(always)]
fn get(&self, index: usize) -> Self::Item {
let a = self.prev.get(index);
(self.fallback)(a)
}
#[inline]
fn try_gpu_map_chain(&self) -> Option<GpuMapChain> {
let mut chain = self.prev.try_gpu_map_chain()?;
chain.push_op(self.wgsl_expr.clone());
Some(chain)
}
#[inline]
fn try_as_gpu_source(&self) -> Option<(&[u8], &'static str)> {
self.prev.try_as_gpu_source()
}
}
pub(crate) struct NodusInitGpu<T> {
pub(crate) data: Vec<T>,
}
impl<T: backend::wgpu::GpuScalar + Clone> Nodus for NodusInitGpu<T> {
type Item = T;
#[inline(always)]
fn len(&self) -> usize {
self.data.len()
}
#[inline(always)]
fn visit_scalar(&self, sink: &mut dyn FnMut(Self::Item)) {
for item in &self.data {
sink(*item);
}
}
#[inline(always)]
fn visit_scalar_ref(&self, sink: &mut dyn FnMut(&Self::Item)) {
for item in &self.data {
sink(item);
}
}
#[inline(always)]
fn is_indexed(&self) -> bool {
true
}
#[inline(always)]
fn get(&self, index: usize) -> Self::Item {
self.data[index]
}
#[inline]
fn try_gpu_map_chain(&self) -> Option<GpuMapChain> {
Some(GpuMapChain::new(self.data.len()))
}
#[inline]
fn try_as_gpu_source(&self) -> Option<(&[u8], &'static str)> {
Some((bytemuck::cast_slice(&self.data), T::wgsl_type()))
}
#[cfg(feature = "rayon")]
#[inline]
fn collect_rayon(&self) -> Vec<Self::Item> {
use rayon::prelude::*;
self.data.par_iter().cloned().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::String;
fn chain_of(ops: &[&str]) -> GpuMapChain {
let mut chain = GpuMapChain::new(0);
for op in ops {
chain.push_op(String::from(*op));
}
chain
}
#[test]
fn test_compose_wgsl_exp_not_mangled() {
let chain = chain_of(&["x * 2.0", "exp(x)"]);
let composed = chain.compose_wgsl();
assert_eq!(composed, "exp(((x) * 2.0))");
assert!(
composed.contains("exp("),
"function name mangled: {composed}"
);
}
#[test]
fn test_compose_wgsl_max_survives() {
let chain = chain_of(&["max(x, 1.0)"]);
assert_eq!(chain.compose_wgsl(), "max((x), 1.0)");
}
#[test]
fn test_compose_wgsl_multi_op_nesting() {
let chain = chain_of(&["sin(x)", "x * 2.0"]);
assert_eq!(chain.compose_wgsl(), "(sin((x))) * 2.0");
}
}