<commit_before>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Also licensed under MIT license, at your choice.

//! A simple compute shader example that draws into a window, based on wgpu.

use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Instant;

use rust_gpu_toy_shared::Config;
use wgpu::util::make_spirv;
use wgpu::{Extent3d, ShaderModule};

use winit::dpi::PhysicalSize;
use winit::{
    event::{Event, WindowEvent},
    event_loop::{ControlFlow, EventLoop},
    window::Window,
};

use spirv_builder::{CompileResult, SpirvBuilder};

struct Pipelines {
    compute_pipeline: wgpu::ComputePipeline,

    copy_pipeline: wgpu::RenderPipeline,
}

struct Layouts {
    compute_bg_layout: wgpu::BindGroupLayout,
    copy_bg_layout: wgpu::BindGroupLayout,

    compute_pipeline_layout: wgpu::PipelineLayout,
    copy_pipeline_layout: wgpu::PipelineLayout,
}

/// State of the runner, inspired by
/// https://sotrh.github.io/learn-wgpu/
struct State {
    pipelines: Pipelines,
    layouts: Layouts,

    surface: wgpu::Surface,
    device: wgpu::Device,
    queue: wgpu::Queue,

    sc_desc: wgpu::SwapChainDescriptor,
    swap_chain: wgpu::SwapChain,
    size: winit::dpi::PhysicalSize<u32>,

    sampler: wgpu::Sampler,

    compute_bind_group: wgpu::BindGroup,
    copy_bind_group: wgpu::BindGroup,

    start_time: Instant,
}

impl State {
    async fn new(window: &Window, compilation: CompileResult) -> Self {
        let size = window.inner_size();
        let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);
        let surface = unsafe { instance.create_surface(window) };
        let adapter = instance
            .request_adapter(&wgpu::RequestAdapterOptions {
                power_preference: Default::default(),
                compatible_surface: Some(&surface),
            })
            .await
            .expect("error finding adapter");
        let features = wgpu::Features::PUSH_CONSTANTS;
        let limits = wgpu::Limits {
            max_push_constant_size: std::mem::size_of::<Config>() as u32,
            ..Default::default()
        };
        let (device, queue) = adapter
            .request_device(
                &wgpu::DeviceDescriptor {
                    label: None,
                    features,
                    limits,
                },
                None,
            )
            .await
            .expect("error creating device");
        let swapchain_format = adapter.get_swap_chain_preferred_format(&surface).unwrap();
        let sc_desc = wgpu::SwapChainDescriptor {
            usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
            format: swapchain_format,
            width: size.width,
            height: size.height,
            present_mode: wgpu::PresentMode::Mailbox,
        };

        let swap_chain = device.create_swap_chain(&surface, &sc_desc);

        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            address_mode_w: wgpu::AddressMode::ClampToEdge,
            mag_filter: wgpu::FilterMode::Nearest,
            min_filter: wgpu::FilterMode::Nearest,
            mipmap_filter: wgpu::FilterMode::Nearest,
            ..Default::default()
        });
        let layouts = Self::create_layouts(&device);
        let pipelines = Self::create_pipelines(&device, &sc_desc, &layouts, &compilation);
        let (compute_group, copy_group) = Self::bind_for_size(&device, &sampler, size, &layouts);
        let start_time = Instant::now();
        State {
            pipelines,
            layouts,
            surface,
            device,
            queue,
            sc_desc,
            swap_chain,
            size,
            sampler,
            compute_bind_group: compute_group,
            copy_bind_group: copy_group,
            start_time,
        }
    }

    fn resize(&mut self, new_size: PhysicalSize<u32>) {
        self.sc_desc.width = new_size.width;
        self.sc_desc.height = new_size.height;
        self.size = new_size;
        let new_swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc);
        self.swap_chain = new_swap_chain;
        let (compute_bind_group, copy_bind_group) =
            State::bind_for_size(&self.device, &self.sampler, new_size, &self.layouts);
        self.compute_bind_group = compute_bind_group;
        self.copy_bind_group = copy_bind_group;
    }

    fn bind_for_size(
        device: &wgpu::Device,
        sampler: &wgpu::Sampler,
        size: PhysicalSize<u32>,
        layouts: &Layouts,
    ) -> (wgpu::BindGroup, wgpu::BindGroup) {
        let img = device.create_texture(&wgpu::TextureDescriptor {
            label: None,
            size: Extent3d {
                width: size.width,
                height: size.height,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Rgba8Unorm,
            usage: wgpu::TextureUsage::STORAGE | wgpu::TextureUsage::SAMPLED,
        });
        let img_view = img.create_view(&Default::default());
        let compute_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: None,
            layout: &layouts.compute_bg_layout,
            entries: &[wgpu::BindGroupEntry {
                binding: 0,
                resource: wgpu::BindingResource::TextureView(&img_view),
            }],
        });

        let copy_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: None,
            layout: &layouts.copy_bg_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&img_view),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::Sampler(&sampler),
                },
            ],
        });
        (compute_bind_group, copy_bind_group)
    }

    fn create_layouts(device: &wgpu::Device) -> Layouts {
        let copy_bg_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
            label: None,
            entries: &[
                wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStage::FRAGMENT,
                    ty: wgpu::BindingType::Texture {
                        multisampled: false,
                        // Should filterable be false if we want nearest-neighbor?
                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
                        view_dimension: wgpu::TextureViewDimension::D2,
                    },
                    count: None,
                },
                wgpu::BindGroupLayoutEntry {
                    binding: 1,
                    visibility: wgpu::ShaderStage::FRAGMENT,
                    ty: wgpu::BindingType::Sampler {
                        filtering: false,
                        comparison: false,
                    },
                    count: None,
                },
            ],
        });
        let copy_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: None,
            bind_group_layouts: &[&copy_bg_layout],
            push_constant_ranges: &[],
        });
        let compute_bg_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
            label: Some("Compute Bind Group Layout"),
            entries: &[wgpu::BindGroupLayoutEntry {
                binding: 0,
                visibility: wgpu::ShaderStage::COMPUTE,
                ty: wgpu::BindingType::StorageTexture {
                    access: wgpu::StorageTextureAccess::WriteOnly,
                    format: wgpu::TextureFormat::Rgba8Unorm,
                    view_dimension: wgpu::TextureViewDimension::D2,
                },
                count: None,
            }],
        });
        let compute_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: None,
                bind_group_layouts: &[&compute_bg_layout],
                push_constant_ranges: &[wgpu::PushConstantRange {
                    stages: wgpu::ShaderStage::COMPUTE,
                    range: 0..std::mem::size_of::<Config>() as u32,
                }],
            });
        Layouts {
            compute_bg_layout,
            copy_bg_layout,
            compute_pipeline_layout,
            copy_pipeline_layout,
        }
    }

    fn replace_pipelines(&mut self, compilation: &CompileResult) {
        let new_pipelines =
            State::create_pipelines(&self.device, &self.sc_desc, &self.layouts, compilation);
        self.pipelines = new_pipelines;
    }

    fn create_pipelines(
        device: &wgpu::Device,
        sc_desc: &wgpu::SwapChainDescriptor,
        layouts: &Layouts,
        compilation: &CompileResult,
    ) -> Pipelines {
        let module = State::create_shader_module(device, compilation);
        let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
            label: None,
            layout: Some(&layouts.compute_pipeline_layout),
            module: &module,
            entry_point: "main",
        });
        let copy_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: None,
            layout: Some(&layouts.copy_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &module,
                entry_point: "vs_main",
                buffers: &[],
            },
            fragment: Some(wgpu::FragmentState {
                module: &module,
                entry_point: "fs_main",
                targets: &[sc_desc.format.into()],
            }),
            primitive: wgpu::PrimitiveState::default(),
            depth_stencil: None,
            multisample: wgpu::MultisampleState::default(),
        });
        Pipelines {
            compute_pipeline,
            copy_pipeline,
        }
    }

    fn create_shader_module(device: &wgpu::Device, compilation: &CompileResult) -> ShaderModule {
        let shader_flags = wgpu::ShaderFlags::empty();
        let spirv_path = compilation.module.unwrap_single();
        let f = std::fs::read(spirv_path).expect("spirv should exist");
        let shader = make_spirv(&f);
        device.create_shader_module(&wgpu::ShaderModuleDescriptor {
            label: None,
            source: shader,
            flags: shader_flags,
        })
    }

    fn render_frame(&self) {
        let frame = self
            .swap_chain
            .get_current_frame()
            .expect("error getting frame from swap chain")
            .output;

        let i_time: f32 = 0.5 + self.start_time.elapsed().as_micros() as f32 * 1e-6;
        let size = self.size;
        let config = Config {
            width: size.width,
            height: size.height,
            time: i_time,
        };

        let mut encoder = self.device.create_command_encoder(&Default::default());
        {
            let mut cpass = encoder.begin_compute_pass(&Default::default());
            cpass.set_pipeline(&self.pipelines.compute_pipeline);
            cpass.set_push_constants(0, bytemuck::bytes_of(&config));
            cpass.set_bind_group(0, &self.compute_bind_group, &[]);
            cpass.dispatch(size.width / 16, size.height / 16, 1);
        }
        {
            let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                label: None,
                color_attachments: &[wgpu::RenderPassColorAttachment {
                    view: &frame.view,
                    resolve_target: None,
                    ops: wgpu::Operations {
                        load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
                        store: true,
                    },
                }],
                depth_stencil_attachment: None,
            });
            rpass.set_pipeline(&self.pipelines.copy_pipeline);
            rpass.set_bind_group(0, &self.copy_bind_group, &[]);
            rpass.draw(0..3, 0..2);
        }
        self.queue.submit(Some(encoder.finish()));
    }
}

async fn run(
    event_loop: EventLoop<CompileResult>,
    window: Window,
    initial_compilation: CompileResult,
) {
    let mut state = State::new(&window, initial_compilation).await;

    event_loop.run(move |event, _, control_flow| {
        // TODO: this may be excessive polling. It really should be synchronized with
        // swapchain presentation, but that's currently underbaked in wgpu.
        *control_flow = ControlFlow::Poll;
        match event {
            Event::RedrawRequested(_) => state.render_frame(),
            Event::MainEventsCleared => {
                window.request_redraw();
            }
            Event::WindowEvent {
                event: WindowEvent::CloseRequested,
                ..
            } => *control_flow = ControlFlow::Exit,
            Event::UserEvent(result) => state.replace_pipelines(&result),
            Event::WindowEvent { window_id, event } if window_id == window.id() => match event {
                WindowEvent::Resized(new_size) => {
                    state.resize(new_size);
                    window.request_redraw();
                }
                _ => (),
            },
            _ => (),
        }
    });
}

fn main() {
    let (initial_tx, initial_rx) = std::sync::mpsc::sync_channel(0);
    let event_loop = EventLoop::with_user_event();
    let proxy = event_loop.create_proxy();
    // This is going to be cleaned up significantly by https://github.com/EmbarkStudios/rust-gpu/pull/663
    let has_sent_first = AtomicBool::new(false);
    // Watch for changes on a background thread
    let thread = std::thread::spawn(|| {
        SpirvBuilder::new("./shaders", "spirv-unknown-vulkan1.2")
            .print_metadata(spirv_builder::MetadataPrintout::None)
            .capability(spirv_builder::Capability::StorageImageWriteWithoutFormat)
            .watch(move |result| {
                if let Ok(_) =
                    // TODO: This use of atomics is signic
                    has_sent_first.compare_exchange(
                        false,
                        true,
                        Ordering::SeqCst,
                        Ordering::SeqCst,
                    )
                {
                    initial_tx.send(result).unwrap();
                } else {
                    proxy
                        .send_event(result)
                        .expect("Event loop should still be running");
                }
            })
            .expect("Correctly setup")
    });
    std::mem::forget(thread);
    let initial = initial_rx
        .recv()
        .expect("Watching should get an initial shader");
    let window = Window::new(&event_loop).unwrap();
    pollster::block_on(run(event_loop, window, initial));
}
<commit_msg>Handle 0 sized swapchains correctly<commit_after>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Also licensed under MIT license, at your choice.

//! A simple compute shader example that draws into a window, based on wgpu.

use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Instant;

use rust_gpu_toy_shared::Config;
use wgpu::util::make_spirv;
use wgpu::{Extent3d, ShaderModule};

use winit::dpi::PhysicalSize;
use winit::{
    event::{Event, WindowEvent},
    event_loop::{ControlFlow, EventLoop},
    window::Window,
};

use spirv_builder::{CompileResult, SpirvBuilder};

struct Pipelines {
    compute_pipeline: wgpu::ComputePipeline,

    copy_pipeline: wgpu::RenderPipeline,
}

struct Layouts {
    compute_bg_layout: wgpu::BindGroupLayout,
    copy_bg_layout: wgpu::BindGroupLayout,

    compute_pipeline_layout: wgpu::PipelineLayout,
    copy_pipeline_layout: wgpu::PipelineLayout,
}

/// State of the runner, inspired by
/// https://sotrh.github.io/learn-wgpu/
struct State {
    pipelines: Pipelines,
    layouts: Layouts,

    surface: wgpu::Surface,
    device: wgpu::Device,
    queue: wgpu::Queue,

    sc_desc: wgpu::SwapChainDescriptor,
    swap_chain: Option<wgpu::SwapChain>,
    size: winit::dpi::PhysicalSize<u32>,

    sampler: wgpu::Sampler,

    compute_bind_group: wgpu::BindGroup,
    copy_bind_group: wgpu::BindGroup,

    start_time: Instant,
}

impl State {
    async fn new(window: &Window, compilation: CompileResult) -> Self {
        let size = window.inner_size();
        let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);
        let surface = unsafe { instance.create_surface(window) };
        let adapter = instance
            .request_adapter(&wgpu::RequestAdapterOptions {
                power_preference: Default::default(),
                compatible_surface: Some(&surface),
            })
            .await
            .expect("error finding adapter");
        let features = wgpu::Features::PUSH_CONSTANTS;
        let limits = wgpu::Limits {
            max_push_constant_size: std::mem::size_of::<Config>() as u32,
            ..Default::default()
        };
        let (device, queue) = adapter
            .request_device(
                &wgpu::DeviceDescriptor {
                    label: None,
                    features,
                    limits,
                },
                None,
            )
            .await
            .expect("error creating device");
        let swapchain_format = adapter.get_swap_chain_preferred_format(&surface).unwrap();
        let sc_desc = wgpu::SwapChainDescriptor {
            usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
            format: swapchain_format,
            width: size.width,
            height: size.height,
            present_mode: wgpu::PresentMode::Mailbox,
        };

        let swap_chain = device.create_swap_chain(&surface, &sc_desc);

        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            address_mode_w: wgpu::AddressMode::ClampToEdge,
            mag_filter: wgpu::FilterMode::Nearest,
            min_filter: wgpu::FilterMode::Nearest,
            mipmap_filter: wgpu::FilterMode::Nearest,
            ..Default::default()
        });
        let layouts = Self::create_layouts(&device);
        let pipelines = Self::create_pipelines(&device, &sc_desc, &layouts, &compilation);
        let (compute_group, copy_group) = Self::bind_for_size(&device, &sampler, size, &layouts);
        let start_time = Instant::now();
        State {
            pipelines,
            layouts,
            surface,
            device,
            queue,
            sc_desc,
            swap_chain: Some(swap_chain),
            size,
            sampler,
            compute_bind_group: compute_group,
            copy_bind_group: copy_group,
            start_time,
        }
    }

    fn resize(&mut self, new_size: PhysicalSize<u32>) {
        if new_size.width != 0 && new_size.height != 0 {
            self.sc_desc.width = new_size.width;
            self.sc_desc.height = new_size.height;
            self.size = new_size;
            let new_swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc);
            self.swap_chain = Some(new_swap_chain);
            let (compute_bind_group, copy_bind_group) =
                State::bind_for_size(&self.device, &self.sampler, new_size, &self.layouts);
            self.compute_bind_group = compute_bind_group;
            self.copy_bind_group = copy_bind_group;
        } else {
            self.swap_chain = None;
        }
    }

    fn bind_for_size(
        device: &wgpu::Device,
        sampler: &wgpu::Sampler,
        size: PhysicalSize<u32>,
        layouts: &Layouts,
    ) -> (wgpu::BindGroup, wgpu::BindGroup) {
        let img = device.create_texture(&wgpu::TextureDescriptor {
            label: None,
            size: Extent3d {
                width: size.width,
                height: size.height,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Rgba8Unorm,
            usage: wgpu::TextureUsage::STORAGE | wgpu::TextureUsage::SAMPLED,
        });
        let img_view = img.create_view(&Default::default());
        let compute_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: None,
            layout: &layouts.compute_bg_layout,
            entries: &[wgpu::BindGroupEntry {
                binding: 0,
                resource: wgpu::BindingResource::TextureView(&img_view),
            }],
        });

        let copy_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: None,
            layout: &layouts.copy_bg_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&img_view),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::Sampler(&sampler),
                },
            ],
        });
        (compute_bind_group, copy_bind_group)
    }

    fn create_layouts(device: &wgpu::Device) -> Layouts {
        let copy_bg_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
            label: None,
            entries: &[
                wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStage::FRAGMENT,
                    ty: wgpu::BindingType::Texture {
                        multisampled: false,
                        // Should filterable be false if we want nearest-neighbor?
                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
                        view_dimension: wgpu::TextureViewDimension::D2,
                    },
                    count: None,
                },
                wgpu::BindGroupLayoutEntry {
                    binding: 1,
                    visibility: wgpu::ShaderStage::FRAGMENT,
                    ty: wgpu::BindingType::Sampler {
                        filtering: false,
                        comparison: false,
                    },
                    count: None,
                },
            ],
        });
        let copy_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: None,
            bind_group_layouts: &[&copy_bg_layout],
            push_constant_ranges: &[],
        });
        let compute_bg_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
            label: Some("Compute Bind Group Layout"),
            entries: &[wgpu::BindGroupLayoutEntry {
                binding: 0,
                visibility: wgpu::ShaderStage::COMPUTE,
                ty: wgpu::BindingType::StorageTexture {
                    access: wgpu::StorageTextureAccess::WriteOnly,
                    format: wgpu::TextureFormat::Rgba8Unorm,
                    view_dimension: wgpu::TextureViewDimension::D2,
                },
                count: None,
            }],
        });
        let compute_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: None,
                bind_group_layouts: &[&compute_bg_layout],
                push_constant_ranges: &[wgpu::PushConstantRange {
                    stages: wgpu::ShaderStage::COMPUTE,
                    range: 0..std::mem::size_of::<Config>() as u32,
                }],
            });
        Layouts {
            compute_bg_layout,
            copy_bg_layout,
            compute_pipeline_layout,
            copy_pipeline_layout,
        }
    }

    fn replace_pipelines(&mut self, compilation: &CompileResult) {
        let new_pipelines =
            State::create_pipelines(&self.device, &self.sc_desc, &self.layouts, compilation);
        self.pipelines = new_pipelines;
    }

    fn create_pipelines(
        device: &wgpu::Device,
        sc_desc: &wgpu::SwapChainDescriptor,
        layouts: &Layouts,
        compilation: &CompileResult,
    ) -> Pipelines {
        let module = State::create_shader_module(device, compilation);
        let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
            label: None,
            layout: Some(&layouts.compute_pipeline_layout),
            module: &module,
            entry_point: "main",
        });
        let copy_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: None,
            layout: Some(&layouts.copy_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &module,
                entry_point: "vs_main",
                buffers: &[],
            },
            fragment: Some(wgpu::FragmentState {
                module: &module,
                entry_point: "fs_main",
                targets: &[sc_desc.format.into()],
            }),
            primitive: wgpu::PrimitiveState::default(),
            depth_stencil: None,
            multisample: wgpu::MultisampleState::default(),
        });
        Pipelines {
            compute_pipeline,
            copy_pipeline,
        }
    }

    fn create_shader_module(device: &wgpu::Device, compilation: &CompileResult) -> ShaderModule {
        let shader_flags = wgpu::ShaderFlags::empty();
        let spirv_path = compilation.module.unwrap_single();
        let f = std::fs::read(spirv_path).expect("spirv should exist");
        let shader = make_spirv(&f);
        device.create_shader_module(&wgpu::ShaderModuleDescriptor {
            label: None,
            source: shader,
            flags: shader_flags,
        })
    }

    fn render_frame(&self) {
        if let Some(chain) = &self.swap_chain {
            let frame = chain
                .get_current_frame()
                .expect("error getting frame from swap chain")
                .output;

            let i_time: f32 = 0.5 + self.start_time.elapsed().as_micros() as f32 * 1e-6;
            let size = self.size;
            let config = Config {
                width: size.width,
                height: size.height,
                time: i_time,
            };

            let mut encoder = self.device.create_command_encoder(&Default::default());
            {
                let mut cpass = encoder.begin_compute_pass(&Default::default());
                cpass.set_pipeline(&self.pipelines.compute_pipeline);
                cpass.set_push_constants(0, bytemuck::bytes_of(&config));
                cpass.set_bind_group(0, &self.compute_bind_group, &[]);
                cpass.dispatch(size.width / 16, size.height / 16, 1);
            }
            {
                let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                    label: None,
                    color_attachments: &[wgpu::RenderPassColorAttachment {
                        view: &frame.view,
                        resolve_target: None,
                        ops: wgpu::Operations {
                            load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
                            store: true,
                        },
                    }],
                    depth_stencil_attachment: None,
                });
                rpass.set_pipeline(&self.pipelines.copy_pipeline);
                rpass.set_bind_group(0, &self.copy_bind_group, &[]);
                rpass.draw(0..3, 0..2);
            }
            self.queue.submit(Some(encoder.finish()));
        }
    }
}

async fn run(
    event_loop: EventLoop<CompileResult>,
    window: Window,
    initial_compilation: CompileResult,
) {
    let mut state = State::new(&window, initial_compilation).await;

    event_loop.run(move |event, _, control_flow| {
        // TODO: this may be excessive polling. It really should be synchronized with
        // swapchain presentation, but that's currently underbaked in wgpu.
        *control_flow = ControlFlow::Poll;
        match event {
            Event::RedrawRequested(_) => state.render_frame(),
            Event::MainEventsCleared => {
                window.request_redraw();
            }
            Event::WindowEvent {
                event: WindowEvent::CloseRequested,
                ..
            } => *control_flow = ControlFlow::Exit,
            Event::UserEvent(result) => state.replace_pipelines(&result),
            Event::WindowEvent { window_id, event } if window_id == window.id() => match event {
                WindowEvent::Resized(new_size) => {
                    state.resize(new_size);
                    window.request_redraw();
                }
                _ => (),
            },
            _ => (),
        }
    });
}

fn main() {
    let (initial_tx, initial_rx) = std::sync::mpsc::sync_channel(0);
    let event_loop = EventLoop::with_user_event();
    let proxy = event_loop.create_proxy();
    // This is going to be cleaned up significantly by https://github.com/EmbarkStudios/rust-gpu/pull/663
    let has_sent_first = AtomicBool::new(false);
    // Watch for changes on a background thread
    let thread = std::thread::spawn(|| {
        SpirvBuilder::new("./shaders", "spirv-unknown-vulkan1.2")
            .print_metadata(spirv_builder::MetadataPrintout::None)
            .capability(spirv_builder::Capability::StorageImageWriteWithoutFormat)
            .watch(move |result| {
                if let Ok(_) =
                    // TODO: This use of atomics is signic
                    has_sent_first.compare_exchange(
                        false,
                        true,
                        Ordering::SeqCst,
                        Ordering::SeqCst,
                    )
                {
                    initial_tx.send(result).unwrap();
                } else {
                    proxy
                        .send_event(result)
                        .expect("Event loop should still be running");
                }
            })
            .expect("Correctly setup")
    });
    std::mem::forget(thread);
    let initial = initial_rx
        .recv()
        .expect("Watching should get an initial shader");
    let window = Window::new(&event_loop).unwrap();
    pollster::block_on(run(event_loop, window, initial));
}
<|endoftext|>


<commit_before>use codec::{
    eyre::Result, stencila_schema::Node, utils::vec_string, Codec, CodecTrait, DecodeOptions,
    EncodeOptions,
};

#[cfg(feature = "decode")]
mod decode;

#[cfg(feature = "decode")]
pub use decode::decode_fragment;

#[cfg(feature = "encode")]
mod encode;

#[cfg(feature = "encode")]
pub use encode::ToMd;

/// A codec for Markdown
pub struct MdCodec {}

impl CodecTrait for MdCodec {
    fn spec() -> Codec {
        Codec {
            formats: vec_string!["md"],
            root_types: vec_string!["Article"],
            from_string: cfg!(feature = "decode"),
            from_path: cfg!(feature = "decode"),
            to_string: cfg!(feature = "encode"),
            to_path: cfg!(feature = "encode"),
            unsupported_types: vec_string![
                // TODO: Fix handling of table headers
                "Table"
            ],
            ..Default::default()
        }
    }

    #[cfg(feature = "decode")]
    fn from_str(str: &str, _options: Option<DecodeOptions>) -> Result<Node> {
        decode::decode(str)
    }

    #[cfg(feature = "encode")]
    fn to_string(node: &Node, options: Option<EncodeOptions>) -> Result<String> {
        encode::encode(node, options)
    }
}
<commit_msg>chore(Markdown codec): Remove `unsupported_types`<commit_after>use codec::{
    eyre::Result, stencila_schema::Node, utils::vec_string, Codec, CodecTrait, DecodeOptions,
    EncodeOptions,
};

#[cfg(feature = "decode")]
mod decode;

#[cfg(feature = "decode")]
pub use decode::decode_fragment;

#[cfg(feature = "encode")]
mod encode;

#[cfg(feature = "encode")]
pub use encode::ToMd;

/// A codec for Markdown
pub struct MdCodec {}

impl CodecTrait for MdCodec {
    fn spec() -> Codec {
        Codec {
            formats: vec_string!["md"],
            root_types: vec_string!["Article"],
            from_string: cfg!(feature = "decode"),
            from_path: cfg!(feature = "decode"),
            to_string: cfg!(feature = "encode"),
            to_path: cfg!(feature = "encode"),
            ..Default::default()
        }
    }

    #[cfg(feature = "decode")]
    fn from_str(str: &str, _options: Option<DecodeOptions>) -> Result<Node> {
        decode::decode(str)
    }

    #[cfg(feature = "encode")]
    fn to_string(node: &Node, options: Option<EncodeOptions>) -> Result<String> {
        encode::encode(node, options)
    }
}
<|endoftext|>


<commit_before>//! A crate exposing the `pact` APIs to other languages
//! via a C Foreign Function Interface.

#![warn(missing_docs)]
#![warn(missing_debug_implementations)]
#![warn(missing_copy_implementations)]

use std::ffi::CStr;
use std::str::FromStr;

use ::log::warn;
use env_logger::Builder;
use libc::c_char;
use pact_models::interaction::Interaction;
use pact_models::pact::Pact;
use pact_models::v4::pact::V4Pact;

use models::message::Message;
use pact_matching as pm;
pub use pact_matching::Mismatch;

use crate::util::*;

pub mod error;
pub mod log;
pub mod models;
pub(crate) mod util;
pub mod mock_server;
pub mod verifier;
pub mod plugins;

const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\0");

/// Get the current library version
#[no_mangle]
pub extern "C" fn pactffi_version() -> *const c_char {
    VERSION.as_ptr() as *const c_char
}

/// Initialise the mock server library, can provide an environment variable name to use to
/// set the log levels.
///
/// # Safety
///
/// log_env_var must be a valid NULL terminated UTF-8 string.
#[no_mangle]
pub unsafe extern fn pactffi_init(log_env_var: *const c_char) {
    init_windows();

    let log_env_var = if !log_env_var.is_null() {
        let c_str = CStr::from_ptr(log_env_var);
        match c_str.to_str() {
            Ok(str) => str,
            Err(err) => {
                warn!("Failed to parse the environment variable name as a UTF-8 string: {}", err);
                "LOG_LEVEL"
            }
        }
    } else {
        "LOG_LEVEL"
    };

    let env = env_logger::Env::new().filter(log_env_var);
    let mut builder = Builder::from_env(env);
    builder.try_init().unwrap_or(());
}

#[cfg(windows)]
fn init_windows() {
    if let Err(err) = ansi_term::enable_ansi_support() {
        warn!("Could not enable ANSI console support - {err}");
    }
}

#[cfg(not(windows))]
fn init_windows() { }

/// Initialises logging, and sets the log level explicitly.
///
/// # Safety
///
/// Exported functions are inherently unsafe.
#[no_mangle]
pub unsafe extern "C" fn pactffi_init_with_log_level(level: *const c_char) {
    let mut builder = Builder::from_default_env();
    let log_level = log_level_from_c_char(level);

    builder.filter_level(log_level.to_level_filter());
    builder.try_init().unwrap_or(());
}

/// Log using the shared core logging facility.
///
/// This is useful for callers to have a single set of logs.
///
/// * `source` - String. The source of the log, such as the class or caller framework to
///                      disambiguate log lines from the rust logging (e.g. pact_go)
/// * `log_level` - String. One of TRACE, DEBUG, INFO, WARN, ERROR
/// * `message` - Message to log
///
/// # Safety
/// This function will fail if any of the pointers passed to it are invalid.
#[no_mangle]
pub unsafe extern "C" fn pactffi_log_message(source: *const c_char, log_level: *const c_char, message: *const c_char) {
    let target = convert_cstr("target", source).unwrap_or("client");

    if !message.is_null() {
        if let Some(message) = convert_cstr("message", message) {
            ::log::log!(target: target, log_level_from_c_char(log_level), "{}", message)
        }
    }
}

unsafe fn log_level_from_c_char(log_level: *const c_char) -> ::log::Level {
    if !log_level.is_null() {
        let level = convert_cstr("log_level", log_level).unwrap_or("INFO");
        ::log::Level::from_str(level).unwrap_or(::log::Level::Info)
    } else {
        ::log::Level::Info
    }
}

fn convert_cstr(name: &str, value: *const c_char) -> Option<&str> {
    unsafe {
        if value.is_null() {
            ::log::warn!("{} is NULL!", name);
            None
        } else {
            let c_str = CStr::from_ptr(value);
            match c_str.to_str() {
                Ok(str) => Some(str),
                Err(err) => {
                    ::log::warn!("Failed to parse {} name as a UTF-8 string: {}", name, err);
                    None
                }
            }
        }
    }
}

ffi_fn! {
    /// Match a pair of messages, producing a collection of mismatches,
    /// which is empty if the two messages matched.
    fn pactffi_match_message(msg_1: *const Message, msg_2: *const Message) -> *const Mismatches {
        let msg_1: Box<dyn Interaction + Send + Sync> = unsafe { Box::from_raw(msg_1 as *mut Message) };
        let msg_2: Box<dyn Interaction + Send + Sync> = unsafe { Box::from_raw(msg_2 as *mut Message) };

        let runtime = tokio::runtime::Runtime::new().unwrap();
        let mismatches = runtime.block_on(async move {
            // TODO: match_message also requires the Pact that the messages belong to
            Mismatches(pm::match_message(&msg_1, &msg_2, &V4Pact::default().boxed()).await)
        });

        ptr::raw_to(mismatches) as *const Mismatches
    } {
        ptr::null_to::<Mismatches>() as *const Mismatches
    }
}

ffi_fn! {
    /// Get an iterator over mismatches.
    fn pactffi_mismatches_get_iter(mismatches: *const Mismatches) -> *mut MismatchesIterator {
        let mismatches = as_ref!(mismatches);
        let iter = MismatchesIterator { current: 0, mismatches };
        ptr::raw_to(iter)
    } {
        ptr::null_mut_to::<MismatchesIterator>()
    }
}

ffi_fn! {
    /// Delete mismatches
    fn pactffi_mismatches_delete(mismatches: *const Mismatches) {
        ptr::drop_raw(mismatches as *mut Mismatches);
    }
}

ffi_fn! {
    /// Get the next mismatch from a mismatches iterator.
    ///
    /// Returns a null pointer if no mismatches remain.
    fn pactffi_mismatches_iter_next(iter: *mut MismatchesIterator) -> *const Mismatch {
        let iter = as_mut!(iter);
        let mismatches = as_ref!(iter.mismatches);
        let index = iter.next();
        let mismatch = mismatches
            .0
            .get(index)
            .ok_or(anyhow::anyhow!("iter past the end of mismatches"))?;
       mismatch as *const Mismatch
    } {
        ptr::null_to::<Mismatch>()
    }
}

ffi_fn! {
    /// Delete a mismatches iterator when you're done with it.
    fn pactffi_mismatches_iter_delete(iter: *mut MismatchesIterator) {
        ptr::drop_raw(iter);
    }
}

ffi_fn! {
    /// Get a JSON representation of the mismatch.
    fn pactffi_mismatch_to_json(mismatch: *const Mismatch) -> *const c_char {
        let mismatch = as_ref!(mismatch);
        let json = mismatch.to_json().to_string();
        string::to_c(&json)? as *const c_char
    } {
        ptr::null_to::<c_char>()
    }
}

ffi_fn! {
    /// Get the type of a mismatch.
    fn pactffi_mismatch_type(mismatch: *const Mismatch) -> *const c_char {
        let mismatch = as_ref!(mismatch);
        let t = mismatch.mismatch_type();
        string::to_c(t)? as *const c_char
    } {
        ptr::null_to::<c_char>()
    }
}

ffi_fn! {
    /// Get a summary of a mismatch.
    fn pactffi_mismatch_summary(mismatch: *const Mismatch) -> *const c_char {
        let mismatch = as_ref!(mismatch);
        let summary = mismatch.summary();
        string::to_c(&summary)? as *const c_char
    } {
        ptr::null_to::<c_char>()
    }
}

ffi_fn! {
    /// Get a description of a mismatch.
    fn pactffi_mismatch_description(mismatch: *const Mismatch) -> *const c_char {
        let mismatch = as_ref!(mismatch);
        let description = mismatch.description();
        string::to_c(&description)? as *const c_char
    } {
        ptr::null_to::<c_char>()
    }
}

ffi_fn! {
    /// Get an ANSI-compatible description of a mismatch.
    fn pactffi_mismatch_ansi_description(mismatch: *const Mismatch) -> *const c_char {
        let mismatch = as_ref!(mismatch);
        let ansi_description = mismatch.ansi_description();
        string::to_c(&ansi_description)? as *const c_char
    } {
        ptr::null_to::<c_char>()
    }
}

/// A collection of mismatches from a matching comparison.
#[allow(missing_copy_implementations)]
#[allow(missing_debug_implementations)]
pub struct Mismatches(Vec<Mismatch>);

/// An iterator over mismatches.
#[allow(missing_copy_implementations)]
#[allow(missing_debug_implementations)]
pub struct MismatchesIterator {
    current: usize,
    mismatches: *const Mismatches,
}

impl MismatchesIterator {
    fn next(&mut self) -> usize {
        let idx = self.current;
        self.current += 1;
        idx
    }
}
<commit_msg>feat(FFI): add an explicit function to enable ANSI terminal support on Windows<commit_after>//! A crate exposing the `pact` APIs to other languages
//! via a C Foreign Function Interface.

#![warn(missing_docs)]
#![warn(missing_debug_implementations)]
#![warn(missing_copy_implementations)]

use std::ffi::CStr;
use std::str::FromStr;

use ::log::warn;
use env_logger::Builder;
use libc::c_char;
use pact_models::interaction::Interaction;
use pact_models::pact::Pact;
use pact_models::v4::pact::V4Pact;

use models::message::Message;
use pact_matching as pm;
pub use pact_matching::Mismatch;

use crate::util::*;

pub mod error;
pub mod log;
pub mod models;
pub(crate) mod util;
pub mod mock_server;
pub mod verifier;
pub mod plugins;

const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\0");

/// Get the current library version
#[no_mangle]
pub extern "C" fn pactffi_version() -> *const c_char {
    VERSION.as_ptr() as *const c_char
}

/// Initialise the mock server library, can provide an environment variable name to use to
/// set the log levels.
///
/// # Safety
///
/// log_env_var must be a valid NULL terminated UTF-8 string.
#[no_mangle]
pub unsafe extern fn pactffi_init(log_env_var: *const c_char) {
    let log_env_var = if !log_env_var.is_null() {
        let c_str = CStr::from_ptr(log_env_var);
        match c_str.to_str() {
            Ok(str) => str,
            Err(err) => {
                warn!("Failed to parse the environment variable name as a UTF-8 string: {}", err);
                "LOG_LEVEL"
            }
        }
    } else {
        "LOG_LEVEL"
    };

    let env = env_logger::Env::new().filter(log_env_var);
    let mut builder = Builder::from_env(env);
    builder.try_init().unwrap_or(());
}

/// Initialises logging, and sets the log level explicitly.
///
/// # Safety
///
/// Exported functions are inherently unsafe.
#[no_mangle]
pub unsafe extern "C" fn pactffi_init_with_log_level(level: *const c_char) {
    let mut builder = Builder::from_default_env();
    let log_level = log_level_from_c_char(level);

    builder.filter_level(log_level.to_level_filter());
    builder.try_init().unwrap_or(());
}

/// Enable ANSI coloured output on Windows.
///
/// # Safety
///
/// This function is safe.
#[no_mangle]
#[cfg(windows)]
pub extern "C" fn enable_ansi_support() {
  if let Err(err) = ansi_term::enable_ansi_support() {
    warn!("Could not enable ANSI console support - {err}");
  }
}

/// Log using the shared core logging facility.
///
/// This is useful for callers to have a single set of logs.
///
/// * `source` - String. The source of the log, such as the class or caller framework to
///                      disambiguate log lines from the rust logging (e.g. pact_go)
/// * `log_level` - String. One of TRACE, DEBUG, INFO, WARN, ERROR
/// * `message` - Message to log
///
/// # Safety
/// This function will fail if any of the pointers passed to it are invalid.
#[no_mangle]
pub unsafe extern "C" fn pactffi_log_message(source: *const c_char, log_level: *const c_char, message: *const c_char) {
    let target = convert_cstr("target", source).unwrap_or("client");

    if !message.is_null() {
        if let Some(message) = convert_cstr("message", message) {
            ::log::log!(target: target, log_level_from_c_char(log_level), "{}", message)
        }
    }
}

unsafe fn log_level_from_c_char(log_level: *const c_char) -> ::log::Level {
    if !log_level.is_null() {
        let level = convert_cstr("log_level", log_level).unwrap_or("INFO");
        ::log::Level::from_str(level).unwrap_or(::log::Level::Info)
    } else {
        ::log::Level::Info
    }
}

fn convert_cstr(name: &str, value: *const c_char) -> Option<&str> {
    unsafe {
        if value.is_null() {
            ::log::warn!("{} is NULL!", name);
            None
        } else {
            let c_str = CStr::from_ptr(value);
            match c_str.to_str() {
                Ok(str) => Some(str),
                Err(err) => {
                    ::log::warn!("Failed to parse {} name as a UTF-8 string: {}", name, err);
                    None
                }
            }
        }
    }
}

ffi_fn! {
    /// Match a pair of messages, producing a collection of mismatches,
    /// which is empty if the two messages matched.
    fn pactffi_match_message(msg_1: *const Message, msg_2: *const Message) -> *const Mismatches {
        let msg_1: Box<dyn Interaction + Send + Sync> = unsafe { Box::from_raw(msg_1 as *mut Message) };
        let msg_2: Box<dyn Interaction + Send + Sync> = unsafe { Box::from_raw(msg_2 as *mut Message) };

        let runtime = tokio::runtime::Runtime::new().unwrap();
        let mismatches = runtime.block_on(async move {
            // TODO: match_message also requires the Pact that the messages belong to
            Mismatches(pm::match_message(&msg_1, &msg_2, &V4Pact::default().boxed()).await)
        });

        ptr::raw_to(mismatches) as *const Mismatches
    } {
        ptr::null_to::<Mismatches>() as *const Mismatches
    }
}

ffi_fn! {
    /// Get an iterator over mismatches.
    fn pactffi_mismatches_get_iter(mismatches: *const Mismatches) -> *mut MismatchesIterator {
        let mismatches = as_ref!(mismatches);
        let iter = MismatchesIterator { current: 0, mismatches };
        ptr::raw_to(iter)
    } {
        ptr::null_mut_to::<MismatchesIterator>()
    }
}

ffi_fn! {
    /// Delete mismatches
    fn pactffi_mismatches_delete(mismatches: *const Mismatches) {
        ptr::drop_raw(mismatches as *mut Mismatches);
    }
}

ffi_fn! {
    /// Get the next mismatch from a mismatches iterator.
    ///
    /// Returns a null pointer if no mismatches remain.
    fn pactffi_mismatches_iter_next(iter: *mut MismatchesIterator) -> *const Mismatch {
        let iter = as_mut!(iter);
        let mismatches = as_ref!(iter.mismatches);
        let index = iter.next();
        let mismatch = mismatches
            .0
            .get(index)
            .ok_or(anyhow::anyhow!("iter past the end of mismatches"))?;
       mismatch as *const Mismatch
    } {
        ptr::null_to::<Mismatch>()
    }
}

ffi_fn! {
    /// Delete a mismatches iterator when you're done with it.
    fn pactffi_mismatches_iter_delete(iter: *mut MismatchesIterator) {
        ptr::drop_raw(iter);
    }
}

ffi_fn! {
    /// Get a JSON representation of the mismatch.
    fn pactffi_mismatch_to_json(mismatch: *const Mismatch) -> *const c_char {
        let mismatch = as_ref!(mismatch);
        let json = mismatch.to_json().to_string();
        string::to_c(&json)? as *const c_char
    } {
        ptr::null_to::<c_char>()
    }
}

ffi_fn! {
    /// Get the type of a mismatch.
    fn pactffi_mismatch_type(mismatch: *const Mismatch) -> *const c_char {
        let mismatch = as_ref!(mismatch);
        let t = mismatch.mismatch_type();
        string::to_c(t)? as *const c_char
    } {
        ptr::null_to::<c_char>()
    }
}

ffi_fn! {
    /// Get a summary of a mismatch.
    fn pactffi_mismatch_summary(mismatch: *const Mismatch) -> *const c_char {
        let mismatch = as_ref!(mismatch);
        let summary = mismatch.summary();
        string::to_c(&summary)? as *const c_char
    } {
        ptr::null_to::<c_char>()
    }
}

ffi_fn! {
    /// Get a description of a mismatch.
    fn pactffi_mismatch_description(mismatch: *const Mismatch) -> *const c_char {
        let mismatch = as_ref!(mismatch);
        let description = mismatch.description();
        string::to_c(&description)? as *const c_char
    } {
        ptr::null_to::<c_char>()
    }
}

ffi_fn! {
    /// Get an ANSI-compatible description of a mismatch.
    fn pactffi_mismatch_ansi_description(mismatch: *const Mismatch) -> *const c_char {
        let mismatch = as_ref!(mismatch);
        let ansi_description = mismatch.ansi_description();
        string::to_c(&ansi_description)? as *const c_char
    } {
        ptr::null_to::<c_char>()
    }
}

/// A collection of mismatches from a matching comparison.
#[allow(missing_copy_implementations)]
#[allow(missing_debug_implementations)]
pub struct Mismatches(Vec<Mismatch>);

/// An iterator over mismatches.
#[allow(missing_copy_implementations)]
#[allow(missing_debug_implementations)]
pub struct MismatchesIterator {
    current: usize,
    mismatches: *const Mismatches,
}

impl MismatchesIterator {
    fn next(&mut self) -> usize {
        let idx = self.current;
        self.current += 1;
        idx
    }
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! # ICU collation support for rust
//!
//! This crate provides [collation](https://en.wikipedia.org/wiki/Unicode_collation_algorithm)
//! (locale-sensitive string ordering), based on the collation as implemented by the ICU library.
//! Specifically the functionality exposed through its C API, as available in the [header
//! `ucol.h`](https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/ucol_8h.html).
//!
//! The main type is [UCollator], which can be created using `UCollator::try_from` from a `&str`.
//!
//! A detailed discussion of collation is out of scope of source code documentation.  An interested
//! reader can check out the [collation documentation on the ICU user
//! guide](http://userguide.icu-project.org/collation).
//!
//! > Are you missing some features from this crate?  Consider [reporting an
//! issue](https://github.com/google/rust_icu/issues) or even [contributing the
//! functionality](https://github.com/google/rust_icu/pulls).
//!
//! ## Examples
//!
//! Some example code for the use of collation is given below.
//!
//! First off, the more low-level API, which uses [ustring::UChar] is the following, which requires
//! a conversion to [ustring::UChar] prior to use.  This function is mostly used in algorithms that
//! compose Unicode functionality.
//!
//! ```
//! use rust_icu_ustring as ustring;
//! use rust_icu_ucol as ucol;
//! use std::convert::TryFrom;
//! let collator = ucol::UCollator::try_from("sr-Latn").expect("collator");
//! let mut mixed_up = vec!["d", "dž", "đ", "a", "b", "c", "č", "ć"];
//! mixed_up.sort_by(|a, b| {
//!    let first = ustring::UChar::try_from(*a).expect("first");
//!    let second = ustring::UChar::try_from(*b).expect("second");
//!    collator.strcoll(&first, &second)
//! });
//! let alphabet = vec!["a", "b", "c", "č", "ć", "d", "dž", "đ"];
//! assert_eq!(alphabet, mixed_up);
//! ```
//! A more rustful API is [UCollator::strcoll_utf8] which can operate on rust `AsRef<str>` and can
//! be used without converting the input data ahead of time.
//!
//! ```
//! use rust_icu_ustring as ustring;
//! use rust_icu_ucol as ucol;
//! use std::convert::TryFrom;
//! let collator = ucol::UCollator::try_from("sr-Latn").expect("collator");
//! let mut mixed_up = vec!["d", "dž", "đ", "a", "b", "c", "č", "ć"];
//! mixed_up.sort_by(|a, b| collator.strcoll_utf8(a, b).expect("strcoll_utf8"));
//! let alphabet = vec!["a", "b", "c", "č", "ć", "d", "dž", "đ"];
//! assert_eq!(alphabet, mixed_up);
//! ```
use {
    rust_icu_common as common,
    rust_icu_common::generalized_fallible_getter,
    rust_icu_common::generalized_fallible_setter,
    rust_icu_common::simple_drop_impl,
    rust_icu_sys as sys,
    rust_icu_sys::versioned_function,
    rust_icu_sys::*,
    rust_icu_uenum as uenum, rust_icu_ustring as ustring,
    std::{cmp::Ordering, convert::TryFrom, ffi, ptr},
};

#[derive(Debug)]
pub struct UCollator {
    rep: ptr::NonNull<sys::UCollator>,
}

// Implements `ucol_close`
simple_drop_impl!(UCollator, ucol_close);

impl TryFrom<&str> for UCollator {
    type Error = common::Error;
    /// Makes a new collator from the supplied locale, e.g. `en-US`, or
    /// `de@collation=phonebook`.
    ///
    /// Other examples:
    ///
    /// * `el-u-kf-upper`
    /// * `el@colCaseFirst=upper`
    ///
    /// Implements ucol_open
    fn try_from(locale: &str) -> Result<UCollator, Self::Error> {
        let locale_cstr = ffi::CString::new(locale)?;
        let mut status = common::Error::OK_CODE;
        // Unsafety note: this is the way to create the collator.  We expect all
        // the passed-in values to be well-formed.
        let rep = unsafe {
            assert!(common::Error::is_ok(status));
            versioned_function!(ucol_open)(locale_cstr.as_ptr(), &mut status) as *mut sys::UCollator
        };
        common::Error::ok_or_warning(status)?;
        Ok(UCollator {
            rep: ptr::NonNull::new(rep).unwrap(),
        })
    }
}

impl UCollator {
    /// Compares strings `first` and `second` according to the collation rules in this collator.
    ///
    /// Returns [Ordering::Less] if `first` compares as less than `second`, and for other return
    /// codes respectively.
    ///
    /// Implements `ucol_strcoll`
    pub fn strcoll(&self, first: &ustring::UChar, second: &ustring::UChar) -> Ordering {
        let result = unsafe {
            assert!(first.len() <= std::i32::MAX as usize);
            assert!(second.len() <= std::i32::MAX as usize);
            versioned_function!(ucol_strcoll)(
                self.rep.as_ptr(),
                first.as_c_ptr(),
                first.len() as i32,
                second.as_c_ptr(),
                second.len() as i32,
            )
        };
        UCollator::to_rust_ordering(result)
    }

    /// Compares strings `first` and `second` according to the collation rules in this collator.
    ///
    /// Returns [Ordering::Less] if `first` compares as less than `second`, and for other return
    /// codes respectively.
    ///
    /// In contrast to [UCollator::strcoll], this function requires no string conversions to
    /// compare two rust strings.
    ///
    /// Implements `ucol_strcoll`
    /// Implements `ucol_strcollUTF8`
    pub fn strcoll_utf8(
        &self,
        first: impl AsRef<str>,
        second: impl AsRef<str>,
    ) -> Result<Ordering, common::Error> {
        let mut status = common::Error::OK_CODE;
        // Unsafety note:
        // - AsRef is always well formed UTF-8 in rust.
        let result = unsafe {
            assert!(first.as_ref().len() <= std::i32::MAX as usize);
            assert!(second.as_ref().len() <= std::i32::MAX as usize);
            versioned_function!(ucol_strcollUTF8)(
                self.rep.as_ptr(),
                first.as_ref().as_ptr() as *const ::std::os::raw::c_char,
                first.as_ref().len() as i32,
                second.as_ref().as_ptr() as *const ::std::os::raw::c_char,
                second.as_ref().len() as i32,
                &mut status,
            )
        };
        common::Error::ok_or_warning(status)?;
        Ok(UCollator::to_rust_ordering(result))
    }

    // Converts ICU ordering result type to a Rust ordering result type.
    fn to_rust_ordering(result: sys::UCollationResult) -> Ordering {
        match result {
            sys::UCollationResult::UCOL_LESS => Ordering::Less,
            sys::UCollationResult::UCOL_GREATER => Ordering::Greater,
            sys::UCollationResult::UCOL_EQUAL => Ordering::Equal,
        }
    }

    /// Implements `ucol_getStrength`.
    pub fn get_strength(&self) -> sys::UCollationStrength {
        let result = unsafe { versioned_function!(ucol_getStrength)(self.rep.as_ptr()) };
        result
    }

    /// Implements `ucol_setStrength`
    pub fn set_strength(&mut self, strength: sys::UCollationStrength) {
        unsafe { versioned_function!(ucol_setStrength)(self.rep.as_ptr(), strength) };
    }

    // Implement `ucol_setAttribute`
    generalized_fallible_setter!(
        set_attribute,
        ucol_setAttribute,
        [attr: sys::UColAttribute, value: sys::UColAttributeValue,]
    );

    // Implement `ucol_getAttribute`
    generalized_fallible_getter!(
        get_attribute,
        ucol_getAttribute,
        [attr: sys::UColAttribute,],
        sys::UColAttributeValue
    );
}

/// Creates an enumeration of all available locales supporting collation.
///
/// Implements `ucol_openAvailableLocales`
/// Implements `ucol_countAvailable`
/// Implements `ucol_getAvailable`
pub fn get_available_locales() -> Result<uenum::Enumeration, common::Error> {
    let mut status = common::Error::OK_CODE;
    let rep = unsafe {
        assert!(common::Error::is_ok(status));
        versioned_function!(ucol_openAvailableLocales)(&mut status)
    };
    common::Error::ok_or_warning(status)?;
    let result = unsafe { uenum::Enumeration::from_raw_parts(None, rep) };
    Ok(result)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn basic() {
        let _ = crate::UCollator::try_from("de@collation=phonebook").expect("collator created");
    }

    #[test]
    fn test_available() {
        let available = crate::get_available_locales()
            .expect("available")
            .filter(|e| e.is_ok() /* retain known good */)
            .map(|e| e.unwrap() /* known good */)
            .collect::<Vec<String>>();
        assert_ne!(0, available.iter().count());
    }

    #[test]
    fn strcoll_utf8_test() -> Result<(), common::Error> {
        let collator = crate::UCollator::try_from("sr-Latn")?;
        let mut mixed_up = vec!["d", "dž", "đ", "a", "b", "c", "č", "ć"];
        mixed_up.sort_by(|a, b| collator.strcoll_utf8(a, b).expect("strcoll_utf8"));

        let alphabet = vec!["a", "b", "c", "č", "ć", "d", "dž", "đ"];
        assert_eq!(alphabet, mixed_up);
        Ok(())
    }

    #[test]
    fn strcoll_test() -> Result<(), common::Error> {
        let collator = crate::UCollator::try_from("sr-Latn")?;
        let mut mixed_up = vec!["d", "dž", "đ", "a", "b", "c", "č", "ć"];
        mixed_up.sort_by(|a, b| {
            let first = ustring::UChar::try_from(*a).expect("first");
            let second = ustring::UChar::try_from(*b).expect("second");
            collator.strcoll(&first, &second)
        });

        let alphabet = vec!["a", "b", "c", "č", "ć", "d", "dž", "đ"];
        assert_eq!(alphabet, mixed_up);
        Ok(())
    }

    #[test]
    fn attribute_setter() {
        let collator = crate::UCollator::try_from("sr-Latn").unwrap();
        collator
            .set_attribute(
                sys::UColAttribute::UCOL_CASE_FIRST,
                sys::UColAttributeValue::UCOL_OFF,
            )
            .unwrap();
        let attr = collator
            .get_attribute(sys::UColAttribute::UCOL_CASE_FIRST)
            .unwrap();
        assert_eq!(sys::UColAttributeValue::UCOL_OFF, attr);
        collator
            .set_attribute(
                sys::UColAttribute::UCOL_CASE_FIRST,
                sys::UColAttributeValue::UCOL_LOWER_FIRST,
            )
            .unwrap();
        let attr = collator
            .get_attribute(sys::UColAttribute::UCOL_CASE_FIRST)
            .unwrap();
        assert_eq!(sys::UColAttributeValue::UCOL_LOWER_FIRST, attr);
    }
}
<commit_msg>collation: add sort key support<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! # ICU collation support for rust
//!
//! This crate provides [collation](https://en.wikipedia.org/wiki/Unicode_collation_algorithm)
//! (locale-sensitive string ordering), based on the collation as implemented by the ICU library.
//! Specifically the functionality exposed through its C API, as available in the [header
//! `ucol.h`](https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/ucol_8h.html).
//!
//! The main type is [UCollator], which can be created using `UCollator::try_from` from a `&str`.
//!
//! A detailed discussion of collation is out of scope of source code documentation.  An interested
//! reader can check out the [collation documentation on the ICU user
//! guide](http://userguide.icu-project.org/collation).
//!
//! > Are you missing some features from this crate?  Consider [reporting an
//! issue](https://github.com/google/rust_icu/issues) or even [contributing the
//! functionality](https://github.com/google/rust_icu/pulls).
//!
//! ## Examples
//!
//! Some example code for the use of collation is given below.
//!
//! First off, the more low-level API, which uses [ustring::UChar] is the following, which requires
//! a conversion to [ustring::UChar] prior to use.  This function is mostly used in algorithms that
//! compose Unicode functionality.
//!
//! ```
//! use rust_icu_ustring as ustring;
//! use rust_icu_ucol as ucol;
//! use std::convert::TryFrom;
//! let collator = ucol::UCollator::try_from("sr-Latn").expect("collator");
//! let mut mixed_up = vec!["d", "dž", "đ", "a", "b", "c", "č", "ć"];
//! mixed_up.sort_by(|a, b| {
//!    let first = ustring::UChar::try_from(*a).expect("first");
//!    let second = ustring::UChar::try_from(*b).expect("second");
//!    collator.strcoll(&first, &second)
//! });
//! let alphabet = vec!["a", "b", "c", "č", "ć", "d", "dž", "đ"];
//! assert_eq!(alphabet, mixed_up);
//! ```
//! A more rustful API is [UCollator::strcoll_utf8] which can operate on rust `AsRef<str>` and can
//! be used without converting the input data ahead of time.
//!
//! ```
//! use rust_icu_ustring as ustring;
//! use rust_icu_ucol as ucol;
//! use std::convert::TryFrom;
//! let collator = ucol::UCollator::try_from("sr-Latn").expect("collator");
//! let mut mixed_up = vec!["d", "dž", "đ", "a", "b", "c", "č", "ć"];
//! mixed_up.sort_by(|a, b| collator.strcoll_utf8(a, b).expect("strcoll_utf8"));
//! let alphabet = vec!["a", "b", "c", "č", "ć", "d", "dž", "đ"];
//! assert_eq!(alphabet, mixed_up);
//! ```
use {
    rust_icu_common as common,
    rust_icu_common::generalized_fallible_getter,
    rust_icu_common::generalized_fallible_setter,
    rust_icu_common::simple_drop_impl,
    rust_icu_sys as sys,
    rust_icu_sys::versioned_function,
    rust_icu_sys::*,
    rust_icu_uenum as uenum, rust_icu_ustring as ustring,
    std::{cmp::Ordering, convert::TryFrom, ffi, ptr},
};

#[derive(Debug)]
pub struct UCollator {
    rep: ptr::NonNull<sys::UCollator>,
}

// Implements `ucol_close`
simple_drop_impl!(UCollator, ucol_close);

impl TryFrom<&str> for UCollator {
    type Error = common::Error;
    /// Makes a new collator from the supplied locale, e.g. `en-US`, or
    /// `de@collation=phonebook`.
    ///
    /// Other examples:
    ///
    /// * `el-u-kf-upper`
    /// * `el@colCaseFirst=upper`
    ///
    /// Implements ucol_open
    fn try_from(locale: &str) -> Result<UCollator, Self::Error> {
        let locale_cstr = ffi::CString::new(locale)?;
        let mut status = common::Error::OK_CODE;
        // Unsafety note: this is the way to create the collator.  We expect all
        // the passed-in values to be well-formed.
        let rep = unsafe {
            assert!(common::Error::is_ok(status));
            versioned_function!(ucol_open)(locale_cstr.as_ptr(), &mut status) as *mut sys::UCollator
        };
        common::Error::ok_or_warning(status)?;
        Ok(UCollator {
            rep: ptr::NonNull::new(rep).unwrap(),
        })
    }
}

impl UCollator {
    /// Compares strings `first` and `second` according to the collation rules in this collator.
    ///
    /// Returns [Ordering::Less] if `first` compares as less than `second`, and for other return
    /// codes respectively.
    ///
    /// Implements `ucol_strcoll`
    pub fn strcoll(&self, first: &ustring::UChar, second: &ustring::UChar) -> Ordering {
        let result = unsafe {
            assert!(first.len() <= std::i32::MAX as usize);
            assert!(second.len() <= std::i32::MAX as usize);
            versioned_function!(ucol_strcoll)(
                self.rep.as_ptr(),
                first.as_c_ptr(),
                first.len() as i32,
                second.as_c_ptr(),
                second.len() as i32,
            )
        };
        UCollator::to_rust_ordering(result)
    }

    /// Get a sort key for a string from this collator.
    ///
    /// Returns a sort key.
    ///
    /// Implements `ucol_getSortKey`
    pub fn get_sort_key(&self, source: &ustring::UChar) -> Vec<u8> {
        // Preflight to see how long the buffer should be.
        let result_length: i32 = unsafe {
            versioned_function!(ucol_getSortKey)(
                self.rep.as_ptr(),
                source.as_c_ptr(),
                source.len() as i32,
                std::ptr::null_mut(),
                0,
            )
        };
        let mut result: Vec<u8> = vec![0; result_length as usize];
        unsafe {
            versioned_function!(ucol_getSortKey)(
                self.rep.as_ptr(),
                source.as_c_ptr(),
                source.len() as i32,
                result.as_mut_ptr(),
                result.len() as i32,
            )
        };
        result
    }

    /// Compares strings `first` and `second` according to the collation rules in this collator.
    ///
    /// Returns [Ordering::Less] if `first` compares as less than `second`, and for other return
    /// codes respectively.
    ///
    /// In contrast to [UCollator::strcoll], this function requires no string conversions to
    /// compare two rust strings.
    ///
    /// Implements `ucol_strcoll`
    /// Implements `ucol_strcollUTF8`
    pub fn strcoll_utf8(
        &self,
        first: impl AsRef<str>,
        second: impl AsRef<str>,
    ) -> Result<Ordering, common::Error> {
        let mut status = common::Error::OK_CODE;
        // Unsafety note:
        // - AsRef is always well formed UTF-8 in rust.
        let result = unsafe {
            assert!(first.as_ref().len() <= std::i32::MAX as usize);
            assert!(second.as_ref().len() <= std::i32::MAX as usize);
            versioned_function!(ucol_strcollUTF8)(
                self.rep.as_ptr(),
                first.as_ref().as_ptr() as *const ::std::os::raw::c_char,
                first.as_ref().len() as i32,
                second.as_ref().as_ptr() as *const ::std::os::raw::c_char,
                second.as_ref().len() as i32,
                &mut status,
            )
        };
        common::Error::ok_or_warning(status)?;
        Ok(UCollator::to_rust_ordering(result))
    }

    // Converts ICU ordering result type to a Rust ordering result type.
    fn to_rust_ordering(result: sys::UCollationResult) -> Ordering {
        match result {
            sys::UCollationResult::UCOL_LESS => Ordering::Less,
            sys::UCollationResult::UCOL_GREATER => Ordering::Greater,
            sys::UCollationResult::UCOL_EQUAL => Ordering::Equal,
        }
    }

    /// Implements `ucol_getStrength`.
    pub fn get_strength(&self) -> sys::UCollationStrength {
        let result = unsafe { versioned_function!(ucol_getStrength)(self.rep.as_ptr()) };
        result
    }

    /// Implements `ucol_setStrength`
    pub fn set_strength(&mut self, strength: sys::UCollationStrength) {
        unsafe { versioned_function!(ucol_setStrength)(self.rep.as_ptr(), strength) };
    }

    // Implement `ucol_setAttribute`
    generalized_fallible_setter!(
        set_attribute,
        ucol_setAttribute,
        [attr: sys::UColAttribute, value: sys::UColAttributeValue,]
    );

    // Implement `ucol_getAttribute`
    generalized_fallible_getter!(
        get_attribute,
        ucol_getAttribute,
        [attr: sys::UColAttribute,],
        sys::UColAttributeValue
    );
}

/// Creates an enumeration of all available locales supporting collation.
///
/// Implements `ucol_openAvailableLocales`
/// Implements `ucol_countAvailable`
/// Implements `ucol_getAvailable`
pub fn get_available_locales() -> Result<uenum::Enumeration, common::Error> {
    let mut status = common::Error::OK_CODE;
    let rep = unsafe {
        assert!(common::Error::is_ok(status));
        versioned_function!(ucol_openAvailableLocales)(&mut status)
    };
    common::Error::ok_or_warning(status)?;
    let result = unsafe { uenum::Enumeration::from_raw_parts(None, rep) };
    Ok(result)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn basic() {
        let _ = crate::UCollator::try_from("de@collation=phonebook").expect("collator created");
    }

    #[test]
    fn test_available() {
        let available = crate::get_available_locales()
            .expect("available")
            .filter(|e| e.is_ok() /* retain known good */)
            .map(|e| e.unwrap() /* known good */)
            .collect::<Vec<String>>();
        assert_ne!(0, available.iter().count());
    }

    #[test]
    fn strcoll_utf8_test() -> Result<(), common::Error> {
        let collator = crate::UCollator::try_from("sr-Latn")?;
        let mut mixed_up = vec!["d", "dž", "đ", "a", "b", "c", "č", "ć"];
        mixed_up.sort_by(|a, b| collator.strcoll_utf8(a, b).expect("strcoll_utf8"));

        let alphabet = vec!["a", "b", "c", "č", "ć", "d", "dž", "đ"];
        assert_eq!(alphabet, mixed_up);
        Ok(())
    }

    #[test]
    fn strcoll_test() -> Result<(), common::Error> {
        let collator = crate::UCollator::try_from("sr-Latn")?;
        let mut mixed_up = vec!["d", "dž", "đ", "a", "b", "c", "č", "ć"];
        mixed_up.sort_by(|a, b| {
            let first = ustring::UChar::try_from(*a).expect("first");
            let second = ustring::UChar::try_from(*b).expect("second");
            collator.strcoll(&first, &second)
        });

        let alphabet = vec!["a", "b", "c", "č", "ć", "d", "dž", "đ"];
        assert_eq!(alphabet, mixed_up);
        Ok(())
    }

    #[test]
    fn get_sort_key_test() -> Result<(), common::Error> {
        let collator = crate::UCollator::try_from("sr-Latn")?;
        let mut mixed_up = vec!["d", "dž", "đ", "a", "b", "c", "č", "ć"];
        mixed_up.sort_by(|a, b| {
            let first = ustring::UChar::try_from(*a).expect("first");
            let second = ustring::UChar::try_from(*b).expect("second");
            let first_key = collator.get_sort_key(&first);
            let second_key = collator.get_sort_key(&second);
            first_key.cmp(&second_key)
        });

        let alphabet = vec!["a", "b", "c", "č", "ć", "d", "dž", "đ"];
        assert_eq!(alphabet, mixed_up);
        Ok(())
    }

    #[test]
    fn attribute_setter() {
        let collator = crate::UCollator::try_from("sr-Latn").unwrap();
        collator
            .set_attribute(
                sys::UColAttribute::UCOL_CASE_FIRST,
                sys::UColAttributeValue::UCOL_OFF,
            )
            .unwrap();
        let attr = collator
            .get_attribute(sys::UColAttribute::UCOL_CASE_FIRST)
            .unwrap();
        assert_eq!(sys::UColAttributeValue::UCOL_OFF, attr);
        collator
            .set_attribute(
                sys::UColAttribute::UCOL_CASE_FIRST,
                sys::UColAttributeValue::UCOL_LOWER_FIRST,
            )
            .unwrap();
        let attr = collator
            .get_attribute(sys::UColAttribute::UCOL_CASE_FIRST)
            .unwrap();
        assert_eq!(sys::UColAttributeValue::UCOL_LOWER_FIRST, attr);
    }
}
<|endoftext|>


<commit_before>//! A struct that represents an endpoint and related data with it.
//!

extern crate config;

use std::collections::{HashMap, HashSet};
use std::rc::{Rc};

use error::{PathfinderError};

use self::config::{Config, Value};

/// Default AMQP exchange point for requests
pub const REQUEST_EXCHANGE: &'static str = "open-matchmaking.direct";
/// Default AMQP exchange point for responses
pub const RESPONSE_EXCHANGE: &'static str = "open-matchmaking.responses.direct";

/// A struct which stores an original URL that must be converted to the
/// certain microservice endpoint.
///
/// # Example
/// ```
/// use engine::router::{Endpoint};
///
/// let endpoint = Endpoint::new(&"/api/matchmaking/search/", &"matchmaking.search");
/// assert_eq!(endpoint.get_url(), String::from("/api/matchmaking/search/"));
/// assert_eq!(endpoint.get_microservice(), String::from("matchmaking.search"));
/// ```
///
#[derive(Debug, Clone)]
pub struct Endpoint {
    url: String,
    microservice: String,
    request_exchange: String,
    response_exchange: String,
}

impl Endpoint {
    /// Returns a new instance of `Endpoint`.
    pub fn new(url: &str, microservice: &str, request_exchange: &str, response_exchange: &str) -> Endpoint {
        Endpoint {
            url: url.to_string(),
            microservice: microservice.to_string(),
            request_exchange: request_exchange.to_string(),
            response_exchange: response_exchange.to_string()
        }
    }

    /// Returns an original URL for which necessary to do a transformation.
    pub fn get_url(&self) -> String {
        self.url.clone()
    }

    /// Returns a microservice name.
    pub fn get_microservice(&self) -> String {
        self.microservice.clone()
    }

    /// Returns a request exchange point name.
    pub fn get_request_exchange(&self) -> String {
        self.request_exchange.clone()
    }

    /// Returns a response exchange point name.
    pub fn get_response_exchange(&self) -> String {
        self.response_exchange.clone()
    }
}

/// Extract a value configuration object as a string if it exists. Otherwise returns an default value as a string.
fn get_value_from_config_as_str(conf: &HashMap<String, Value>, key: &str, default: &str) -> String {
    match conf.get(key) {
        Some(value) => value.to_owned().into_str().unwrap(),
        None => String::from(default)
    }
}

/// Returns a HashMap with mapping for URL onto certain queue/topic name that
/// were extracted from a configuration.
pub fn extract_endpoints(conf: Box<Config>) -> HashMap<String, Rc<Box<Endpoint>>> {
    let mut endpoints = HashMap::new();

    let config_endpoints: Vec<Value> = match conf.get_array("endpoints") {
        Ok(array) => array,
        Err(_) => Vec::new()
    };

    let default_request_exchange = String::from(REQUEST_EXCHANGE);
    let default_response_exchange = String::from(RESPONSE_EXCHANGE);

    for endpoint in &config_endpoints {

        // One the high level you have structure like
        // {"search": {"url": ..., "microservice": ...}},
        // but will be convenient for further processing, if we return
        // {"url": ..., "microservice": ...} instead.
        let configuration = match endpoint.clone().into_table() {
            Ok(table) => {
                let endpoint_name = table.keys().last().unwrap().as_str().clone();
                table[endpoint_name].clone().into_table().unwrap()
            },
            Err(_) => {
                let error = format!("endpoint \"{}\" is invalid.", endpoint);
                println!("{}", PathfinderError::InvalidEndpoint(error));
                continue;
            }
        };

        // Check on required fields
        let mut missing_fields = Vec::new();
        let required_fields : HashSet<&str> = ["url", "microservice"].iter().cloned().collect();
        for key in required_fields  {
            if !configuration.contains_key(key) {
                missing_fields.push(key);
            }
        }

        if missing_fields.len() > 0 {
            let error = format!("keys {:?} for {} endpoint is missing.", missing_fields, endpoint);
            println!("{}", PathfinderError::InvalidEndpoint(error));
            continue;
        }

        let url = get_value_from_config_as_str(&configuration, "url", "");
        let microservice = get_value_from_config_as_str(&configuration, "microservice", "");
        let request_exchange = get_value_from_config_as_str(&configuration, "request_exchange", &default_request_exchange);
        let response_exchange = get_value_from_config_as_str(&configuration, "response_exchange", &default_response_exchange);
        let endpoint = Rc::new(Box::new(Endpoint::new(&url, &microservice, &request_exchange, &response_exchange)));
        endpoints.insert(url, endpoint);
    }

    endpoints
}

#[cfg(test)]
mod tests {
    use config::{get_config};
    use engine::router::endpoint::{Endpoint, extract_endpoints};

    #[test]
    fn test_extract_endpoints_returns_an_empty_dict_by_default() {
        let conf = get_config(&"");
        let endpoints = extract_endpoints(conf);
        assert_eq!(endpoints.len(), 0);
    }

    #[test]
    fn test_extract_endpoints_returns_an_empty_dict_for_a_file_without_endpoints() {
        let conf = get_config(&"./tests/files/config_without_endpoints.yaml");
        let endpoints = extract_endpoints(conf);
        assert_eq!(endpoints.len(), 0);
    }

    #[test]
    fn test_extract_endpoints_returns_dict_for_a_file_with_valid_endpoints() {
        let conf = get_config(&"./tests/files/config_with_valid_endpoints.yaml");
        let endpoints = extract_endpoints(conf);
        assert_eq!(endpoints.len(), 3);
        assert_eq!(endpoints.contains_key("/api/matchmaking/search"), true);
        assert_eq!(endpoints.contains_key("/api/matchmaking/leaderboard"), true);
        assert_eq!(endpoints.contains_key("/api/matchmaking/player-of-the-game"), true);
    }

    #[test]
    fn test_extract_endpoints_returns_dict_without_invalid_endpoints() {
        let conf = get_config(&"./tests/files/config_with_invalid_endpoints.yaml");
        let endpoints = extract_endpoints(conf);
        assert_eq!(endpoints.len(), 1);
        assert_eq!(endpoints.contains_key("/api/matchmaking/player-of-the-game"), true);
    }

    #[test]
    fn test_get_url() {
        let url = "/api/matchmaking/test";
        let microservice_name = "api.matchmaking.test";
        let endpoint = Endpoint::new(url, microservice_name);

        assert_eq!(endpoint.get_url(), url);
    }

    #[test]
    fn test_get_microservice_name() {
        let url = "/api/matchmaking/test";
        let microservice_name = "api.matchmaking.test";
        let endpoint = Endpoint::new(url, microservice_name);

        assert_eq!(endpoint.get_microservice(), microservice_name);
    }
}
<commit_msg>Added tests for Endpoint structure<commit_after>//! A struct that represents an endpoint and related data with it.
//!

extern crate config;

use std::collections::{HashMap, HashSet};
use std::rc::{Rc};

use error::{PathfinderError};

use self::config::{Config, Value};

/// Default AMQP exchange point for requests
pub const REQUEST_EXCHANGE: &'static str = "open-matchmaking.direct";
/// Default AMQP exchange point for responses
pub const RESPONSE_EXCHANGE: &'static str = "open-matchmaking.responses.direct";

/// A struct which stores an original URL that must be converted to the
/// certain microservice endpoint.
///
/// # Example
/// ```
/// use engine::router::{Endpoint};
///
/// let endpoint = Endpoint::new(&"/api/matchmaking/search/", &"matchmaking.search");
/// assert_eq!(endpoint.get_url(), String::from("/api/matchmaking/search/"));
/// assert_eq!(endpoint.get_microservice(), String::from("matchmaking.search"));
/// ```
///
#[derive(Debug, Clone)]
pub struct Endpoint {
    url: String,
    microservice: String,
    request_exchange: String,
    response_exchange: String,
}

impl Endpoint {
    /// Returns a new instance of `Endpoint`.
    pub fn new(url: &str, microservice: &str, request_exchange: &str, response_exchange: &str) -> Endpoint {
        Endpoint {
            url: url.to_string(),
            microservice: microservice.to_string(),
            request_exchange: request_exchange.to_string(),
            response_exchange: response_exchange.to_string()
        }
    }

    /// Returns an original URL for which necessary to do a transformation.
    pub fn get_url(&self) -> String {
        self.url.clone()
    }

    /// Returns a microservice name.
    pub fn get_microservice(&self) -> String {
        self.microservice.clone()
    }

    /// Returns a request exchange point name.
    pub fn get_request_exchange(&self) -> String {
        self.request_exchange.clone()
    }

    /// Returns a response exchange point name.
    pub fn get_response_exchange(&self) -> String {
        self.response_exchange.clone()
    }
}

/// Extract a value configuration object as a string if it exists. Otherwise returns an default value as a string.
fn get_value_from_config_as_str(conf: &HashMap<String, Value>, key: &str, default: &str) -> String {
    match conf.get(key) {
        Some(value) => value.to_owned().into_str().unwrap(),
        None => String::from(default)
    }
}

/// Returns a HashMap with mapping for URL onto certain queue/topic name that
/// were extracted from a configuration.
pub fn extract_endpoints(conf: Box<Config>) -> HashMap<String, Rc<Box<Endpoint>>> {
    let mut endpoints = HashMap::new();

    let config_endpoints: Vec<Value> = match conf.get_array("endpoints") {
        Ok(array) => array,
        Err(_) => Vec::new()
    };

    let default_request_exchange = String::from(REQUEST_EXCHANGE);
    let default_response_exchange = String::from(RESPONSE_EXCHANGE);

    for endpoint in &config_endpoints {

        // One the high level you have structure like
        // {"search": {"url": ..., "microservice": ...}},
        // but will be convenient for further processing, if we return
        // {"url": ..., "microservice": ...} instead.
        let configuration = match endpoint.clone().into_table() {
            Ok(table) => {
                let endpoint_name = table.keys().last().unwrap().as_str().clone();
                table[endpoint_name].clone().into_table().unwrap()
            },
            Err(_) => {
                let error = format!("endpoint \"{}\" is invalid.", endpoint);
                println!("{}", PathfinderError::InvalidEndpoint(error));
                continue;
            }
        };

        // Check on required fields
        let mut missing_fields = Vec::new();
        let required_fields : HashSet<&str> = ["url", "microservice"].iter().cloned().collect();
        for key in required_fields  {
            if !configuration.contains_key(key) {
                missing_fields.push(key);
            }
        }

        if missing_fields.len() > 0 {
            let error = format!("keys {:?} for {} endpoint is missing.", missing_fields, endpoint);
            println!("{}", PathfinderError::InvalidEndpoint(error));
            continue;
        }

        let url = get_value_from_config_as_str(&configuration, "url", "");
        let microservice = get_value_from_config_as_str(&configuration, "microservice", "");
        let request_exchange = get_value_from_config_as_str(&configuration, "request_exchange", &default_request_exchange);
        let response_exchange = get_value_from_config_as_str(&configuration, "response_exchange", &default_response_exchange);
        let endpoint = Rc::new(Box::new(Endpoint::new(&url, &microservice, &request_exchange, &response_exchange)));
        endpoints.insert(url, endpoint);
    }

    endpoints
}

#[cfg(test)]
mod tests {
    use config::{get_config};
    use engine::router::endpoint::{Endpoint, extract_endpoints};

    #[test]
    fn test_extract_endpoints_returns_an_empty_dict_by_default() {
        let conf = get_config(&"");
        let endpoints = extract_endpoints(conf);
        assert_eq!(endpoints.len(), 0);
    }

    #[test]
    fn test_extract_endpoints_returns_an_empty_dict_for_a_file_without_endpoints() {
        let conf = get_config(&"./tests/files/config_without_endpoints.yaml");
        let endpoints = extract_endpoints(conf);
        assert_eq!(endpoints.len(), 0);
    }

    #[test]
    fn test_extract_endpoints_returns_dict_for_a_file_with_valid_endpoints() {
        let conf = get_config(&"./tests/files/config_with_valid_endpoints.yaml");
        let endpoints = extract_endpoints(conf);
        assert_eq!(endpoints.len(), 3);
        assert_eq!(endpoints.contains_key("/api/matchmaking/search"), true);
        assert_eq!(endpoints.contains_key("/api/matchmaking/leaderboard"), true);
        assert_eq!(endpoints.contains_key("/api/matchmaking/player-of-the-game"), true);
    }

    #[test]
    fn test_extract_endpoints_returns_dict_without_invalid_endpoints() {
        let conf = get_config(&"./tests/files/config_with_invalid_endpoints.yaml");
        let endpoints = extract_endpoints(conf);
        assert_eq!(endpoints.len(), 1);
        assert_eq!(endpoints.contains_key("/api/matchmaking/player-of-the-game"), true);
    }

    #[test]
    fn test_get_url() {
        let url = "/api/matchmaking/test";
        let microservice_name = "api.matchmaking.test";
        let request_exchange = "open-matchmaking.direct";
        let respone_exchange = "open-matchmaking.responses.direct";
        let endpoint = Endpoint::new(url, microservice_name, request_exchange, respone_exchange);

        assert_eq!(endpoint.get_url(), url);
    }

    #[test]
    fn test_get_microservice_name() {
        let url = "/api/matchmaking/test";
        let microservice_name = "api.matchmaking.test";
        let request_exchange = "open-matchmaking.direct";
        let respone_exchange = "open-matchmaking.responses.direct";
        let endpoint = Endpoint::new(url, microservice_name, request_exchange, respone_exchange);

        assert_eq!(endpoint.get_microservice(), microservice_name);
    }

    #[test]
    fn test_get_request_exchange() {
        let url = "/api/matchmaking/test";
        let microservice_name = "api.matchmaking.test";
        let request_exchange = "open-matchmaking.direct";
        let respone_exchange = "open-matchmaking.responses.direct";
        let endpoint = Endpoint::new(url, microservice_name, request_exchange, respone_exchange);

        assert_eq!(endpoint.get_request_exchange(), request_exchange);
    }

    #[test]
    fn test_get_response_exchange() {
        let url = "/api/matchmaking/test";
        let microservice_name = "api.matchmaking.test";
        let request_exchange = "open-matchmaking.direct";
        let respone_exchange = "open-matchmaking.responses.direct";
        let endpoint = Endpoint::new(url, microservice_name, request_exchange, respone_exchange);

        assert_eq!(endpoint.get_response_exchange(), respone_exchange);
    }
}
<|endoftext|>


<commit_before>// Copyright 2015 The Noise-rs Developers.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Useful things for debugging noise functions.

#![allow(unstable)]

extern crate image;

use noise;
use std::num::{self, Float, NumCast};

fn cast<T: NumCast, R: NumCast>(val: T) -> R {
    num::cast(val).unwrap()
}

fn clamp<F: Float>(val: F, min: F, max: F) -> F {
    match () {
        _ if val < min => min,
        _ if val > max => max,
        _ => val,
    }
}

pub fn render_png<T, F>(filename: &str, seed: &noise::Seed, width: u32, height: u32, func: F) where
    T: Float + NumCast,
    F: noise::GenFn2<T>,
{
    let mut pixels = Vec::with_capacity((width * height) as usize);

    for y in (0..height) {
        for x in (0..width) {
            let value: f32 = cast(func(seed, &[cast::<_,T>(x) - cast::<_,T>(width/2), cast::<_,T>(y) - cast::<_,T>(height/2)]));
            pixels.push(cast(clamp(value * 0.5 + 0.5, 0.0, 1.0) * 255.0));
        }
    }

    let _ = image::save_buffer(&Path::new(filename), &*pixels, width, height, image::ColorType::Grey(8));
}
<commit_msg>Update for image changes<commit_after>// Copyright 2015 The Noise-rs Developers.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Useful things for debugging noise functions.

#![allow(unstable)]

extern crate image;

use noise;
use std::num::{self, Float, NumCast};

fn cast<T: NumCast, R: NumCast>(val: T) -> R {
    num::cast(val).unwrap()
}

fn clamp<F: Float>(val: F, min: F, max: F) -> F {
    match () {
        _ if val < min => min,
        _ if val > max => max,
        _ => val,
    }
}

pub fn render_png<T, F>(filename: &str, seed: &noise::Seed, width: u32, height: u32, func: F) where
    T: Float + NumCast,
    F: noise::GenFn2<T>,
{
    let mut pixels = Vec::with_capacity((width * height) as usize);

    for y in (0..height) {
        for x in (0..width) {
            let value: f32 = cast(func(seed, &[cast::<_,T>(x) - cast::<_,T>(width/2), cast::<_,T>(y) - cast::<_,T>(height/2)]));
            pixels.push(cast(clamp(value * 0.5 + 0.5, 0.0, 1.0) * 255.0));
        }
    }

    let _ = image::save_buffer(&Path::new(filename), &*pixels, width, height, image::Gray(8));
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::hash::Hash;

pub struct Map<K, V> {
    committed: HashMap<K, V>,
    new: HashMap<K, V>,
}

impl<K: Eq + Hash + Clone, V: Clone> Map<K, V> {
    pub fn new() -> Self {
        Map {
            committed: HashMap::new(),
            new: HashMap::new(),
        }
    }

    pub fn insert(&mut self, key: K, value: V) {
        self.new.insert(key, value);
    }

    pub fn get(&self, key: &K) -> Option<&V> {
        self.new.get(key).or_else(|| self.committed.get(key))
    }

    pub fn commit(&mut self) {
        self.committed.extend(self.new.drain())
    }

    pub fn rollback(&mut self) {
        self.new.clear()
    }
}
<commit_msg>remove unused `Clone` constraints on rollback_map<commit_after>use std::collections::HashMap;
use std::hash::Hash;

pub struct Map<K, V> {
    committed: HashMap<K, V>,
    new: HashMap<K, V>,
}

impl<K: Eq + Hash, V> Map<K, V> {
    pub fn new() -> Self {
        Map {
            committed: HashMap::new(),
            new: HashMap::new(),
        }
    }

    pub fn insert(&mut self, key: K, value: V) {
        self.new.insert(key, value);
    }

    pub fn get(&self, key: &K) -> Option<&V> {
        self.new.get(key).or_else(|| self.committed.get(key))
    }

    pub fn commit(&mut self) {
        self.committed.extend(self.new.drain())
    }

    pub fn rollback(&mut self) {
        self.new.clear()
    }
}
<|endoftext|>


<commit_before>use std::process::Command;

fn main() {
    // Code lifted from https://stackoverflow.com/a/44407625/2350060
    if let Ok(hash_output) = Command::new("git")
        .args(&["describe", "--abbrev=4", "--dirty", "--always", "--tags"])
        .output()
    {
        let hash = String::from_utf8_lossy(&hash_output.stdout);
        println!("cargo:rustc-env=GIT_HASH={}", hash);
        // Re-build this crate when Git HEAD changes. Idea lifted from vergen crate.
        println!("cargo:rebuild-if-changed=.git/HEAD");
    }
}
<commit_msg>Do not use GIT_HASH if git-describe failed<commit_after>use std::process::Command;

fn main() {
    // Code lifted from https://stackoverflow.com/a/44407625/2350060
    if let Ok(hash_output) = Command::new("git")
        .args(&["describe", "--abbrev=4", "--dirty", "--always", "--tags"])
        .output()
    {
        if hash_output.status.success() {
            let hash = String::from_utf8_lossy(&hash_output.stdout);
            println!("cargo:rustc-env=GIT_HASH={}", hash);
            // Re-build this crate when Git HEAD changes. Idea lifted from vergen crate.
            println!("cargo:rebuild-if-changed=.git/HEAD");
        }
    }
}
<|endoftext|>