diff --git a/Cargo.toml b/Cargo.toml index cbdb0ae..ab1ea9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,10 @@ name = "sicht" version = "0.1.0" edition = "2021" +[features] +serde = ["dep:serde", "dep:serde_derive"] + [dependencies] -serde = { version = "1.0.217", features = ["derive"] } -serde_derive = "1.0.217" +serde = { optional = true, version = "1.0.217", features = ["derive"] } +serde_derive = { optional = true, version = "1.0.217" } +slotmap = "1.1.1" diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..c9f0802 --- /dev/null +++ b/flake.lock @@ -0,0 +1,82 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1780246643, + "narHash": "sha256-4T1KWX7xWGQMs9hNZ24IOY3aYOi8D6+5WtkNRBSttB8=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "3109eaae18e09d0b8aef23dc2579e7d94b8d4b4e", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1780284119, + "narHash": "sha256-y2wR4Mk6D/N1ID4FZa2oUMStCUxyIoRzmgOOpLzoWmo=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "51390d0bfca0a68a8c337d215a4bbeddc2ca616e", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..0da360e --- /dev/null +++ b/flake.nix @@ -0,0 +1,85 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = inputs @ { self, nixpkgs, flake-utils, rust-overlay, ... }: + flake-utils.lib.eachDefaultSystem ( + system: let + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { inherit system overlays; }; + in { + devShells.default = with pkgs; mkShell rec { + buildInputs = [ + (rust-bin.nightly.latest.minimal.override { + extensions = [ "clippy" "rust-analyzer" "rust-docs" "rust-src" "miri" ]; + }) + # We use nightly rustfmt features. + (rust-bin.selectLatestNightlyWith (toolchain: toolchain.rustfmt)) + + # Vulkan dependencies + shaderc + spirv-tools + vulkan-loader + vulkan-tools + vulkan-tools-lunarg + vulkan-validation-layers + vulkan-extension-layer + + # winit dependencies + libxkbcommon + wayland + xorg.libX11 + xorg.libXcursor + xorg.libXi + xorg.libXrandr + SDL2 + + clang + libclang + glibc.dev + + valgrind + gdb + + feh + ]; + + LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs; + SHADERC_LIB_DIR = lib.makeLibraryPath [ shaderc ]; + VK_LAYER_PATH = "${vulkan-validation-layers}/share/vulkan/explicit_layer.d"; + }; + devShells.CI = with pkgs; mkShell rec { + buildInputs = [ + (rust-bin.stable.latest.minimal.override { + extensions = [ "clippy" ]; + # Windows CI unfortunately needs to cross-compile from within WSL because Nix doesn't + # work on Windows. + targets = [ "x86_64-pc-windows-msvc" ]; + }) + # We use nightly rustfmt features. + (rust-bin.selectLatestNightlyWith (toolchain: toolchain.rustfmt)) + + # Vulkan dependencies + shaderc + + # winit dependencies + libxkbcommon + wayland + xorg.libX11 + xorg.libXcursor + xorg.libXi + xorg.libXrandr + ]; + + LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs; + SHADERC_LIB_DIR = lib.makeLibraryPath [ shaderc ]; + }; + } + ); +} diff --git a/src/birelational_map.rs b/src/birelational_map.rs new file mode 100644 index 0000000..73ddd60 --- /dev/null +++ b/src/birelational_map.rs @@ -0,0 +1,151 @@ +use std::{collections::HashMap, hash::Hash}; + +use slotmap::{DefaultKey, SlotMap}; + +pub trait BirelationalId { + type Id; + + fn get_id(&self) -> Self::Id; +} + +impl BirelationalId for usize { + type Id = usize; + + fn get_id(&self) -> Self::Id { + *self + } +} + +// A <= AID => [B] +// B <= BID => [A] + +// Entity <= EntityId => [Component] +// Component <= ComponentId => [Entity] + +pub struct BirelationalMap +where + K: BirelationalId, + V: BirelationalId, + K::Id: Hash + Eq + PartialEq, + V::Id: Hash + Eq + PartialEq, +{ + keys: SlotMap, + values: SlotMap, + keys_map: HashMap)>, + values_map: HashMap)>, +} + +impl BirelationalMap +where + K: BirelationalId, + V: BirelationalId, + K::Id: Hash + Eq + PartialEq, + V::Id: Hash + Eq + PartialEq, +{ + pub fn new() -> Self { + Self { + keys: SlotMap::new(), + values: SlotMap::new(), + keys_map: HashMap::new(), + values_map: HashMap::new(), + } + } + + pub fn get(&mut self, key: K) -> Option> { + self.keys_map + .get(&key.get_id())? + .1 + .iter() + .map(|x| self.values.get(*x)) + .collect() + } + + pub fn get_value(&mut self, value: V) -> Option> { + self.values_map + .get(&value.get_id())? + .1 + .iter() + .map(|x| self.keys.get(*x)) + .collect() + } + + pub fn insert(&mut self, key: K, value: V) { + let (key_id, value_id) = (key.get_id(), value.get_id()); + let key_exists = self.keys_map.contains_key(&key_id); + let value_exists = self.values_map.contains_key(&value_id); + + match (key_exists, value_exists) { + (true, true) => { + let key_idx = self.keys_map.get(&key_id).unwrap().0; + let value_idx = self.values_map.get(&value_id).unwrap().0; + + let (_, values) = self.keys_map.get_mut(&key_id).unwrap(); + if !values.contains(&value_idx) { + values.push(value_idx); + } + + let (_, keys) = self.values_map.get_mut(&value_id).unwrap(); + if !keys.contains(&key_idx) { + keys.push(key_idx); + } + } + (true, false) => { + let key_idx = self.keys_map.get(&key_id).unwrap().0; + let value_idx = self.values.insert(value); + + let (_, values) = self.keys_map.get_mut(&key_id).unwrap(); + values.push(value_idx); + self.values_map.insert(value_id, (value_idx, vec![key_idx])); + } + (false, true) => { + let value_idx = self.values_map.get(&value_id).unwrap().0; + let key_idx = self.keys.insert(key); + + self.keys_map.insert(key_id, (key_idx, vec![value_idx])); + let (_, keys) = self.values_map.get_mut(&value_id).unwrap(); + keys.push(key_idx); + } + (false, false) => { + let key_idx = self.keys.insert(key); + let value_idx = self.values.insert(value); + + self.keys_map.insert(key_id, (key_idx, vec![value_idx])); + self.values_map.insert(value_id, (value_idx, vec![key_idx])); + } + } + } + + pub fn remove(&mut self, key: K, value: V) + where + for<'a> &'a V: PartialEq, + for<'a> &'a K: PartialEq, + { + if let Some((_, items)) = self.keys_map.get_mut(&key.get_id()) { + let taken_items = std::mem::take(items); + *items = taken_items + .into_iter() + .filter(|x| self.values.get(*x) != Some(&value)) + .collect(); + } + + if let Some((_, items)) = self.values_map.get_mut(&value.get_id()) { + let taken_items = std::mem::take(items); + *items = taken_items + .into_iter() + .filter(|x| self.keys.get(*x) != Some(&key)) + .collect(); + } + } +} + +impl Default for BirelationalMap +where + K: BirelationalId, + V: BirelationalId, + K::Id: Hash + Eq + PartialEq, + V::Id: Hash + Eq + PartialEq, +{ + fn default() -> Self { + Self::new() + } +} diff --git a/src/diplopie.rs b/src/diplopie.rs index 386f8f2..7264790 100644 --- a/src/diplopie.rs +++ b/src/diplopie.rs @@ -4,14 +4,18 @@ use std::collections::btree_map::Iter; use std::collections::BTreeMap; use std::fmt::{Debug, Formatter}; +/// Named after +/// An instance of Diplopia +/// +/// Also see #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Diplopie where K: Ord + Clone, O: Ord + Clone, { - bild: BTreeMap, - urbild: BTreeMap, + od: BTreeMap, + os: BTreeMap, } impl Diplopie @@ -21,90 +25,91 @@ where { #[must_use] pub fn init(map: BTreeMap) -> Self { - let urbild = map.iter().map(|(k, o)| (o.clone(), k.clone())).collect(); - Self { bild: map, urbild } + let os = map.iter().map(|(k, v)| (v.clone(), k.clone())).collect(); + Self { od: map, os } } + #[inline] pub fn get(&self, key: &K) -> Option<&O> where K: Borrow, { - self.get_bild(key) + self.get_od(key) } - pub fn get_bild(&self, bild: &K) -> Option<&O> { - self.bild.get(bild) + pub fn get_od(&self, key: &K) -> Option<&O> { + self.od.get(key) } - pub fn get_urbild(&self, urbild: &O) -> Option<&K> { - self.urbild.get(urbild) + pub fn get_os(&self, value: &O) -> Option<&K> { + self.os.get(value) } - pub fn insert(&mut self, bild: K, urbild: O) { - self.bild.insert(bild.clone(), urbild.clone()); - self.urbild.insert(urbild, bild); + pub fn insert(&mut self, key: K, value: O) { + self.od.insert(key.clone(), value.clone()); + self.os.insert(value, key); } #[allow(clippy::iter_without_into_iter)] pub fn iter(&self) -> Iter<'_, K, O> { - self.bild.iter() + self.od.iter() } pub fn generate_from_iter(iter: impl Iterator) -> Self { - let (bild, urbild) = iter.fold( + let (od, os) = iter.fold( (BTreeMap::default(), BTreeMap::default()), - |(mut bild, mut urbild), (item, coitem)| { - bild.insert(item.clone(), coitem.clone()); - urbild.insert(coitem, item); - (bild, urbild) + |(mut normal, mut reverse), (item, coitem)| { + normal.insert(item.clone(), coitem.clone()); + reverse.insert(coitem, item); + (normal, reverse) }, ); - Self { bild, urbild } + Self { od, os } } } -impl Debug for Diplopie +impl Debug for Diplopie where K: Ord + Clone + Debug, - O: Ord + Clone + Debug, + V: Ord + Clone + Debug, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_map().entries(self.iter()).finish() } } -impl Default for Diplopie +impl Default for Diplopie where K: Ord + Clone, - O: Ord + Clone, + V: Ord + Clone, { fn default() -> Self { Self { - bild: BTreeMap::default(), - urbild: BTreeMap::default(), + od: BTreeMap::default(), + os: BTreeMap::default(), } } } -impl FromIterator<(K, O)> for Diplopie +impl FromIterator<(K, V)> for Diplopie where K: Ord + Clone, - O: Ord + Clone, + V: Ord + Clone, { - fn from_iter>(iter: I) -> Self { + fn from_iter>(iter: I) -> Self { Self::generate_from_iter(iter.into_iter()) } } -impl Extend<(K, O)> for Diplopie +impl Extend<(K, V)> for Diplopie where K: Ord + Clone, - O: Ord + Clone, + V: Ord + Clone, { fn extend(&mut self, iter: I) where - I: IntoIterator, + I: IntoIterator, { iter.into_iter().for_each(move |(k, v)| { self.insert(k, v); diff --git a/src/iter.rs b/src/iter.rs index 12d641e..5cc19a9 100644 --- a/src/iter.rs +++ b/src/iter.rs @@ -5,7 +5,7 @@ impl<'a, K, O, V> IntoIterator for &'a SichtMap where K: Ord + Clone + 'a, O: Ord + Clone + 'a, - V: 'a, + V: Ord + 'a, { type Item = (&'a K, &'a V); type IntoIter = Iter<'a, K, V>; @@ -19,6 +19,7 @@ impl FromIterator<(K, O, V)> for SichtMap where K: Ord + Clone, O: Ord + Clone, + V: Ord, { fn from_iter>(iter: I) -> Self { iter.into_iter() @@ -31,6 +32,7 @@ impl FromIterator<((K, V), (K, O))> for SichtMap where K: Ord + Clone, O: Ord + Clone, + V: Ord, { fn from_iter>(iter: I) -> Self { let (map, lookup): (BTreeMap, Diplopie) = iter.into_iter().unzip(); diff --git a/src/lib.rs b/src/lib.rs index 03e8226..90683d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,21 +1,13 @@ #![deny(rust_2018_idioms)] -#![deny(clippy::pedantic, clippy::dbg_macro)] -#![feature(allocator_api)] +// #![deny(missing_docs)] + +pub mod birelational_map; pub mod diplopie; -pub mod iter; +mod iter; pub mod map; + +#[cfg(feature = "serde")] pub mod serde; pub use crate::diplopie::Diplopie; pub use crate::map::SichtMap; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} diff --git a/src/map.rs b/src/map.rs index 04f1d92..f865583 100644 --- a/src/map.rs +++ b/src/map.rs @@ -6,6 +6,7 @@ pub struct SichtMap where K: Ord + Clone, O: Ord + Clone, + V: Ord, { pub(crate) map: BTreeMap, lookup: Diplopie, @@ -15,6 +16,7 @@ impl SichtMap where K: Ord + Clone, O: Ord + Clone, + V: Ord, { #[must_use] pub fn new() -> Self { @@ -34,6 +36,7 @@ impl SichtMap where K: Ord + Clone, O: Ord + Clone, + V: Ord, { pub fn get(&self, key: &K) -> Option<&V> { self.get_with_base_key(key) @@ -44,7 +47,7 @@ where } pub fn get_with_outer_key(&self, key: &O) -> Option<&V> { - let base_key = self.lookup.get_urbild(key)?; + let base_key = self.lookup.get_os(key)?; self.get_with_base_key(base_key) } @@ -72,7 +75,7 @@ impl Debug for SichtMap where K: Ord + Debug + Clone, O: Ord + Debug + Clone, - V: Debug, + V: Ord + Debug, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_map().entries(self.iter()).finish() @@ -83,7 +86,7 @@ impl Clone for SichtMap where K: Ord + Clone, O: Ord + Clone, - V: Clone, + V: Clone + Ord, { fn clone(&self) -> Self { Self { @@ -96,8 +99,9 @@ impl Default for SichtMap where K: Ord + Clone, O: Ord + Clone, + V: Ord, { fn default() -> Self { - SichtMap::new() + SichtMap::::new() } } diff --git a/src/serde.rs b/src/serde.rs index 2f7ac6c..5f44f9f 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -9,7 +9,7 @@ impl<'de, K, O, V> Deserialize<'de> for SichtMap where K: Deserialize<'de> + Ord + Clone + 'de, O: Deserialize<'de> + Ord + Clone + 'de, - V: Deserialize<'de>, + V: Deserialize<'de> + Ord, { fn deserialize(deserializer: D) -> Result where @@ -33,6 +33,7 @@ where where K: Clone + Ord, O: Clone + Ord, + V: Ord, { type Value = SichtMap; @@ -120,7 +121,7 @@ pub trait Pair { pub struct Error {} impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + fn fmt(&self, _f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { todo!() } } @@ -128,7 +129,7 @@ impl Display for Error { impl std::error::Error for Error {} impl serde::de::Error for Error { - fn custom(msg: T) -> Self + fn custom(_msg: T) -> Self where T: Display, { diff --git a/tests/birelational_map.rs b/tests/birelational_map.rs new file mode 100644 index 0000000..d10124b --- /dev/null +++ b/tests/birelational_map.rs @@ -0,0 +1,43 @@ +use sicht::birelational_map::BirelationalMap; + +#[test] +pub fn inserting() { + let mut map = BirelationalMap::new(); + map.insert(10, 20); + + let value = *map.get(10).unwrap()[0]; + assert_eq!(value, 20); + + let key = *map.get_value(20).unwrap()[0]; + assert_eq!(key, 10); +} + +#[test] +pub fn inserting_twice() { + let mut map = BirelationalMap::new(); + map.insert(10, 20); + map.insert(10, 30); + + let value = map.get(10).unwrap(); + assert_eq!(*value[0], 20); + assert_eq!(*value[1], 30); + + let key = *map.get_value(30).unwrap()[0]; + assert_eq!(key, 10); + + let key = *map.get_value(20).unwrap()[0]; + assert_eq!(key, 10); +} + +#[test] +pub fn removal() { + let mut map = BirelationalMap::new(); + map.insert(10, 20); + map.insert(10, 30); + + map.remove(10, 30); + + let values = map.get(10).unwrap(); + assert_eq!(values.len(), 1); + assert_eq!(*values[0], 20); +}