diff --git a/inox2d/Cargo.toml b/inox2d/Cargo.toml index b4f122ba..ec521540 100644 --- a/inox2d/Cargo.toml +++ b/inox2d/Cargo.toml @@ -21,6 +21,8 @@ owo-colors = { version = "4.0.0", optional = true } simple-tga-reader = "0.1.0" thiserror = "1.0.39" tracing = "0.1.37" +parking_lot = "0.12.5" +rayon = "1.12.0" [dev-dependencies] clap = { version = "4.1.8", features = ["derive"] } diff --git a/inox2d/src/formats/payload.rs b/inox2d/src/formats/payload.rs index cb25138a..dd0cb537 100644 --- a/inox2d/src/formats/payload.rs +++ b/inox2d/src/formats/payload.rs @@ -176,8 +176,8 @@ fn deserialize_simple_physics(obj: JsonObject) -> InoxParseResult } fn deserialize_drawable(obj: JsonObject) -> InoxParseResult { - Ok(Drawable { - blending: Blending { + Ok(Drawable::new( + Blending { mode: match obj.get_str("blend_mode")? { "Normal" => BlendMode::Normal, "Multiply" => BlendMode::Multiply, @@ -192,7 +192,7 @@ fn deserialize_drawable(obj: JsonObject) -> InoxParseResult { screen_tint: obj.get_vec3("screenTint").unwrap_or(vec3(0.0, 0.0, 0.0)), opacity: obj.get_f32("opacity").unwrap_or(1.0), }, - masks: { + { if let Ok(masks) = obj.get_list("masks") { Some(Masks { threshold: obj.get_f32("mask_threshold").unwrap_or(0.5), @@ -209,7 +209,7 @@ fn deserialize_drawable(obj: JsonObject) -> InoxParseResult { None } }, - }) + )) } fn deserialize_mesh(obj: JsonObject) -> InoxParseResult { @@ -446,6 +446,7 @@ fn deserialize_binding_values(param_name: &str, values: &[JsonValue]) -> InoxPar "zSort" => BindingValues::ZSort(deserialize_inner_binding_values(values)?), "transform.t.x" => BindingValues::TransformTX(deserialize_inner_binding_values(values)?), "transform.t.y" => BindingValues::TransformTY(deserialize_inner_binding_values(values)?), + "transform.t.z" => BindingValues::TransformTZ(deserialize_inner_binding_values(values)?), "transform.s.x" => BindingValues::TransformSX(deserialize_inner_binding_values(values)?), "transform.s.y" => BindingValues::TransformSY(deserialize_inner_binding_values(values)?), "transform.r.x" => BindingValues::TransformRX(deserialize_inner_binding_values(values)?), @@ -464,8 +465,13 @@ fn deserialize_binding_values(param_name: &str, values: &[JsonValue]) -> InoxPar BindingValues::Deform(Matrix2d::from_slice_vecs(&parsed, true)?) } - // TODO - "opacity" => BindingValues::Opacity, + "tint.r" => BindingValues::TintR(deserialize_inner_binding_values(values)?), + "tint.g" => BindingValues::TintG(deserialize_inner_binding_values(values)?), + "tint.b" => BindingValues::TintB(deserialize_inner_binding_values(values)?), + "screenTint.r" => BindingValues::ScreenTintR(deserialize_inner_binding_values(values)?), + "screenTint.g" => BindingValues::ScreenTintG(deserialize_inner_binding_values(values)?), + "screenTint.b" => BindingValues::ScreenTintB(deserialize_inner_binding_values(values)?), + "opacity" => BindingValues::Opacity(deserialize_inner_binding_values(values)?), param_name => return Err(InoxParseError::UnknownParamName(param_name.to_owned())), }) } diff --git a/inox2d/src/math/interp.rs b/inox2d/src/math/interp.rs index 85c8e8cc..76823d87 100644 --- a/inox2d/src/math/interp.rs +++ b/inox2d/src/math/interp.rs @@ -67,7 +67,15 @@ fn interpolate_linear(t: f32, range_in: InterpRange, range_out: InterpRange range_in.end, ); - (t - range_in.beg) * (range_out.end - range_out.beg) / (range_in.end - range_in.beg) + range_out.beg + let range_out_delta = range_out.end - range_out.beg; + let range_in_delta = range_in.end - range_in.beg; + if range_out_delta == 0.0 { + // Calculus teachers HATE this ONE WEIRD TRICK to getting rid of + // divide-by-zero errors in your code! + return range_out.beg; + } + + (t - range_in.beg) * range_out_delta / range_in_delta + range_out.beg } #[inline] diff --git a/inox2d/src/node.rs b/inox2d/src/node.rs index 432f3dc2..ae924114 100644 --- a/inox2d/src/node.rs +++ b/inox2d/src/node.rs @@ -3,7 +3,7 @@ pub mod drawables; use crate::math::transform::TransformOffset; -#[derive(Clone, Copy, Hash, Eq, PartialEq)] +#[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)] #[repr(transparent)] pub struct InoxNodeUuid(pub(crate) u32); diff --git a/inox2d/src/node/components.rs b/inox2d/src/node/components.rs index 882df767..c8750ae2 100644 --- a/inox2d/src/node/components.rs +++ b/inox2d/src/node/components.rs @@ -30,10 +30,32 @@ pub struct Composite {} /// If has this as a component, the node should render something pub struct Drawable { pub blending: Blending, + + /// The original parameters for this drawable. + /// Copied over `blending` on reset. + initial_blending: Blending, + /// If Some, the node should consider masking when rendering pub masks: Option, } +impl Drawable { + pub fn new(blending: Blending, masks: Option) -> Self { + Self { + blending, + initial_blending: blending, + masks, + } + } + + /// Reset the drawable back to the initial configuration set when `new` + /// was called. + pub fn reset(&mut self) { + self.blending = self.initial_blending; + } +} + +#[derive(Copy, Clone)] pub struct Blending { pub mode: BlendMode, pub tint: Vec3, diff --git a/inox2d/src/node/drawables.rs b/inox2d/src/node/drawables.rs index 451f09bc..6f935de7 100644 --- a/inox2d/src/node/drawables.rs +++ b/inox2d/src/node/drawables.rs @@ -4,7 +4,7 @@ use crate::node::{ components::{Composite, Drawable, Mesh, TexturedMesh, TransformStore}, InoxNodeUuid, }; -use crate::puppet::World; +use crate::puppet::Query; /// Possible component combinations of a renderable node. /// @@ -38,7 +38,7 @@ impl<'comps> DrawableKind<'comps> { /// `None` if node not renderable. /// /// If `check`, will send a warning to `tracing` if component combination non-standard for a supposed-to-be Drawable node. - pub(crate) fn new(id: InoxNodeUuid, comps: &'comps World, check: bool) -> Option { + pub(crate) fn new(id: InoxNodeUuid, comps: &'comps impl Query, check: bool) -> Option { let drawable = match comps.get::(id) { Some(drawable) => drawable, None => return None, diff --git a/inox2d/src/params.rs b/inox2d/src/params.rs index 358af1cb..6d2ff73e 100644 --- a/inox2d/src/params.rs +++ b/inox2d/src/params.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use glam::{vec2, Vec2}; @@ -8,10 +8,10 @@ use crate::math::{ matrix::Matrix2d, }; use crate::node::{ - components::{DeformSource, DeformStack, Mesh, TransformStore, ZSort}, + components::{DeformSource, DeformStack, Drawable, Mesh, TransformStore, ZSort}, InoxNodeUuid, }; -use crate::puppet::{InoxNodeTree, Puppet, World}; +use crate::puppet::{InoxNodeTree, Partition, Puppet, World}; /// Parameter binding to a node. This allows to animate a node based on the value of the parameter that owns it. pub struct Binding { @@ -26,14 +26,20 @@ pub enum BindingValues { ZSort(Matrix2d), TransformTX(Matrix2d), TransformTY(Matrix2d), + TransformTZ(Matrix2d), TransformSX(Matrix2d), TransformSY(Matrix2d), TransformRX(Matrix2d), TransformRY(Matrix2d), TransformRZ(Matrix2d), Deform(Matrix2d>), - // TODO - Opacity, + TintR(Matrix2d), + TintG(Matrix2d), + TintB(Matrix2d), + ScreenTintR(Matrix2d), + ScreenTintG(Matrix2d), + ScreenTintB(Matrix2d), + Opacity(Matrix2d), } #[derive(Debug, Clone)] @@ -70,12 +76,17 @@ pub struct Param { } impl Param { + /// Iterate all bindings + pub(crate) fn iter_bindings(&self) -> impl Iterator { + self.bindings.iter() + } + /// Internal function that modifies puppet components according to one param set. /// Must be only called ONCE per frame to ensure correct behavior. /// /// End users may repeatedly apply a same parameter for multiple times in between frames, /// but other facilities should be present to make sure this `apply()` is only called once per parameter. - pub(crate) fn apply(&self, val: Vec2, nodes: &InoxNodeTree, comps: &mut World) { + pub(crate) fn apply(&self, val: Vec2, nodes: &InoxNodeTree, comps: &mut Partition<'_>) { let val = val.clamp(self.min, self.max); let val_normed = (val - self.min) / (self.max - self.min); @@ -108,6 +119,10 @@ impl Param { // Apply offset on each binding for binding in &self.bindings { + if !comps.contains(binding.node) { + continue; + } + let range_in = InterpRange::new( vec2(self.axis_points.x[x_mindex], self.axis_points.y[y_mindex]), vec2(self.axis_points.x[x_maxdex], self.axis_points.y[y_maxdex]), @@ -142,6 +157,16 @@ impl Param { .translation .y += bi_interpolate_f32(val_normed, range_in, out_top, out_bottom, binding.interpolate_mode); } + BindingValues::TransformTZ(ref matrix) => { + let (out_top, out_bottom) = ranges_out(matrix, x_mindex, x_maxdex, y_mindex, y_maxdex); + + comps + .get_mut::(binding.node) + .unwrap() + .relative + .translation + .z += bi_interpolate_f32(val_normed, range_in, out_top, out_bottom, binding.interpolate_mode); + } BindingValues::TransformSX(ref matrix) => { let (out_top, out_bottom) = ranges_out(matrix, x_mindex, x_maxdex, y_mindex, y_maxdex); @@ -228,8 +253,48 @@ impl Param { .expect("Nodes being deformed must have a DeformStack component.") .push(DeformSource::Param(self.uuid), Deform::Direct(direct_deform)); } - // TODO - BindingValues::Opacity => {} + BindingValues::TintR(ref matrix) => { + let (out_top, out_bottom) = ranges_out(matrix, x_mindex, x_maxdex, y_mindex, y_maxdex); + + comps.get_mut::(binding.node).unwrap().blending.tint.x *= + bi_interpolate_f32(val_normed, range_in, out_top, out_bottom, binding.interpolate_mode); + } + BindingValues::TintG(ref matrix) => { + let (out_top, out_bottom) = ranges_out(matrix, x_mindex, x_maxdex, y_mindex, y_maxdex); + + comps.get_mut::(binding.node).unwrap().blending.tint.y *= + bi_interpolate_f32(val_normed, range_in, out_top, out_bottom, binding.interpolate_mode); + } + BindingValues::TintB(ref matrix) => { + let (out_top, out_bottom) = ranges_out(matrix, x_mindex, x_maxdex, y_mindex, y_maxdex); + + comps.get_mut::(binding.node).unwrap().blending.tint.z *= + bi_interpolate_f32(val_normed, range_in, out_top, out_bottom, binding.interpolate_mode); + } + BindingValues::ScreenTintR(ref matrix) => { + let (out_top, out_bottom) = ranges_out(matrix, x_mindex, x_maxdex, y_mindex, y_maxdex); + + comps.get_mut::(binding.node).unwrap().blending.screen_tint.x += + bi_interpolate_f32(val_normed, range_in, out_top, out_bottom, binding.interpolate_mode); + } + BindingValues::ScreenTintG(ref matrix) => { + let (out_top, out_bottom) = ranges_out(matrix, x_mindex, x_maxdex, y_mindex, y_maxdex); + + comps.get_mut::(binding.node).unwrap().blending.screen_tint.y += + bi_interpolate_f32(val_normed, range_in, out_top, out_bottom, binding.interpolate_mode); + } + BindingValues::ScreenTintB(ref matrix) => { + let (out_top, out_bottom) = ranges_out(matrix, x_mindex, x_maxdex, y_mindex, y_maxdex); + + comps.get_mut::(binding.node).unwrap().blending.screen_tint.z += + bi_interpolate_f32(val_normed, range_in, out_top, out_bottom, binding.interpolate_mode); + } + BindingValues::Opacity(ref matrix) => { + let (out_top, out_bottom) = ranges_out(matrix, x_mindex, x_maxdex, y_mindex, y_maxdex); + + comps.get_mut::(binding.node).unwrap().blending.opacity *= + bi_interpolate_f32(val_normed, range_in, out_top, out_bottom, binding.interpolate_mode); + } } } } @@ -264,15 +329,30 @@ impl ParamCtx { } } + pub fn get(&self, param_name: &str) -> Result { + if let Some(value) = self.values.get(param_name) { + Ok(*value) + } else { + Err(SetParamError::NoParameterNamed(param_name.to_string())) + } + } + /// Modify components as specified by all params. Must be called ONCE per frame. pub(crate) fn apply(&self, params: &HashMap, nodes: &InoxNodeTree, comps: &mut World) { - // a correct implementation should not care about the order of `.apply()` - for (param_name, val) in self.values.iter() { - // TODO: a correct implementation should not fail on param value (0, 0) - if *val != Vec2::ZERO { - params.get(param_name).unwrap().apply(*val, nodes, comps); + let mut bindings_bucket = HashSet::new(); + + for (param_name, _val) in self.values.iter() { + for node in params.get(param_name).unwrap().iter_bindings().map(|b| b.node) { + bindings_bucket.insert(node); } } + + let accessed_nodes: Vec<_> = bindings_bucket.iter().copied().collect(); + comps.with_node_partitions(accessed_nodes.as_slice(), |mut comps, _| { + for (param_name, val) in self.values.iter() { + params.get(param_name).unwrap().apply(*val, nodes, &mut comps); + } + }) } } diff --git a/inox2d/src/puppet.rs b/inox2d/src/puppet.rs index 7cdcbfa1..196c886b 100644 --- a/inox2d/src/puppet.rs +++ b/inox2d/src/puppet.rs @@ -13,7 +13,7 @@ use crate::render::RenderCtx; use meta::PuppetMeta; use transforms::TransformCtx; pub use tree::InoxNodeTree; -pub use world::World; +pub use world::{Partition, Query, World}; /// Inochi2D puppet. pub struct Puppet { diff --git a/inox2d/src/puppet/world.rs b/inox2d/src/puppet/world.rs index ebad4444..78796f83 100644 --- a/inox2d/src/puppet/world.rs +++ b/inox2d/src/puppet/world.rs @@ -1,7 +1,14 @@ use std::any::TypeId; -use std::collections::HashMap; +use std::borrow::Cow; +use std::cell::UnsafeCell; +use std::cmp::max; +use std::collections::{HashMap, HashSet}; +use std::hash::RandomState; use std::mem::{size_of, transmute, ManuallyDrop, MaybeUninit}; +use rayon::current_num_threads; +use rayon::prelude::*; + use super::InoxNodeUuid; // to keep the provenance of the pointer in Vec (or any data struct that contains pointers), @@ -27,11 +34,11 @@ impl Drop for AnyVec { impl AnyVec { // Self is inherently Send + Sync as a pack of bytes regardless of inner type, which is bad pub fn new() -> Self { - let vec = ManuallyDrop::new(Vec::::new()); + let vec = ManuallyDrop::new(Vec::>::new()); Self { // SAFETY: ManuallyDrop guaranteed to have same bit layout as inner, and inner is a proper Vec // provenance considerations present, see comment for VecBytes - vec_bytes: unsafe { transmute::>, VecBytes>(vec) }, + vec_bytes: unsafe { transmute::>>, VecBytes>(vec) }, // SAFETY: only to be called once at end of lifetime, and vec_bytes contain a valid Vec throughout self lifetime drop: |vec_bytes| unsafe { let vec: Vec = transmute(*vec_bytes); @@ -42,12 +49,12 @@ impl AnyVec { } /// T MUST be the same as in new::() for a same instance - pub unsafe fn downcast_unchecked(&self) -> &Vec { + pub unsafe fn downcast_unchecked(&self) -> &Vec> { transmute(&self.vec_bytes) } /// T MUST be the same as in new::() for a same instance - pub unsafe fn downcast_mut_unchecked(&mut self) -> &mut Vec { + pub unsafe fn downcast_mut_unchecked(&mut self) -> &mut Vec> { transmute(&mut self.vec_bytes) } } @@ -80,7 +87,7 @@ impl World { debug_assert!(!pair.1.contains_key(&node),); pair.1.insert(node, column.len()); - column.push(v); + column.push(UnsafeCell::new(v)); } pub fn get(&self, node: InoxNodeUuid) -> Option<&T> { @@ -97,7 +104,7 @@ impl World { }; debug_assert!(index < column.len()); // SAFETY: what has been inserted into pair.1 should be a valid index - Some(unsafe { column.get_unchecked(index) }) + Some(unsafe { &*column.get_unchecked(index).get() }) } pub fn get_mut(&mut self, node: InoxNodeUuid) -> Option<&mut T> { @@ -114,7 +121,31 @@ impl World { }; debug_assert!(index < column.len()); // SAFETY: what has been inserted into pair.1 should be a valid index - Some(unsafe { column.get_unchecked_mut(index) }) + // SAFETY: since we own a &mut self, no other &mut references can exist + // to any other world cell + Some(unsafe { &mut *column.get_unchecked_mut(index).get() }) + } + + /// # Safety + /// + /// All safety requirements of `UnsafeCell` apply. Caller must ensure no + /// two aliasing mutable references to the same (node, T) pair exist at the + /// same time. + pub unsafe fn get_interior_mut(&self, node: InoxNodeUuid) -> Option<&mut T> { + let pair = match self.columns.get(&TypeId::of::()) { + Some(c) => c, + None => return None, + }; + // SAFETY: AnyVec in pair must be of type T, enforced by hashing + let column = unsafe { pair.0.downcast_unchecked() }; + + let index = match pair.1.get(&node) { + Some(i) => *i, + None => return None, + }; + debug_assert!(index < column.len()); + // SAFETY: what has been inserted into pair.1 should be a valid index + Some(unsafe { &mut *column.get_unchecked(index).get() }) } /// # Safety @@ -124,7 +155,7 @@ impl World { let pair = self.columns.get(&TypeId::of::()).unwrap_unchecked(); let index = *pair.1.get(&node).unwrap_unchecked(); - pair.0.downcast_unchecked().get_unchecked(index) + &*pair.0.downcast_unchecked().get_unchecked(index).get() } /// # Safety @@ -134,7 +165,42 @@ impl World { let pair = self.columns.get_mut(&TypeId::of::()).unwrap_unchecked(); let index = *pair.1.get(&node).unwrap_unchecked(); - pair.0.downcast_mut_unchecked().get_unchecked_mut(index) + &mut *pair.0.downcast_mut_unchecked().get_unchecked_mut(index).get() + } + + /// Yield a partition that refers to the total contents of the world. + pub fn as_partition(&mut self) -> Partition<'_> { + Partition { + nodes: None, + components: None, + data: self, + } + } + + /// Do parallel work on the given world. + /// + /// This function splits the world node-wise into chunks and provides + /// each partition to the provided callback in a separate thread. + pub fn with_node_partitions(&mut self, nodes: &[InoxNodeUuid], your_fn: F) + where + F: for<'b> Fn(Partition<'b>, &[InoxNodeUuid]) + Sync, + { + self.as_partition().with_node_partitions(nodes, your_fn) + } + + /// Do parallel work on the given world and return a list of values. + /// + /// One value will be returned per invocation of your function; you will + /// need to fold them yourself. + /// + /// This function splits the world node-wise into chunks and provides + /// each partition to the provided callback in a separate thread. + pub fn with_node_partitions_map(&mut self, nodes: &[InoxNodeUuid], your_fn: F) -> Vec + where + F: for<'b> Fn(Partition<'b>, &[InoxNodeUuid]) -> T + Sync, + T: Send, + { + self.as_partition().with_node_partitions_map(nodes, your_fn) } } @@ -144,10 +210,232 @@ impl Default for World { } } +/// A subset of the World that is partitioned by node UUID or component type. +/// +/// Partitions will allow access to the specific UUIDs or components only, with +/// all other access to nodes failing. This is intended as a way to partition +/// multithreaded writes to the ECS world. +/// +/// SAFETY CONSIDERATIONS: +/// +/// Partitions must only be created in functions that have mutable access to +/// the underlying World, in such a way that the Partition's lifetime is bound +/// to that mutable borrow. While we do not store a mutable pointer, we do +/// mutate the underlying world. +pub struct Partition<'a> { + nodes: Option>, + components: Option>, + data: &'a World, +} + +impl<'a> Partition<'a> { + /// Create a new world partition. + /// + /// SAFETY: Callers must ensure that all live partitions of the given World + /// reference disjoint node IDs before handing them to safe code. + pub unsafe fn new_unchecked( + nodes: Option>, + components: Option>, + data: &'a World, + ) -> Self { + Self { + nodes, + components, + data, + } + } + + pub fn get(&self, node: InoxNodeUuid) -> Option<&T> { + // Note: the node scan is still necessary as some other partition may + // be handing out &mut Ts + if self.contains(node) { + self.data.get(node) + } else { + None + } + } + + pub fn get_mut(&mut self, node: InoxNodeUuid) -> Option<&mut T> { + if self.contains(node) { + unsafe { self.data.get_interior_mut(node) } + } else { + None + } + } + + /// Retrieve the list of nodes present in the partition. + /// + /// If the world is not partitioned by node, then this yields an empty + /// array. To check if a particular node is part of the partition, use + /// `.contains()`. + pub fn nodes(&self) -> impl Iterator + use<'_> { + self.nodes.iter().flat_map(|n| n.iter()).copied() + } + + /// Determine if a particular node is contained in the partition. + pub fn contains(&self, node: InoxNodeUuid) -> bool { + self.nodes.is_none() || self.nodes().find(|n| *n == node).is_some() + } + + /// Determine if a particular component is contained in the partition. + pub fn contains_component(&self) -> bool { + self.components.is_none() + || self + .components + .as_ref() + .unwrap() + .iter() + .find(|c| **c == TypeId::of::()) + .is_some() + } + + /// Determine if a particular component's TypeId is contained in the + /// partition. + pub fn contains_type_id(&self, type_id: TypeId) -> bool { + if let Some(components) = &self.components { + components.iter().find(|c| **c == type_id).is_some() + } else { + true + } + } + + /// Split the given partition by a particular component. + /// + /// The first partition returned will be limited to the given component. + /// The second will contain all other components in this partition. + /// + /// In the event that the requested component is not present, the first + /// partition will be empty (no components), and the second partition will + /// contain the same components as this partition. + pub fn split_by_component(self) -> (Partition<'a>, Partition<'a>) { + let c = TypeId::of::(); + let has_c = if let Some(components_partition) = self.components.as_ref() { + components_partition.iter().find(|other_c| **other_c == c).is_some() + } else { + //None signals no restriction + true + }; + + let c_list = if has_c { Cow::Owned(vec![c]) } else { Cow::Owned(vec![]) }; + + let not_c = if has_c { + if let Some(components_partition) = self.components.as_ref() { + Cow::Owned( + components_partition + .iter() + .filter(|other_c| **other_c != c) + .copied() + .collect(), + ) + } else { + Cow::Owned(self.data.columns.keys().copied().collect()) + } + } else { + self.components.unwrap() + }; + + ( + Partition { + nodes: self.nodes.clone(), + components: Some(c_list), + data: self.data, + }, + Partition { + nodes: self.nodes, + components: Some(not_c), + data: self.data, + }, + ) + } + + /// Do parallel work on the given partition. + /// + /// This function splits the partition node-wise into chunks and provides + /// each split of the partition to the provided callback in a separate + /// thread. + pub fn with_node_partitions(&mut self, nodes: &[InoxNodeUuid], your_fn: F) + where + F: for<'b> Fn(Partition<'b>, &[InoxNodeUuid]) + Sync, + { + self.with_node_partitions_map(nodes, your_fn); + } + + /// Do parallel work on the given partition and return a list of values. + /// + /// One value will be returned per invocation of your function; you will + /// need to fold them yourself. + /// + /// This function splits the partition node-wise into chunks and provides + /// each split of the partition to the provided callback in a separate + /// thread. + pub fn with_node_partitions_map(&mut self, nodes: &[InoxNodeUuid], your_fn: F) -> Vec + where + F: for<'b> Fn(Partition<'b>, &[InoxNodeUuid]) -> T + Sync, + T: Send, + { + // SAFETY: We must filter duplicates from `nodes` to ensure disjoint + // partitions. + // + // NOTE: For some reason type inference fails and we have to manually + // tell Rust to use the standard state type. + let nodes_set: HashSet = HashSet::from_iter(nodes.iter().copied()); + let nodes_clean: Vec<_> = if let Some(nodes) = &self.nodes { + // If we are already in a node partition, we must also intersect + // with our own node partition. + let outer_set: HashSet = HashSet::from_iter(nodes.iter().copied()); + + nodes_set.intersection(&outer_set).copied().collect() + } else { + nodes_set.into_iter().collect() + }; + + let nodes_count = nodes_clean.len(); + let batch_size = max(nodes_count / current_num_threads(), 1); + + nodes_clean + .par_chunks(batch_size) + .map(|nodes| { + your_fn( + unsafe { Partition::new_unchecked(Some(nodes.into()), None, &self.data) }, + nodes, + ) + }) + .collect() + } +} + +/// Represents any type that can retrieve components for a given node. +pub trait Query { + fn get(&self, node: InoxNodeUuid) -> Option<&T>; + + fn get_mut(&mut self, node: InoxNodeUuid) -> Option<&mut T>; +} + +impl Query for World { + fn get(&self, node: InoxNodeUuid) -> Option<&T> { + self.get(node) + } + + fn get_mut(&mut self, node: InoxNodeUuid) -> Option<&mut T> { + self.get_mut(node) + } +} + +impl Query for Partition<'_> { + fn get(&self, node: InoxNodeUuid) -> Option<&T> { + self.get(node) + } + + fn get_mut(&mut self, node: InoxNodeUuid) -> Option<&mut T> { + self.get_mut(node) + } +} + #[cfg(test)] mod tests { mod any_vec { use super::super::AnyVec; + use std::cell::UnsafeCell; #[test] fn new_and_drop_empty() { @@ -180,16 +468,22 @@ mod tests { let mut any_vec = AnyVec::new::(); unsafe { - any_vec.downcast_mut_unchecked().push(Data { int: 0, c: b'A' }); - any_vec.downcast_mut_unchecked().push(Data { int: 1, c: b'B' }); - any_vec.downcast_mut_unchecked().push(Data { int: 2, c: b'C' }); - - assert_eq!(any_vec.downcast_unchecked::()[0], Data { int: 0, c: b'A' }); - assert_eq!(any_vec.downcast_unchecked::()[1], Data { int: 1, c: b'B' }); - - any_vec.downcast_mut_unchecked::()[2].c = b'D'; - - assert_eq!(any_vec.downcast_unchecked::()[2], Data { int: 2, c: b'D' }); + any_vec + .downcast_mut_unchecked() + .push(UnsafeCell::new(Data { int: 0, c: b'A' })); + any_vec + .downcast_mut_unchecked() + .push(UnsafeCell::new(Data { int: 1, c: b'B' })); + any_vec + .downcast_mut_unchecked() + .push(UnsafeCell::new(Data { int: 2, c: b'C' })); + + assert_eq!(*any_vec.downcast_unchecked::()[0].get(), Data { int: 0, c: b'A' }); + assert_eq!(*any_vec.downcast_unchecked::()[1].get(), Data { int: 1, c: b'B' }); + + (*any_vec.downcast_mut_unchecked::()[2].get()).c = b'D'; + + assert_eq!(*any_vec.downcast_unchecked::()[2].get(), Data { int: 2, c: b'D' }); } } } @@ -256,4 +550,87 @@ mod tests { } } } + + mod partition { + use crate::node::InoxNodeUuid; + + use super::super::World; + use std::any::TypeId; + + struct ComponentA {} + + struct ComponentB {} + + struct ComponentC {} + + #[test] + fn disjoint_component_partitions() { + let mut world = World::new(); + + let partition = world.as_partition(); + + assert_eq!(partition.nodes.as_ref(), None); + assert_eq!(partition.components.as_deref(), None); + + let (partition_a, partition_b) = world.as_partition().split_by_component::(); + + assert_eq!(partition_a.nodes.as_ref(), None); + assert_eq!(partition_b.nodes.as_ref(), None); + assert_eq!( + partition_a.components.as_deref(), + Some([TypeId::of::()].as_slice()) + ); + assert_eq!(partition_b.components.as_deref(), Some([].as_slice())); + + world.add(InoxNodeUuid(0), ComponentB {}); + world.add(InoxNodeUuid(0), ComponentC {}); + + let (partition_a, partition_b) = world.as_partition().split_by_component::(); + + assert_eq!(partition_a.nodes.as_ref(), None); + assert_eq!(partition_b.nodes.as_ref(), None); + assert_eq!( + partition_a.components.as_deref(), + Some([TypeId::of::()].as_slice()) + ); + assert!(partition_b + .components + .as_deref() + .unwrap() + .contains(&TypeId::of::())); + assert!(partition_b + .components + .as_deref() + .unwrap() + .contains(&TypeId::of::())); + + let (partition_c, partition_d) = partition_a.split_by_component::(); + + assert_eq!(partition_c.nodes.as_ref(), None); + assert_eq!(partition_d.nodes.as_ref(), None); + assert_eq!(partition_c.components.as_deref(), Some([].as_slice())); + assert_eq!( + partition_d.components.as_deref(), + Some([TypeId::of::()].as_slice()) + ); + + let (partition_c, partition_d) = partition_b.split_by_component::(); + + assert_eq!(partition_c.nodes.as_ref(), None); + assert_eq!(partition_d.nodes.as_ref(), None); + assert_eq!( + partition_c.components.as_deref(), + Some([TypeId::of::()].as_slice()) + ); + assert_eq!( + partition_d.components.as_deref(), + Some([TypeId::of::()].as_slice()) + ); + + let partition = world.as_partition(); + + assert_eq!(partition.nodes.as_ref(), None); + assert_eq!(partition.components.as_deref(), None); + } + } } diff --git a/inox2d/src/render.rs b/inox2d/src/render.rs index c77fe74d..0cb1826a 100644 --- a/inox2d/src/render.rs +++ b/inox2d/src/render.rs @@ -5,7 +5,7 @@ use std::collections::HashSet; use std::mem::swap; use crate::node::{ - components::{DeformStack, Mask, Masks, ZSort}, + components::{DeformStack, Drawable, Mask, Masks, ZSort}, drawables::{CompositeComponents, DrawableKind, TexturedMeshComponents}, InoxNodeUuid, }; @@ -129,6 +129,10 @@ impl RenderCtx { if let Some(deform_stack) = comps.get_mut::(node.uuid) { deform_stack.reset(); } + + if let Some(drawable) = comps.get_mut::(node.uuid) { + drawable.reset(); + } } } diff --git a/inox2d/src/render/deform_stack.rs b/inox2d/src/render/deform_stack.rs index 037a23db..792a99d6 100644 --- a/inox2d/src/render/deform_stack.rs +++ b/inox2d/src/render/deform_stack.rs @@ -5,7 +5,7 @@ use glam::Vec2; use crate::math::deform::{linear_combine, Deform}; use crate::node::components::{DeformSource, DeformStack}; -use crate::puppet::{InoxNodeTree, World}; +use crate::puppet::{InoxNodeTree, Query}; impl DeformStack { pub(crate) fn new(deform_len: usize) -> Self { @@ -23,7 +23,7 @@ impl DeformStack { } /// Combine the deformations received so far according to some rules, and write to the result - pub(crate) fn combine(&self, _nodes: &InoxNodeTree, _node_comps: &World, result: &mut [Vec2]) { + pub(crate) fn combine(&self, _nodes: &InoxNodeTree, _node_comps: &impl Query, result: &mut [Vec2]) { if result.len() != self.deform_len { panic!("Required output deform dimensions different from what DeformStack is initialized with.") }