feagi_serialization/implementations/
cortical_mapped_xyzp_neuron_data.rs

1//! Serialization implementation for cortical-mapped neuron voxel xyzp data.
2
3use crate::{FeagiByteContainer, FeagiByteStructureType, FeagiSerializable};
4use byteorder::{ByteOrder, LittleEndian};
5use feagi_structures::genomic::cortical_area::CorticalID;
6use feagi_structures::neuron_voxels::xyzp::{
7    CorticalMappedXYZPNeuronVoxels, NeuronVoxelXYZP, NeuronVoxelXYZPArrays,
8};
9use feagi_structures::FeagiDataError;
10use std::any::Any;
11
12/// Current version of the neuron XYZP serialization format.
13const BYTE_STRUCT_VERSION: u8 = 1;
14
15/// Bytes per cortical ID header: 8 (ID) + 4 (start index) + 4 (byte count).
16const NUMBER_BYTES_PER_CORTICAL_ID_HEADER: usize =
17    CorticalID::NUMBER_OF_BYTES + size_of::<u32>() + size_of::<u32>();
18
19/// Bytes for cortical area count header.
20const NUMBER_BYTES_CORTICAL_COUNT_HEADER: usize = size_of::<u16>();
21
22impl FeagiSerializable for CorticalMappedXYZPNeuronVoxels {
23    fn get_type(&self) -> FeagiByteStructureType {
24        FeagiByteStructureType::NeuronCategoricalXYZP
25    }
26
27    fn get_version(&self) -> u8 {
28        BYTE_STRUCT_VERSION
29    }
30
31    fn get_number_of_bytes_needed(&self) -> usize {
32        let mut number_bytes_needed: usize =
33            FeagiByteContainer::STRUCT_HEADER_BYTE_COUNT + NUMBER_BYTES_CORTICAL_COUNT_HEADER;
34        for neuron_data in self.iter() {
35            number_bytes_needed +=
36                neuron_data.get_size_in_number_of_bytes() + NUMBER_BYTES_PER_CORTICAL_ID_HEADER;
37        }
38        number_bytes_needed
39    }
40
41    fn try_serialize_struct_to_byte_slice(
42        &self,
43        byte_destination: &mut [u8],
44    ) -> Result<(), FeagiDataError> {
45        // write per struct header
46        byte_destination[0] = self.get_type() as u8;
47        byte_destination[1] = self.get_version();
48
49        // Initial Section Header
50        let number_cortical_areas: usize = self.mappings.len();
51        const NUMBER_BYTES_INITIAL_SECTION_HEADER: usize = size_of::<u16>();
52        LittleEndian::write_u16(
53            &mut byte_destination[FeagiByteContainer::STRUCT_HEADER_BYTE_COUNT
54                ..FeagiByteContainer::STRUCT_HEADER_BYTE_COUNT
55                    + NUMBER_BYTES_INITIAL_SECTION_HEADER],
56            number_cortical_areas as u16,
57        );
58
59        // Write Cortical Secondary Header and Neuron Data Together in a single loop
60        let mut subheader_write_index: usize =
61            FeagiByteContainer::STRUCT_HEADER_BYTE_COUNT + NUMBER_BYTES_INITIAL_SECTION_HEADER;
62        let mut neuron_data_write_index: usize =
63            subheader_write_index + (number_cortical_areas * NUMBER_BYTES_PER_CORTICAL_ID_HEADER);
64
65        for (cortical_id, neuron_data) in &self.mappings {
66            // Write cortical subheader
67            let cortical_area_lookup_header_slice = &mut byte_destination
68                [subheader_write_index..subheader_write_index + CorticalID::NUMBER_OF_BYTES];
69            let cortical_area_lookup_header_slice: &mut [u8; CorticalID::NUMBER_OF_BYTES] =
70                cortical_area_lookup_header_slice.try_into().unwrap();
71            cortical_id.write_id_to_bytes(cortical_area_lookup_header_slice);
72
73            let reading_length: u32 = neuron_data.get_size_in_number_of_bytes() as u32;
74            LittleEndian::write_u32(
75                &mut byte_destination[subheader_write_index + CorticalID::NUMBER_OF_BYTES
76                    ..subheader_write_index + CorticalID::NUMBER_OF_BYTES + size_of::<u32>()],
77                neuron_data_write_index as u32,
78            );
79            LittleEndian::write_u32(
80                &mut byte_destination[subheader_write_index
81                    + CorticalID::NUMBER_OF_BYTES
82                    + size_of::<u32>()
83                    ..subheader_write_index
84                        + CorticalID::NUMBER_OF_BYTES
85                        + size_of::<u32>()
86                        + size_of::<u32>()],
87                reading_length,
88            );
89
90            // write neuron data
91            write_neuron_array_to_bytes(
92                neuron_data,
93                &mut byte_destination
94                    [neuron_data_write_index..(neuron_data_write_index + reading_length as usize)],
95            )?;
96
97            // update indexes
98            subheader_write_index += NUMBER_BYTES_PER_CORTICAL_ID_HEADER;
99            neuron_data_write_index += reading_length as usize;
100        }
101
102        Ok(())
103    }
104
105    fn try_deserialize_and_update_self_from_byte_slice(
106        &mut self,
107        byte_reading: &[u8],
108    ) -> Result<(), FeagiDataError> {
109        // Assuming type is correct
110        self.verify_byte_slice_is_of_correct_version(byte_reading)?;
111        self.clear_neurons_only(); // This causes a memory leak. Too Bad!
112
113        let number_cortical_areas: usize = LittleEndian::read_u16(&byte_reading[2..4]) as usize;
114        let mut reading_header_byte_index: usize =
115            FeagiByteContainer::STRUCT_HEADER_BYTE_COUNT + NUMBER_BYTES_CORTICAL_COUNT_HEADER;
116
117        for _cortical_index in 0..number_cortical_areas {
118            let cortical_id = CorticalID::try_from_bytes(
119                <&[u8; CorticalID::NUMBER_OF_BYTES]>::try_from(
120                    &byte_reading[reading_header_byte_index
121                        ..reading_header_byte_index + CorticalID::NUMBER_OF_BYTES],
122                )
123                .unwrap(),
124            )?;
125
126            const CORTICAL_ID_AND_U32_OFFSET: usize =
127                size_of::<u32>() + CorticalID::NUMBER_OF_BYTES;
128            const CORTICAL_ID_AND_U32_AND_U32_OFFSET: usize =
129                size_of::<u32>() + size_of::<u32>() + CorticalID::NUMBER_OF_BYTES;
130
131            let data_start_reading: usize = LittleEndian::read_u32(
132                &byte_reading[reading_header_byte_index + CorticalID::NUMBER_OF_BYTES
133                    ..reading_header_byte_index + CORTICAL_ID_AND_U32_OFFSET],
134            ) as usize;
135            let number_bytes_to_read: usize = LittleEndian::read_u32(
136                &byte_reading[reading_header_byte_index + CORTICAL_ID_AND_U32_OFFSET
137                    ..reading_header_byte_index + CORTICAL_ID_AND_U32_AND_U32_OFFSET],
138            ) as usize;
139
140            if byte_reading.len() < data_start_reading + number_bytes_to_read {
141                return Err(FeagiDataError::SerializationError("Byte structure for NeuronCategoricalXYZP is too short to fit the data the header says it contains!".into()));
142            }
143
144            let neuron_bytes =
145                &byte_reading[data_start_reading..data_start_reading + number_bytes_to_read];
146            let bytes_length = neuron_bytes.len();
147
148            #[allow(clippy::manual_is_multiple_of)] // Manual modulo check is clear and works
149            if bytes_length % NeuronVoxelXYZP::NUMBER_BYTES_PER_NEURON != 0 {
150                return Err(FeagiDataError::SerializationError("As NeuronXYCPArrays contains 4 internal arrays of equal length, each of elements of 4 bytes each (uint32 and float), the input bytes array must be divisible by 16!".into()));
151            }
152
153            let x_end = bytes_length / 4; // q1
154            let y_end = bytes_length / 2; // q2
155            let z_end = x_end * 3; // q3
156
157            let num_neurons = bytes_length / NeuronVoxelXYZP::NUMBER_BYTES_PER_NEURON;
158            let neuron_array = self.ensure_clear_and_borrow_mut(&cortical_id);
159            neuron_array.ensure_capacity(num_neurons);
160
161            // TODO this could potentially be parallelized
162            for i in 0..num_neurons {
163                let x_start = i * 4;
164                let y_start = x_end + x_start;
165                let z_start = y_end + x_start;
166                let p_start = z_end + x_start;
167
168                neuron_array.push_raw(
169                    LittleEndian::read_u32(&neuron_bytes[x_start..x_start + 4]),
170                    LittleEndian::read_u32(&neuron_bytes[y_start..y_start + 4]),
171                    LittleEndian::read_u32(&neuron_bytes[z_start..z_start + 4]),
172                    LittleEndian::read_f32(&neuron_bytes[p_start..p_start + 4]),
173                )
174            }
175            reading_header_byte_index += NUMBER_BYTES_PER_CORTICAL_ID_HEADER;
176        }
177
178        Ok(())
179    }
180
181    fn as_any(&self) -> &dyn Any {
182        self
183    }
184}
185
186/// Serializes a neuron voxel array to bytes in structure-of-arrays format.
187///
188/// Writes X, Y, Z coordinates and potential values as separate contiguous blocks
189/// for memory efficiency and cache locality during deserialization.
190#[inline]
191fn write_neuron_array_to_bytes(
192    neuron_array: &NeuronVoxelXYZPArrays,
193    bytes_to_write_to: &mut [u8],
194) -> Result<(), FeagiDataError> {
195    const U32_F32_LENGTH: usize = 4;
196    let number_of_neurons_to_write: usize = neuron_array.len();
197    let number_bytes_needed = neuron_array.get_size_in_number_of_bytes();
198    if bytes_to_write_to.len() != number_bytes_needed {
199        return Err(FeagiDataError::SerializationError(format!(
200            "Need exactly {} bytes to write xyzp neuron data, but given a space of {} bytes!",
201            bytes_to_write_to.len(),
202            number_bytes_needed
203        )));
204    }
205
206    let x_offset: usize = 0;
207    let y_offset = number_of_neurons_to_write * U32_F32_LENGTH; // quarter way through the total bytes
208    let z_offset = number_of_neurons_to_write * U32_F32_LENGTH * 2; // halfway through the total bytes
209    let p_offset = number_of_neurons_to_write * U32_F32_LENGTH * 3; // three quarters way through the total bytes
210
211    let (x, y, z, p) = neuron_array.borrow_xyzp_vectors();
212
213    // OPTIMIZATION: Use bulk memory operations for little-endian systems (x86_64, ARM64)
214    #[cfg(target_endian = "little")]
215    {
216        let x_len = x.len() * U32_F32_LENGTH;
217        let y_len = y.len() * U32_F32_LENGTH;
218        let z_len = z.len() * U32_F32_LENGTH;
219        let p_len = p.len() * U32_F32_LENGTH;
220
221        // Use direct pointer copies for maximum performance
222        // These will use optimized memcpy (often SIMD-accelerated) internally
223        unsafe {
224            std::ptr::copy_nonoverlapping(
225                x.as_ptr() as *const u8,
226                bytes_to_write_to.as_mut_ptr().add(x_offset),
227                x_len,
228            );
229            std::ptr::copy_nonoverlapping(
230                y.as_ptr() as *const u8,
231                bytes_to_write_to.as_mut_ptr().add(y_offset),
232                y_len,
233            );
234            std::ptr::copy_nonoverlapping(
235                z.as_ptr() as *const u8,
236                bytes_to_write_to.as_mut_ptr().add(z_offset),
237                z_len,
238            );
239            std::ptr::copy_nonoverlapping(
240                p.as_ptr() as *const u8,
241                bytes_to_write_to.as_mut_ptr().add(p_offset),
242                p_len,
243            );
244        }
245    }
246
247    #[cfg(not(target_endian = "little"))]
248    {
249        // Fallback for big-endian systems: use individual writes with endianness conversion
250        let mut x_off = x_offset;
251        let mut y_off = y_offset;
252        let mut z_off = z_offset;
253        let mut p_off = p_offset;
254
255        for i in 0..number_of_neurons_to_write {
256            LittleEndian::write_u32(&mut bytes_to_write_to[x_off..x_off + U32_F32_LENGTH], x[i]);
257            LittleEndian::write_u32(&mut bytes_to_write_to[y_off..y_off + U32_F32_LENGTH], y[i]);
258            LittleEndian::write_u32(&mut bytes_to_write_to[z_off..z_off + U32_F32_LENGTH], z[i]);
259            LittleEndian::write_f32(&mut bytes_to_write_to[p_off..p_off + U32_F32_LENGTH], p[i]);
260
261            x_off += U32_F32_LENGTH;
262            y_off += U32_F32_LENGTH;
263            z_off += U32_F32_LENGTH;
264            p_off += U32_F32_LENGTH;
265        }
266    }
267
268    Ok(())
269}