1use crate::{Axis, Deform, Rect};
15
16pub const CAPACITY: usize = 64 * 64;
22
23static mut SCRATCH: [f64; CAPACITY * 2] = [0.0; CAPACITY * 2];
24
25#[expect(unsafe_code, reason = "exporting a C symbol and its scratch buffer")]
27#[unsafe(no_mangle)]
28pub extern "C" fn solium_effect_scratch() -> *const f64 {
29 &raw const SCRATCH as *const f64
30}
31
32#[expect(unsafe_code, reason = "exporting a C symbol")]
33#[unsafe(no_mangle)]
34pub extern "C" fn solium_effect_capacity() -> u32 {
35 CAPACITY as u32
36}
37
38#[expect(unsafe_code, reason = "exporting a C symbol for the preview")]
40#[unsafe(no_mangle)]
41pub extern "C" fn solium_effect_count() -> u32 {
42 Deform::all().len() as u32
43}
44
45#[expect(unsafe_code, reason = "exporting a C symbol for the preview")]
47#[unsafe(no_mangle)]
48pub extern "C" fn solium_axis_count() -> u32 {
49 Axis::all().len() as u32
50}
51
52fn effect(index: u32, axis: u32, progress: f64, spread: f64) -> Deform {
58 let axis = Axis::all()
59 .get(axis as usize)
60 .map_or_else(Axis::default, |(_, axis)| *axis);
61 let [(_, first), ..] = Deform::all();
64 match Deform::all()
65 .get(index as usize)
66 .map_or(first, |(_, effect)| *effect)
67 {
68 Deform::Genie { .. } => Deform::Genie {
69 progress: crate::number(progress),
70 spread: crate::number(spread),
71 axis,
72 },
73 }
74}
75
76#[expect(unsafe_code, reason = "exporting a C symbol for the preview")]
78#[unsafe(no_mangle)]
79pub extern "C" fn solium_effect_columns(index: u32, axis: u32) -> u32 {
80 effect(index, axis, 0.0, 0.0).segments().0
81}
82
83#[expect(unsafe_code, reason = "exporting a C symbol for the preview")]
85#[unsafe(no_mangle)]
86pub extern "C" fn solium_effect_rows(index: u32, axis: u32) -> u32 {
87 effect(index, axis, 0.0, 0.0).segments().1
88}
89
90#[expect(unsafe_code, reason = "exporting a C symbol for the preview")]
97#[unsafe(no_mangle)]
98pub extern "C" fn solium_effect_grid(
99 index: u32,
100 axis: u32,
101 progress: f64,
102 spread: f64,
103 from_x: f64,
104 from_y: f64,
105 from_w: f64,
106 from_h: f64,
107 to_x: f64,
108 to_y: f64,
109 to_w: f64,
110 to_h: f64,
111) -> u32 {
112 let deform = effect(index, axis, progress, spread);
113 let from = Rect::new(from_x, from_y, from_w, from_h);
114 let to = Rect::new(to_x, to_y, to_w, to_h);
115 let (columns, rows) = deform.segments();
116
117 let mut written = 0_usize;
118 for row in 0..=rows {
119 let v = f64::from(row) / f64::from(rows);
120 for column in 0..=columns {
121 if written >= CAPACITY {
122 break;
123 }
124 let u = f64::from(column) / f64::from(columns);
125 let (x, y) = deform.place(from, to, u, v);
126 publish(written, x, y);
127 written += 1;
128 }
129 }
130 written as u32
131}
132
133#[expect(unsafe_code, reason = "writing the scratch buffer the caller reads")]
135fn publish(index: usize, x: f64, y: f64) {
136 if index >= CAPACITY {
137 return;
138 }
139 let scratch = &raw mut SCRATCH;
140 unsafe {
146 (*scratch)[index * 2] = x;
147 (*scratch)[index * 2 + 1] = y;
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[expect(unsafe_code, reason = "reading the scratch buffer the page reads")]
158 #[test]
159 fn the_exported_surface_agrees_with_the_engine() {
160 let from = Rect::new(0.0, 0.0, 800.0, 600.0);
161 let to = Rect::new(400.0, 900.0, 64.0, 32.0);
162
163 for index in 0..solium_effect_count() {
164 for axis in 0..solium_axis_count() {
165 let deform = effect(index, axis, 0.4, 1.2);
166 let (columns, rows) = deform.segments();
167 assert_eq!(solium_effect_columns(index, axis), columns);
168 assert_eq!(solium_effect_rows(index, axis), rows);
169
170 let written = solium_effect_grid(
171 index, axis, 0.4, 1.2, from.x, from.y, from.w, from.h, to.x, to.y, to.w, to.h,
172 );
173 assert_eq!(written, (columns + 1) * (rows + 1));
174
175 let base = solium_effect_scratch();
178 for row in 0..=rows {
179 for column in 0..=columns {
180 let at = (row * (columns + 1) + column) as usize;
181 let (u, v) = (
182 f64::from(column) / f64::from(columns),
183 f64::from(row) / f64::from(rows),
184 );
185 let (x, y) = deform.place(from, to, u, v);
186 let (got_x, got_y) = unsafe { (*base.add(at * 2), *base.add(at * 2 + 1)) };
190 assert!((got_x - x).abs() < f64::EPSILON, "point {at} x");
191 assert!((got_y - y).abs() < f64::EPSILON, "point {at} y");
192 }
193 }
194 }
195 }
196 }
197
198 #[test]
199 fn an_out_of_range_effect_falls_back_rather_than_panicking() {
200 assert_eq!(effect(9999, 9999, 0.5, 1.0), effect(0, 0, 0.5, 1.0));
202 }
203
204 #[test]
206 fn the_grid_fits_the_buffer_it_is_published_in() {
207 for index in 0..solium_effect_count() {
208 for axis in 0..solium_axis_count() {
209 let (columns, rows) = effect(index, axis, 0.0, 0.0).segments();
210 assert!(
211 ((columns + 1) * (rows + 1)) as usize <= CAPACITY,
212 "{}x{} points does not fit {CAPACITY}",
213 columns + 1,
214 rows + 1
215 );
216 }
217 }
218 }
219}