From 00000010da9a6f0389d370252a317d1f6eeb31b6 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Sat, 21 Jan 2023 02:03:11 -0600 Subject: [PATCH] initial --- .envrc | 1 + .gitignore | 1 + assignment-0/.gitignore | 3 + assignment-0/Cargo.lock | 390 ++++++++++++++++++++++++++++++++ assignment-0/Cargo.toml | 11 + assignment-0/src/main.rs | 33 +++ assignment-0/src/ppm.rs | 26 +++ assignment-0/src/value_noise.rs | 88 +++++++ assignment-0/src/vec2.rs | 23 ++ flake.lock | 93 ++++++++ flake.nix | 20 ++ 11 files changed, 689 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 assignment-0/.gitignore create mode 100644 assignment-0/Cargo.lock create mode 100644 assignment-0/Cargo.toml create mode 100644 assignment-0/src/main.rs create mode 100644 assignment-0/src/ppm.rs create mode 100644 assignment-0/src/value_noise.rs create mode 100644 assignment-0/src/vec2.rs create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/.envrc b/.envrc new file mode 100644 index 000000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..92b2793 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.direnv diff --git a/assignment-0/.gitignore b/assignment-0/.gitignore new file mode 100644 index 000000000..aa58f4e --- /dev/null +++ b/assignment-0/.gitignore @@ -0,0 +1,3 @@ +/target +out.ppm + diff --git a/assignment-0/Cargo.lock b/assignment-0/Cargo.lock new file mode 100644 index 000000000..3f25d9c --- /dev/null +++ b/assignment-0/Cargo.lock @@ -0,0 +1,390 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" + +[[package]] +name = "assignment-0" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "rand", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2" +dependencies = [ + "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", +] + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rustix" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" diff --git a/assignment-0/Cargo.toml b/assignment-0/Cargo.toml new file mode 100644 index 000000000..aa0f266 --- /dev/null +++ b/assignment-0/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "assignment-0" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.68" +clap = { version = "4.1.1", features = ["derive"] } +rand = "0.8.5" diff --git a/assignment-0/src/main.rs b/assignment-0/src/main.rs new file mode 100644 index 000000000..bfb509b --- /dev/null +++ b/assignment-0/src/main.rs @@ -0,0 +1,33 @@ +use std::{fs::File, path::PathBuf}; + +use anyhow::Result; +use clap::Parser; + +use crate::value_noise::generate_noise; + +mod ppm; +mod value_noise; +mod vec2; + +#[derive(Parser)] +struct Opt { + #[clap(short = 'o', long = "out", default_value = "out.ppm")] + output_path: PathBuf, +} + +fn main() -> Result<()> { + let opt = Opt::parse(); + + let rng = rand::thread_rng(); + + let width = 256; + let height = 256; + let ppm = generate_noise(width, height, rng); + + { + let file = File::create(opt.output_path)?; + ppm.write(file)?; + } + + Ok(()) +} diff --git a/assignment-0/src/ppm.rs b/assignment-0/src/ppm.rs new file mode 100644 index 000000000..8a2dfb6 --- /dev/null +++ b/assignment-0/src/ppm.rs @@ -0,0 +1,26 @@ +use std::io::{Result, Write}; + +pub struct Pixel(pub u8, pub u8, pub u8); + +pub struct Ppm { + pub(crate) width: usize, + pub(crate) height: usize, + pub(crate) data: Vec, +} + +impl Ppm { + pub fn write(&self, mut w: impl Write) -> Result<()> { + // Header + let header = format!("P3 {} {} 255\n", self.width, self.height); + w.write_all(header.as_bytes())?; + + // Pixel data + for pixel in self.data.iter() { + let Pixel(red, green, blue) = pixel; + let pixel = format!("{red} {green} {blue}\n"); + w.write_all(pixel.as_bytes())?; + } + + Ok(()) + } +} diff --git a/assignment-0/src/value_noise.rs b/assignment-0/src/value_noise.rs new file mode 100644 index 000000000..e8e1352 --- /dev/null +++ b/assignment-0/src/value_noise.rs @@ -0,0 +1,88 @@ +// https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1/creating-simple-2D-noise.html + +use std::mem::{self}; + +use rand::{Rng, RngCore}; + +use crate::ppm::{Pixel, Ppm}; +use crate::vec2::Vec2; + +const MAX_TABLE_SIZE: usize = 256; +const MAX_TABLE_SIZE_MASK: usize = MAX_TABLE_SIZE - 1; + +pub fn generate_noise(width: usize, height: usize, rng: impl RngCore) -> Ppm { + let mut noise_map = vec![0.0; width * height]; + let noise = ValueNoise::new(rng); + let frequency = 0.05; + + for j in 0..height { + for i in 0..width { + let vec = Vec2::new(i as f64, j as f64); + noise_map[j * width + i] = noise.eval(vec * frequency); + } + } + + let data = noise_map + .into_iter() + .map(|f| { + let v = (f * 256.0).floor() as u8; + Pixel(v, v, v) + }) + .collect(); + Ppm { + width, + height, + data, + } +} + +pub struct ValueNoise { + r: [f64; MAX_TABLE_SIZE * MAX_TABLE_SIZE], +} + +impl ValueNoise { + pub fn new(mut rng: impl RngCore) -> Self { + let mut r: [f64; MAX_TABLE_SIZE * MAX_TABLE_SIZE] = unsafe { mem::zeroed() }; + for k in 0..MAX_TABLE_SIZE * MAX_TABLE_SIZE { + r[k] = rng.gen_range(0.0..1.0); + } + ValueNoise { r } + } + + pub fn eval(&self, point: Vec2) -> f64 { + let xi = point.x.floor(); + let yi = point.y.floor(); + + let tx = point.x - xi; + let ty = point.y - yi; + + let xi = xi as usize; + let yi = yi as usize; + + let rx0 = xi & MAX_TABLE_SIZE_MASK; + let rx1 = (rx0 + 1) & MAX_TABLE_SIZE_MASK; + let ry0 = yi & MAX_TABLE_SIZE_MASK; + let ry1 = (ry0 + 1) & MAX_TABLE_SIZE_MASK; + + let c00 = self.r[ry0 * MAX_TABLE_SIZE_MASK + rx0]; + let c10 = self.r[ry0 * MAX_TABLE_SIZE_MASK + rx1]; + let c01 = self.r[ry1 * MAX_TABLE_SIZE_MASK + rx0]; + let c11 = self.r[ry1 * MAX_TABLE_SIZE_MASK + rx1]; + + let sx = smooth_step(tx); + let sy = smooth_step(ty); + + let nx0 = lerp(c00, c10, sx); + let nx1 = lerp(c01, c11, sx); + + lerp(nx0, nx1, sy) + } +} + +fn smooth_step(t: f64) -> f64 { + t * t * (3.0 - 2.0 * t) +} + +fn lerp(lo: f64, hi: f64, t: f64) -> f64 { + lo * (1.0 - t) + hi * t +} diff --git a/assignment-0/src/vec2.rs b/assignment-0/src/vec2.rs new file mode 100644 index 000000000..e38732a --- /dev/null +++ b/assignment-0/src/vec2.rs @@ -0,0 +1,23 @@ +use std::ops::Mul; + +pub struct Vec2 { + pub x: T, + pub y: T, +} + +impl Vec2 { + pub fn new(x: T, y: T) -> Self { + Self { x, y } + } +} + +impl> Mul for Vec2 { + type Output = Vec2; + + fn mul(self, rhs: S) -> Self::Output { + Vec2 { + x: self.x * rhs, + y: self.y * rhs, + } + } +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..2335fbb --- /dev/null +++ b/flake.lock @@ -0,0 +1,93 @@ +{ + "nodes": { + "fenix": { + "inputs": { + "nixpkgs": "nixpkgs", + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1674240251, + "narHash": "sha256-AVMmf/CtcGensTZmMicToDpOwySEGNKYgRPC7lu3m8w=", + "owner": "nix-community", + "repo": "fenix", + "rev": "d8067f4d1d3d30732703209bec5ca7d62aaececc", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "type": "github" + }, + "original": { + "id": "flake-utils", + "type": "indirect" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1673796341, + "narHash": "sha256-1kZi9OkukpNmOaPY7S5/+SlCDOuYnP3HkXHvNDyLQcc=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "6dccdc458512abce8d19f74195bb20fdb067df50", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1667629849, + "narHash": "sha256-P+v+nDOFWicM4wziFK9S/ajF2lc0N2Rg9p6Y35uMoZI=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "3bacde6273b09a21a8ccfba15586fb165078fb62", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "fenix": "fenix", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs_2" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1674162026, + "narHash": "sha256-iY0bxoVE7zAZmp0BB/m5hZW5pWHUfgntDvc1m2zyt/U=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "6e52c64031825920983515b9e975e93232739f7f", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..6402583 --- /dev/null +++ b/flake.nix @@ -0,0 +1,20 @@ +{ + inputs = { fenix.url = "github:nix-community/fenix"; }; + + outputs = { self, nixpkgs, flake-utils, fenix }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ fenix.overlays.default ]; + }; + + toolchain = pkgs.fenix.default; + in rec { + devShell = pkgs.mkShell { + packages = (with pkgs; [ cargo-watch cargo-deny cargo-edit ]) + ++ (with toolchain; [ cargo rustc rustfmt clippy ]); + CARGO_UNSTABLE_SPARSE_REGISTRY = "true"; + }; + }); +}