Skip to content

Commit

Permalink
Merge pull request #74 from jabibamman/develop
Browse files Browse the repository at this point in the history
Feat - Complete communication between client and reference server
  • Loading branch information
chikatetsu authored Jan 25, 2024
2 parents 17d111b + 164f61f commit 1479d0a
Show file tree
Hide file tree
Showing 19 changed files with 380 additions and 67 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

![GitHub Release](https://img.shields.io/github/v/release/jabibamman/frakt)
[![wakatime](https://wakatime.com/badge/github/jabibamman/frakt.svg)](https://wakatime.com/badge/github/jabibamman/frakt)
[![codecov](https://codecov.io/gh/jabibamman/frakt/branch/master/graph/badge.svg)](https://codecov.io/gh/jabibamman/frakt)
Expand All @@ -10,6 +9,8 @@

Frakt is a Rust-based project focused on the computation and visualization of fractals. This workspace includes several components, such as clients, servers, and shared libraries.

Our documentation is available [here](https://jabibamman.github.io/frakt/client).

## Installation

### Prerequisites
Expand Down
1 change: 1 addition & 0 deletions cli/src/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod operation_tests {
verbose: args.verbose.clone(),
debug: args.debug.clone(),
open: false,
save: false,
};

let address = format!("{}:{}", client_args.hostname, client_args.port);
Expand Down
5 changes: 5 additions & 0 deletions cli/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ pub struct CliClientArgs {
/// Default: false
#[clap(short = 'o', long = "open", default_value = "false")]
pub open: bool,

/// Optional: Add a flag to save the image to a file.
/// Default: false
#[clap(short = 's', long = "save", default_value = "false")]
pub save: bool,
}

/// Represents command line arguments for a server in a CLI application.
Expand Down
28 changes: 19 additions & 9 deletions client/src/fractal_generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ use shared::types::pixel_intensity::PixelIntensity;
/// * `fragment_task`: A `FragmentTask` containing details such as the fractal type, resolution, and range.
///
/// # Returns
/// Returns an `ImageBuffer` containing the generated Fractal.
/// Returns a tuple containing the generated image, the pixel data, and the pixel intensity matrice.
///
/// # Details
/// This function scales the coordinates based on the provided resolution and range, computes the number of
/// iterations for each pixel, and then maps these iterations to a color value.
pub fn generate_fractal_set(
fragment_task: FragmentTask,
) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, FractalError> {
) -> Result<(ImageBuffer<Rgb<u8>, Vec<u8>>, Vec<u8>, Vec<PixelIntensity>), FractalError> {
let descriptor = &fragment_task.fractal.fractal_type;
let descriptor: &dyn FractalOperations = match descriptor {
Julia(julia_descriptor) => julia_descriptor,
Expand All @@ -41,6 +41,11 @@ pub fn generate_fractal_set(

let mut img = ImageBuffer::new(resolution.nx.into(), resolution.ny.into());

let mut pixel_data_vec = Vec::new();

// crée une matrice de pixel_intensity
let mut pixel_matrice_intensity = Vec::new();

info!("Generating fractal set...");
for (x, y, pixel) in img.enumerate_pixels_mut() {
let scaled_x = x as f64 * scale_x + range.min.x;
Expand All @@ -50,9 +55,14 @@ pub fn generate_fractal_set(
let pixel_intensity =
descriptor.compute_pixel_intensity(&complex_point, fragment_task.max_iteration);
*pixel = Rgb(color(pixel_intensity));

pixel_matrice_intensity.push(pixel_intensity);
pixel_data_vec.push(pixel[0]);
pixel_data_vec.push(pixel[1]);
pixel_data_vec.push(pixel[2]);
}

Ok(img)
Ok((img, pixel_data_vec, pixel_matrice_intensity))
}

///Generates a color based on the provided pixel intensity.
Expand Down Expand Up @@ -142,9 +152,9 @@ mod julia_descriptor_tests {
},
};

let result = generate_fractal_set(fragment_task);

assert_eq!(result.expect("dimensions").dimensions(), (800, 600));
if let Ok((img, _, _)) = generate_fractal_set(fragment_task) {
assert_eq!(img.dimensions(), (800, 600));
}
}

#[test]
Expand Down Expand Up @@ -188,8 +198,8 @@ mod julia_descriptor_tests {
},
};

let result = generate_fractal_set(fragment_task);

assert_eq!(result.expect("dimensions").dimensions(), (800, 600));
if let Ok((img, _, _)) = generate_fractal_set(fragment_task) {
assert_eq!(img.dimensions(), (800, 600));
}
}
}
18 changes: 15 additions & 3 deletions client/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use log::{error, info};
use networking::{connect_to_server, process_fragment_task, receive_fragment_task, send_request};
use shared::{types::error::FractalError, utils::fragment_request_impl::FragmentRequestOperation};

Expand All @@ -17,10 +18,21 @@ fn main() -> Result<(), FractalError> {

let mut stream = connect_to_server(&cli_args)?;
send_request(&mut stream, &serialized_request)?;
let fragment_task = receive_fragment_task(&mut stream)?;

if let Some(task) = fragment_task {
process_fragment_task(task, cli_args.open)?;
loop {
match receive_fragment_task(&mut stream) {
Ok(Some((fragment_task, data))) => {
stream = process_fragment_task(fragment_task, data, &cli_args)?;
}
Ok(None) => {
info!("No more tasks to process");
break;
}
Err(e) => {
error!("Error receiving fragment task: {:?}", e);
break;
}
}
}

Ok(())
Expand Down
128 changes: 109 additions & 19 deletions client/src/networking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,20 @@ use std::{
use cli::parser::CliClientArgs;
use image::{ImageBuffer, Rgb};
use log::{debug, error, info};
use server::services::{reader::get_response, write::write};
use server::services::{
reader::get_response,
write::{write, write_img},
};
use shared::{
types::{error::FractalError, filesystem::FileExtension, messages::FragmentTask},
types::{
error::FractalError,
filesystem::FileExtension,
messages::{FragmentResult, FragmentTask},
pixel_data::PixelData,
},
utils::{
filesystem::{get_dir_path_buf, get_extension_str, get_file_path},
fragment_result_impl::FragmentResultOperation,
fragment_task_impl::FragmentTaskOperation,
},
};
Expand Down Expand Up @@ -64,13 +73,16 @@ pub fn send_request(stream: &mut TcpStream, serialized_request: &str) -> io::Res
///
/// # Return
///
/// * `Result<Option<FragmentTask>, FractalError>` - A `Result` containing an `Option<FragmentTask>` if the response was received successfully, or a `FractalError` if the response could not be received.
/// * `Result<Option<FragmentTask, Vec<u8>>>, FractalError>` - A `FragmentTask` if the response was successful, or a `FractalError` if the response failed.
///
pub fn receive_fragment_task(stream: &mut TcpStream) -> Result<Option<FragmentTask>, FractalError> {
let response = get_response(stream)?;
debug!("Received response: {}", response);
match FragmentTask::deserialize(&response) {
Ok(fract) => Ok(Some(fract)),
pub fn receive_fragment_task(
stream: &mut TcpStream,
) -> Result<Option<(FragmentTask, Vec<u8>)>, FractalError> {
let (fragment_task, data) = get_response(stream)?;
debug!("Received response: {}", fragment_task);
debug!("Received data in receive fragment task: {:?}", data);
match FragmentTask::deserialize(&fragment_task) {
Ok(task) => Ok(Some((task, data))),
Err(e) => {
error!("Deserialization error: {}", e);
Err(FractalError::TaskNotSet(format!(
Expand Down Expand Up @@ -107,29 +119,107 @@ pub fn save_fractal_image(
/// # Arguments
///
/// * `task` - A `FragmentTask` containing details such as the fractal type, resolution, and range.
/// * `data` - A `Vec<u8>` containing the image data.
/// * `open_after_save` - A boolean indicating whether the image should be opened after it is saved.
///
/// # Return
///
/// * `Result<(), FractalError>` - An `io::Error` if the image could not be saved.
///
/// # Details
///
/// This function generates a file path for the image, generates the fractal image, saves it to the filesystem, and opens it if `open_after_save` is true.
/// * `Result<(TcpStream), FractalError>` - A `TcpStream` if the connection was successful, or a `FractalError` if the connection failed.
///
pub fn process_fragment_task(
task: FragmentTask,
open_after_save: bool,
) -> Result<(), FractalError> {
data: Vec<u8>,
cli_args: &CliClientArgs,
) -> Result<TcpStream, FractalError> {
let dir_path_buf = get_dir_path_buf()?;

let img_path = get_file_path("julia", dir_path_buf, get_extension_str(FileExtension::PNG))?;
let img = generate_fractal_set(task)?;
save_fractal_image(img, &img_path)?;
let (img, pixel_data_bytes, pixel_intensity_matrice) = generate_fractal_set(task.clone())?;

debug!("Pixel data bytes: {:?}", pixel_data_bytes);

let pixel_data = convert_to_pixel_data(pixel_data_bytes.clone(), task.clone());

let mut vec_data = data.clone();

for i in 0..pixel_intensity_matrice.len() {
vec_data.extend(pixel_intensity_matrice[i].zn.to_be_bytes());
vec_data.extend(pixel_intensity_matrice[i].count.to_be_bytes());
}

if open_after_save {
debug!("Vec data: {:?}", vec_data);

if cli_args.save {
save_fractal_image(img.clone(), &img_path)?;
}

let stream = send_fragment_result(&task, cli_args, pixel_data, vec_data)?;

if cli_args.open {
open_image(&img_path)?;
}

Ok(())
Ok(stream)
}

/// Send a `FragmentResult` to the server after generating a fractal image.
///
/// # Arguments
///
/// * `img` - An `ImageBuffer` containing the fractal image.
/// * `fragment_task` - A `FragmentTask` containing details such as the fractal type, resolution, and range.
/// * `cli_args` - A `CliClientArgs` containing the command line arguments.
/// * `pixel_data` - A `PixelData` struct containing the image data.
/// * `data` - A `Vec<u8>` containing the image data.
///
/// # Return
///
/// * `Result<(), FractalError>` - An `io::Error` if the image could not be saved.
///
/// # Details
///
/// This function converts the `ImageBuffer` to a `Vec<u8>` and then to a `PixelData` struct.
///
fn send_fragment_result(
fragment_task: &FragmentTask,
cli_args: &CliClientArgs,
pixel_data: PixelData,
data: Vec<u8>,
) -> Result<TcpStream, FractalError> {
let fragment_result = FragmentResult::new(
fragment_task.id,
fragment_task.resolution,
fragment_task.range,
pixel_data,
);
let serialized = FragmentResult::serialize(&fragment_result)?;

let mut new_stream = connect_to_server(cli_args)?;
debug!("Sending fragment result: {}", serialized);
write_img(&mut new_stream, &serialized, data)?;

Ok(new_stream)
}

/// Convert a `Vec<u8>` to a `PixelData` struct.
///
/// # Arguments
///
/// * `data` - A `Vec<u8>` containing the image data.
///
/// # Return
///
/// * `PixelData` - A `PixelData` struct containing the image data.
///
/// # Details
///
/// This function converts the `Vec<u8>` to a `PixelData` struct.
fn convert_to_pixel_data(data: Vec<u8>, task: FragmentTask) -> PixelData {
let pixel_size = 3; // Pour RGB, 4 pour RGBA, etc.
let total_pixels = data.len() / pixel_size;

PixelData {
offset: task.id.count,
count: total_pixels as u32,
}
}
44 changes: 18 additions & 26 deletions server/src/services/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ use log::debug;
/// * `stream`: A mutable reference to a `TcpStream` from which the message will be read.
///
/// # Returns
/// An `io::Result<String>` which is `Ok` containing the JSON string if the read is successful,
/// or an `io::Error` if an error occurs during the read operation.
/// An `io::Result` containing a tuple of the JSON data as a `String` and the binary data as a `Vec<u8>`.
/// The binary data is empty if there's no additional data.
///
/// # Errors
/// Returns an `io::Error` if there's an issue with stream reading or if the JSON data
Expand All @@ -33,29 +33,22 @@ use log::debug;
/// fn main() -> io::Result<()> {
/// let mut stream = TcpStream::connect("localhost:8787").unwrap();
/// match read_message(&mut stream) {
/// Ok(json_msg) => println!("Received JSON: {}", json_msg),
/// Ok((json_str, data)) => println!("Message: {}, Data: {:?}", json_str, data),
/// Err(e) => eprintln!("Failed to read message: {}", e),
/// }
///
/// Ok(())
/// }
/// ```
pub fn read_message(stream: &mut TcpStream) -> io::Result<String> {
let mut size_buffer = [0u8; 8];
stream.read_exact(&mut size_buffer)?;
pub fn read_message(stream: &mut TcpStream) -> io::Result<(String, Vec<u8>)> {
let mut total_message = [0; 4];
let mut json_message = [0; 4];

let total_size = u32::from_be_bytes([
size_buffer[0],
size_buffer[1],
size_buffer[2],
size_buffer[3],
]) as usize;
let json_size = u32::from_be_bytes([
size_buffer[4],
size_buffer[5],
size_buffer[6],
size_buffer[7],
]) as usize;
stream.read_exact(&mut total_message)?;
stream.read_exact(&mut json_message)?;

let json_size = u32::from_be_bytes(json_message) as usize;
let total_size = u32::from_be_bytes(total_message) as usize;

let mut json_buffer = vec![0; json_size];
stream.read_exact(&mut json_buffer)?;
Expand All @@ -65,21 +58,20 @@ pub fn read_message(stream: &mut TcpStream) -> io::Result<String> {
Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
};

debug!("Réponse JSON du serveur: {}", json_str);
debug!("Réponse String message du serveur: {}", json_str);

// donnée supplémentaire en binaire
let binary_data_size = total_size - json_size;
let mut data = Vec::new();
if binary_data_size > 0 {
let mut binary_buffer = vec![0; binary_data_size];
stream.read_exact(&mut binary_buffer)?;
debug!("Données binaires reçues: {:?}", binary_buffer);
data = vec![0; binary_data_size];
stream.read_exact(&mut data)?;
}

Ok(json_str)
Ok((json_str, data))
}

pub fn get_response(stream: &mut TcpStream) -> io::Result<String> {
pub fn get_response(stream: &mut TcpStream) -> io::Result<(String, Vec<u8>)> {
stream.set_read_timeout(Some(Duration::new(5, 0)))?;
let response = read_message(stream)?;
Ok(response)
Ok(read_message(stream)?)
}
Loading

0 comments on commit 1479d0a

Please sign in to comment.