From a4e7f999c738535aa08b08c42aa172ca204e19fd Mon Sep 17 00:00:00 2001 From: Max Fierro Date: Tue, 19 Nov 2024 03:46:44 -0800 Subject: [PATCH] Testing sled db --- Cargo.lock | 183 +++++++++++++++++++++++++ Cargo.toml | 3 +- src/database/engine/sled.rs | 103 ++++++++++++++ src/database/mod.rs | 88 ++++++------ src/database/model.rs | 10 -- src/database/record/dir.rs | 9 +- src/database/record/mur.rs | 85 ++++++------ src/game/mock/mod.rs | 24 ++-- src/game/model.rs | 3 +- src/game/zero_by/mod.rs | 7 +- src/game/zero_by/variants.rs | 8 +- src/solver/algorithm/strong/acyclic.rs | 34 +++-- src/solver/mod.rs | 20 ++- src/test.rs | 13 +- 14 files changed, 448 insertions(+), 142 deletions(-) create mode 100644 src/database/engine/sled.rs diff --git a/Cargo.lock b/Cargo.lock index e3571a3..b7585ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,6 +65,18 @@ version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitvec" version = "1.0.1" @@ -77,6 +89,18 @@ dependencies = [ "wyz", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[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.4.6" @@ -123,6 +147,30 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "equivalent" version = "1.0.1" @@ -141,12 +189,31 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "gamesman-nova" version = "0.1.5" @@ -158,6 +225,7 @@ dependencies = [ "petgraph", "regex", "serde_json", + "sled", "strum", "strum_macros", ] @@ -184,18 +252,74 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "libc" +version = "0.2.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "memchr" version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + [[package]] name = "petgraph" version = "0.6.4" @@ -230,6 +354,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.9.6" @@ -271,6 +404,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.188" @@ -302,6 +441,28 @@ dependencies = [ "serde", ] +[[package]] +name = "sled" +version = "0.34.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "strsim" version = "0.10.0" @@ -356,6 +517,28 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[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-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.48.0" diff --git a/Cargo.toml b/Cargo.toml index 57800dc..2a8447f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/GamesCrafters/GamesmanNova" keywords = ["game", "solver", "strong-solver", "research"] edition = "2021" readme = "README.md" -exclude = ["/.github", "/dev"] +exclude = ["/.github", "/dev", "/doc"] [[bin]] path = "src/main.rs" @@ -28,6 +28,7 @@ exitcode = "^1" anyhow = "^1" bitvec = "^1" regex = "^1" +sled = "^0.34" [dev-dependencies] strum_macros = "0.26" diff --git a/src/database/engine/sled.rs b/src/database/engine/sled.rs new file mode 100644 index 0000000..e7f1d8e --- /dev/null +++ b/src/database/engine/sled.rs @@ -0,0 +1,103 @@ +//! # Sled Database Engine API +//! +//! It's all downhill from here. + +use anyhow::anyhow; +use anyhow::Result; + +use std::path::Path; + +use crate::database::ByteMap; +use crate::database::Persistent; +use crate::database::ProtoRelational; +use crate::database::Relation; +use crate::database::Schema; + +/* DEFINITIONS */ + +pub struct SledDatabase { + db: sled::Db, +} + +pub struct SledNamespace { + tree: sled::Tree, + schema: Schema, +} + +/* IMPLEMENTATIONS */ + +impl ByteMap for SledNamespace { + fn insert(&mut self, key: K, record: V) -> Result<()> + where + K: AsRef<[u8]>, + V: AsRef<[u8]>, + { + self.tree + .insert(key, record.as_ref())?; + Ok(()) + } + + fn get(&self, key: K) -> Result>> + where + K: AsRef<[u8]>, + { + Ok(self + .tree + .get(key)? + .map(|v| v.to_vec())) + } + + fn remove(&mut self, key: K) -> Result>> + where + K: AsRef<[u8]>, + { + Ok(self + .tree + .remove(key)? + .map(|v| v.to_vec())) + } +} + +impl Relation for SledNamespace { + fn schema(&self) -> &Schema { + &self.schema + } + + fn count(&self) -> usize { + self.tree.len() + } +} + +impl ProtoRelational for SledDatabase { + type Namespace = SledNamespace; + fn namespace(&self, schema: Schema, name: &str) -> Result { + Ok(SledNamespace { + tree: self.db.open_tree(name)?, + schema, + }) + } +} + +impl Persistent for SledDatabase { + fn new(path: &Path) -> Result + where + Self: Sized, + { + let db = sled::open(path)?; + Ok(SledDatabase { db }) + } + + fn flush(&self) -> Result { + self.db + .flush() + .map_err(|e| anyhow!("Failed to flush database: {}", e)) + } +} + +impl Drop for SledDatabase { + fn drop(&mut self) { + self.db + .flush() + .expect(&format!("Failed to flush Sled database")); + } +} diff --git a/src/database/mod.rs b/src/database/mod.rs index 57fb0cb..85122cd 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -1,4 +1,3 @@ -#![allow(drop_bounds)] //! # Database Module //! //! This module contains memory and I/O mechanisms used to store and fetch @@ -8,7 +7,7 @@ use anyhow::Result; use std::path::{Path, PathBuf}; -use crate::database::model::{Key, SequenceKey, Value}; +use crate::database::model::SequenceKey; /* RE-EXPORTS */ @@ -25,6 +24,12 @@ pub mod error; /* IMPLEMENTATION MODULES */ +/// TODO +pub mod engine { + pub mod sled; +} + +/// TODO pub mod record { pub mod mur; pub mod dir; @@ -83,44 +88,56 @@ pub enum Datatype { /* DATABASE RESOURCE INTERFACES */ -/// Represents the behavior of a map from `Key` to `Value` types, which are both -/// concrete aliases for a sequence of bits. -pub trait Map { - /// Replaces the value associated with `key` with the bits of `record`, +/// Allows a resource to act as a map from bytes to other bytes. Commonly +/// understood as a Key-Value store. +pub trait ByteMap { + /// Replaces the value associated with `key` with the bytes of `record`, /// creating one if it does not already exist. Fails if under any violation /// of implementation-specific assumptions of record size or contents. - fn insert(&mut self, key: &Key, record: &R) -> Result<()>; + fn insert(&mut self, key: K, record: V) -> Result<()> + where + K: AsRef<[u8]>, + V: AsRef<[u8]>; - /// Returns the bits associated with the value of `key`, or `None` if there - /// is no such association. Infallible due to all possible values of `key` - /// being considered valid (but not necessarily existent). - fn get(&self, key: &Key) -> Option<&Value>; + /// Returns the bytes associated with the value of `key`, or `None` if there + /// is no such association. + fn get(&self, key: K) -> Result>> + where + K: AsRef<[u8]>; /// Removes the association of `key` to whatever value it is currently bound /// to, or does nothing if there is no such value. - fn remove(&mut self, key: &Key); + fn remove(&mut self, key: K) -> Result>> + where + K: AsRef<[u8]>; +} + +/// Allows a database to be sectionable into different namespaces, such as +/// tables in the case of a relational database. Such sections are assumed to +/// operate over their elements independently of each other. +pub trait ProtoRelational { + type Namespace: ByteMap + Relation; + + /// Create and return a new namespace under this database enforcing the + /// provided `schema` as a relation. + fn namespace(&self, schema: Schema, name: &str) -> Result; } /// Allows a database to be evicted to persistent media. Implementing this trait /// requires custom handling of what happens when the database is closed; if it -/// has data on memory, then it should persist dirty data to ensure consistency -/// via [`Drop`]. Database file structure is implementation-specific. -pub trait Persistent -where - Self: Drop, -{ +/// has data on memory, then it should persist dirty data to ensure consistency. +/// This is possible via [`Persistent::flush`]. +pub trait Persistent { /// Interprets the contents of a directory at `path` to be the contents of - /// a persistent database. Fails if the contents of `path` are unexpected. - fn from(path: &Path) -> Result + /// a persistent database. If no contents have been written, they will be + /// initialized. Fails if the path does not exist. + fn new(path: &Path) -> Result where Self: Sized; - /// Binds the contents of the database to a particular `path` for the sake - /// of persistence. It is undefined behavior to forego calling this function - /// before pushing data to the underlying database. Fails if the database is - /// already bound to another path, or if `path` is non-empty, or under any - /// I/O failure. - fn bind(&self, path: &Path) -> Result<()>; + /// Persist the in-memory contents of the database to disk. Potentially very + /// slow. Use sparingly to ensure recoverability. + fn flush(&self) -> Result; } /// A grouping of fixed-length records which share a table [`Schema`] that can @@ -128,28 +145,13 @@ where /// keeping track of useful metadata. pub trait Relation where - Self: Map, + Self: ByteMap, { /// Returns a reference to the schema associated with `self`. fn schema(&self) -> &Schema; /// Returns the number of records currently contained in `self`. - fn count(&self) -> u64; - - /// Returns the total number of bytes being used to store the contents of - /// `self`, excluding metadata (both in memory and persistent media). - fn bytes(&self) -> u64; - - /// Returns the identifier associated with `self`. - fn id(&self) -> SequenceKey; -} - -/* RECORD INTERFACE */ - -/// Represents an in-memory sequence of bits that can be directly accessed. -pub trait Record { - /// Returns a reference to the sequence of bits in `self`. - fn raw(&self) -> &Value; + fn count(&self) -> usize; } /* IMPLEMENTATIONS */ diff --git a/src/database/model.rs b/src/database/model.rs index 73f216e..8d04aff 100644 --- a/src/database/model.rs +++ b/src/database/model.rs @@ -2,15 +2,5 @@ //! //! Provides definitions for types used in database interfaces. -use bitvec::order::Msb0; -use bitvec::slice::BitSlice; - /// A generic number used to differentiate between objects. pub type SequenceKey = u64; - -/// The type of a raw sequence of bits encoding a database value associated -/// with a key, backed by a [`BitSlice`] with [`u8`] big-endian storage. -pub type Value = BitSlice; - -/// The type of a database key per an implementation of [`Map`]. -pub type Key = BitSlice; diff --git a/src/database/record/dir.rs b/src/database/record/dir.rs index f979989..087d62c 100644 --- a/src/database/record/dir.rs +++ b/src/database/record/dir.rs @@ -14,7 +14,7 @@ use std::ffi::CString; use crate::database::error::DatabaseError::RecordViolation; use crate::database::util::SchemaBuilder; -use crate::database::{Attribute, Datatype, Record, Schema}; +use crate::database::{Attribute, Datatype, Schema}; use crate::util::min_ubits; /* CONSTANTS */ @@ -99,10 +99,9 @@ pub struct RecordBuffer { buf: BitArr!(for BUFFER_SIZE, in u8, Msb0), } -impl Record for RecordBuffer { - #[inline(always)] - fn raw(&self) -> &BitSlice { - &self.buf +impl AsRef<[u8]> for RecordBuffer { + fn as_ref(&self) -> &[u8] { + self.buf.as_raw_slice() } } diff --git a/src/database/record/mur.rs b/src/database/record/mur.rs index dc05315..d78f009 100644 --- a/src/database/record/mur.rs +++ b/src/database/record/mur.rs @@ -11,7 +11,7 @@ use bitvec::slice::BitSlice; use bitvec::{bitarr, BitArr}; use crate::database::error::DatabaseError::RecordViolation; -use crate::database::{Attribute, Datatype, Record, Schema, SchemaBuilder}; +use crate::database::{Attribute, Datatype, Schema, SchemaBuilder}; use crate::game::model::{Player, PlayerCount}; use crate::solver::model::{IUtility, RUtility, Remoteness, SUtility}; use crate::solver::UtilityType; @@ -23,7 +23,7 @@ use crate::util; pub const RECORD_NAME: &str = "Multi-Utility Remoteness Draw Record"; /// The maximum number of bits that can be used to encode a single record. -pub const BUFFER_SIZE: usize = 128; +pub const BUFFER_BIT_SIZE: usize = 128; /// The exact number of bits that are used to encode a draw value. pub const DRAW_SIZE: usize = 1; @@ -60,7 +60,7 @@ pub const SIMPLE_UTILITY_SIZE: usize = 2; /// next value is pushed to the leftmost position. This ensures that a /// minimally-sized representation of the record can be provided. pub struct RecordBuffer { - buf: BitArr!(for BUFFER_SIZE, in u8, Msb0), + buf: BitArr!(for BUFFER_BIT_SIZE, in u8, Msb0), players: PlayerCount, utility: UtilityType, remoteness: bool, @@ -80,9 +80,9 @@ pub fn schema( ) -> Result { let bit_size = RecordBuffer::bit_size(players, utility, remoteness, draw); let max_players = - RecordBuffer::player_count(BUFFER_SIZE, utility, remoteness, draw); + RecordBuffer::player_count(BUFFER_BIT_SIZE, remoteness, utility, draw); - if bit_size > BUFFER_SIZE { + if bit_size > BUFFER_BIT_SIZE { bail!(RecordViolation { name: RECORD_NAME, hint: format!( @@ -125,10 +125,9 @@ pub fn schema( /* RECORD IMPLEMENTATION */ -impl Record for RecordBuffer { - #[inline(always)] - fn raw(&self) -> &BitSlice { - &self.buf[..Self::bit_size( +impl AsRef<[u8]> for RecordBuffer { + fn as_ref(&self) -> &[u8] { + &self.buf.as_raw_slice()[..Self::byte_size( self.players, self.utility, self.remoteness, @@ -149,9 +148,9 @@ impl RecordBuffer { ) -> Result { let size = Self::bit_size(players, utility, remoteness, draw); let max_players = - Self::player_count(BUFFER_SIZE, utility, remoteness, draw); + Self::player_count(BUFFER_BIT_SIZE, remoteness, utility, draw); - if size > BUFFER_SIZE { + if size > BUFFER_BIT_SIZE { bail!(RecordViolation { name: RECORD_NAME, hint: format!( @@ -162,7 +161,7 @@ impl RecordBuffer { }) } Ok(Self { - buf: bitarr!(u8, Msb0; 0; BUFFER_SIZE), + buf: bitarr!(u8, Msb0; 0; BUFFER_BIT_SIZE), remoteness, players, utility, @@ -170,29 +169,30 @@ impl RecordBuffer { }) } - /// Create a record buffer from a pre-existing sequence of bits, according + /// Create a record buffer from a pre-existing sequence of bytes, according /// to desired utilitty, remoteness, and draw capabilities. Fails if the /// provided sequence of bits is inconsistent with the desired capabilities /// due to sizing constraints. pub fn from( - bits: &BitSlice, + bytes: &[u8], + players: usize, utility: UtilityType, remoteness: bool, draw: bool, ) -> Result { - let len = bits.len(); - if len > BUFFER_SIZE { + let bit_len = bytes.len() * 8; + if bytes.len() > BUFFER_BIT_SIZE * 8 { bail!(RecordViolation { name: RECORD_NAME, hint: format!( "The record implementation operates on a buffer of \ - {BUFFER_SIZE} bits, but there was an attempt to \ - instantiate one from a buffer of {len} bits.", + {BUFFER_BIT_SIZE} bits, but there was an attempt to \ + instantiate one from a buffer of {bit_len} bits.", ), }) } - if len < Self::minimum_bit_size(remoteness, draw) { + if bytes.len() < Self::byte_size(players, utility, remoteness, draw) { bail!(RecordViolation { name: RECORD_NAME, hint: format!( @@ -204,9 +204,10 @@ impl RecordBuffer { }) } - let players = Self::player_count(len, utility, remoteness, draw); - let mut buf = bitarr!(u8, Msb0; 0; BUFFER_SIZE); - buf[0..len].copy_from_bitslice(bits); + let mut buf = bitarr!(u8, Msb0; 0; BUFFER_BIT_SIZE); + buf[0..Self::bit_size(players, utility, remoteness, draw)] + .copy_from_bitslice(BitSlice::from_slice(bytes)); + Ok(Self { remoteness, players, @@ -230,7 +231,7 @@ impl RecordBuffer { }) } - let start = self.remoteness_index(); + let start = self.remoteness_bit_index(); let end = start + REMOTENESS_SIZE; Ok(self.buf[start..end].load_be::()) } @@ -247,7 +248,7 @@ impl RecordBuffer { }) } - let index = self.draw_index(); + let index = self.draw_bit_index(); Ok(*self.buf.get(index).unwrap()) } @@ -321,7 +322,7 @@ impl RecordBuffer { }) } - let start = self.utility_index(player); + let start = self.utility_bit_index(player); let end = start + SIMPLE_UTILITY_SIZE; self.buf[start..end].store_be(utility); } @@ -375,7 +376,7 @@ impl RecordBuffer { }) } - let start = self.utility_index(player); + let start = self.utility_bit_index(player); let end = start + INTEGER_UTILITY_SIZE; self.buf[start..end].store_be(utility); } @@ -418,7 +419,7 @@ impl RecordBuffer { }) } - let start = self.remoteness_index(); + let start = self.remoteness_bit_index(); let end = start + REMOTENESS_SIZE; self.buf[start..end].store_be(value); Ok(()) @@ -436,7 +437,7 @@ impl RecordBuffer { }) } - let index = self.draw_index(); + let index = self.draw_bit_index(); self.buf.set(index, value); Ok(()) } @@ -456,7 +457,7 @@ impl RecordBuffer { }) } - let start = self.utility_index(player); + let start = self.utility_bit_index(player); let end = start + self.utility.space(); Ok(&self.buf[start..end]) } @@ -465,14 +466,12 @@ impl RecordBuffer { #[inline(always)] const fn player_count( - length: usize, - utility: UtilityType, + buffer_bit_size: usize, remoteness: bool, + utility: UtilityType, draw: bool, ) -> usize { - (length - - if remoteness { REMOTENESS_SIZE } else { 0 } - - if draw { DRAW_SIZE } else { 0 }) + (buffer_bit_size - Self::bit_size(0, utility, remoteness, draw)) / utility.space() } @@ -489,26 +488,30 @@ impl RecordBuffer { } #[inline(always)] - const fn minimum_bit_size(remoteness: bool, draw: bool) -> usize { - 0 + if remoteness { REMOTENESS_SIZE } else { 0 } - + if draw { DRAW_SIZE } else { 0 } + const fn byte_size( + players: usize, + utility: UtilityType, + remoteness: bool, + draw: bool, + ) -> usize { + Self::bit_size(players, utility, remoteness, draw).div_ceil(8) } /* LAYOUT METHODS */ #[inline(always)] - const fn utility_index(&self, player: Player) -> usize { + const fn utility_bit_index(&self, player: Player) -> usize { player * self.utility.space() } #[inline(always)] - const fn remoteness_index(&self) -> usize { + const fn remoteness_bit_index(&self) -> usize { self.players * self.utility.space() } #[inline(always)] - const fn draw_index(&self) -> usize { - self.remoteness_index() + const fn draw_bit_index(&self) -> usize { + self.remoteness_bit_index() + if self.remoteness { REMOTENESS_SIZE } else { 0 } } } diff --git a/src/game/mock/mod.rs b/src/game/mock/mod.rs index eaaf95c..b138843 100644 --- a/src/game/mock/mod.rs +++ b/src/game/mock/mod.rs @@ -5,7 +5,9 @@ //! creating example games a matter of simply declaring them and wrapping them //! in any necessary external interface implementations. +use bitvec::array::BitArray; use bitvec::field::BitField; +use bitvec::order::Msb0; use petgraph::csr::DefaultIx; use petgraph::Direction; use petgraph::{graph::NodeIndex, Graph}; @@ -69,9 +71,9 @@ impl<'a> Session<'a> { .inserted .get(&(node as *const Node)) { - let mut state = State::ZERO; + let mut state = BitArray::<_, Msb0>::ZERO; state.store_be::(index.index() as DefaultIx); - Some(state) + Some(state.data) } else { None } @@ -91,13 +93,15 @@ impl<'a> Session<'a> { fn transition(&self, state: State, dir: Direction) -> Vec { self.game .neighbors_directed( - NodeIndex::from(state.load_be::()), + NodeIndex::from( + BitArray::<_, Msb0>::from(state).load_be::(), + ), dir, ) .map(|n| { - let mut state = State::ZERO; + let mut state: BitArray<_, Msb0> = BitArray::ZERO; state.store_be(n.index()); - state + state.data }) .collect() } @@ -105,7 +109,9 @@ impl<'a> Session<'a> { /// Returns a reference to the game node with `state`, or panics if there is /// no such node. fn node(&self, state: State) -> &Node { - self.game[NodeIndex::from(state.load_be::())] + self.game[NodeIndex::from( + BitArray::<_, Msb0>::from(state).load_be::(), + )] } } @@ -123,9 +129,9 @@ impl Transition for Session<'_> { impl Bounded for Session<'_> { fn start(&self) -> State { - let mut state = State::ZERO; + let mut state = BitArray::<_, Msb0>::ZERO; state.store_be::(self.start.index() as DefaultIx); - state + state.data } fn end(&self, state: State) -> bool { @@ -188,7 +194,7 @@ mod tests { .collect(); let repeats = states.iter().any(|&i| { - states[(1 + i.load_be::())..] + states[(1 + BitArray::<_, Msb0>::from(i).load_be::())..] .iter() .any(|&j| i == j) }); diff --git a/src/game/model.rs b/src/game/model.rs index b352eff..a181923 100644 --- a/src/game/model.rs +++ b/src/game/model.rs @@ -2,14 +2,13 @@ //! //! Provides definitions for types used in game interfaces. -use bitvec::{array::BitArray, order::Msb0}; use clap::ValueEnum; /// The default number of bytes used to encode states. pub const DEFAULT_STATE_BYTES: usize = 8; /// Unique identifier of a particular state in a game. -pub type State = BitArray<[u8; B], Msb0>; +pub type State = [u8; B]; /// String encoding some specific game's variant. pub type Variant = String; diff --git a/src/game/zero_by/mod.rs b/src/game/zero_by/mod.rs index 5e4b03c..f61c747 100644 --- a/src/game/zero_by/mod.rs +++ b/src/game/zero_by/mod.rs @@ -11,7 +11,9 @@ use anyhow::bail; use anyhow::{Context, Result}; +use bitvec::array::BitArray; use bitvec::field::BitField; +use bitvec::order::Msb0; use crate::game::error::GameError; use crate::game::model::Variant; @@ -87,13 +89,14 @@ impl Session { } fn encode_state(&self, turn: Player, elements: Elements) -> State { - let mut state = State::ZERO; + let mut state: BitArray<_, Msb0> = BitArray::ZERO; state[..self.player_bits].store_be(turn); state[self.player_bits..].store_be(elements); - state + state.data } fn decode_state(&self, state: State) -> (Player, Elements) { + let state: BitArray<_, Msb0> = BitArray::from(state); let player = state[..self.player_bits].load_be::(); let elements = state[self.player_bits..].load_be::(); (player, elements) diff --git a/src/game/zero_by/variants.rs b/src/game/zero_by/variants.rs index aeb8973..9c43c85 100644 --- a/src/game/zero_by/variants.rs +++ b/src/game/zero_by/variants.rs @@ -3,11 +3,13 @@ //! This module helps parse the variant string provided to the Zero-By game //! into parameters that can help build a game session. +use bitvec::array::BitArray; use bitvec::field::BitField; +use bitvec::order::Msb0; use regex::Regex; use crate::game::error::GameError; -use crate::game::model::{Player, State}; +use crate::game::model::Player; use crate::game::zero_by::{Session, NAME}; use crate::util::min_ubits; @@ -39,13 +41,13 @@ pub fn parse_variant(variant: String) -> Result { let players = parse_player_count(¶ms)?; let start_elems = params[1]; - let mut start_state = State::ZERO; + let mut start_state: BitArray<_, Msb0> = BitArray::ZERO; let player_bits = min_ubits(players as u64); start_state[..player_bits].store_be(Player::default()); start_state[player_bits..].store_be(start_elems); Ok(Session { - start_state, + start_state: start_state.data, start_elems, player_bits, players, diff --git a/src/solver/algorithm/strong/acyclic.rs b/src/solver/algorithm/strong/acyclic.rs index f3e1100..fa0e713 100644 --- a/src/solver/algorithm/strong/acyclic.rs +++ b/src/solver/algorithm/strong/acyclic.rs @@ -2,10 +2,13 @@ //! //! This module implements strong acyclic solving routines. +use std::path::Path; + use anyhow::{Context, Result}; +use crate::database::engine::sled; use crate::database::record::mur; -use crate::database::Map; +use crate::database::{ByteMap, Persistent, ProtoRelational}; use crate::game::model::PlayerCount; use crate::game::Information; use crate::game::{Bounded, Transition}; @@ -15,12 +18,6 @@ use crate::solver::UtilityType; use crate::solver::{IntegerUtility, Sequential}; use crate::util::Identify; -/* CONSTANTS */ - -/// This string is added as a postfix to a game variant encoding to create a -/// table name for its solution set within a given database. -pub const SOLUTION_TABLE_POSTFIX: &str = "solution"; - /* SOLVERS */ pub fn solver( @@ -35,7 +32,13 @@ where + Identify + Information, { - todo!() + let db = sled::SledDatabase::new(Path::new("~/tmp/"))?; + let mut solution = db.namespace( + mur::schema(N, UtilityType::Integer, true, false)?, + &G::info().name, + )?; + + backward_induction(&mut solution, game) } /* SOLVING ALGORITHMS */ @@ -49,7 +52,7 @@ fn backward_induction( game: &G, ) -> Result<()> where - M: Map, + M: ByteMap, G: Transition + Bounded + IntegerUtility + Sequential, { let mut stack = Vec::new(); @@ -59,7 +62,7 @@ where let mut buf = new_record::() .context("Failed to create placeholder record.")?; - if solution.get(&curr).is_none() { + if solution.get(&curr)?.is_none() { solution.insert(&curr, &buf)?; if game.end(curr) { buf = new_record::() @@ -74,11 +77,12 @@ where solution.insert(&curr, &buf)?; } else { stack.push(curr); - stack.extend( - children - .iter() - .filter(|&x| solution.get(x).is_none()), - ); + stack.extend(children.iter().filter(|x| { + solution + .get(x) + .expect("Database GET error.") + .is_none() + })); } } else { let mut optimal = buf; diff --git a/src/solver/mod.rs b/src/solver/mod.rs index a0b38ba..70b353d 100644 --- a/src/solver/mod.rs +++ b/src/solver/mod.rs @@ -6,10 +6,12 @@ //! solutions, or finding "solutions" under different game-theoretic definitions //! of that word. -use crate::game::model::{ - Partition, Player, PlayerCount, State, StateCount, - DEFAULT_STATE_BYTES as DBYTES, -}; +use crate::game::model::Partition; +use crate::game::model::Player; +use crate::game::model::PlayerCount; +use crate::game::model::State; +use crate::game::model::StateCount; +use crate::game::model::DEFAULT_STATE_BYTES as DBYTES; use crate::solver::model::{IUtility, RUtility, SUtility}; /* UTILITY MODULES */ @@ -54,6 +56,12 @@ pub mod algorithm { } } +/* CONSTANTS */ + +/// This string is added as a postfix to a game variant encoding to create a +/// table name for its solution set within a given database. +pub const SOLUTION_TABLE_POSTFIX: &str = "solution"; + /* DEFINITIONS */ /// Denotes the quantization level of utility values under consideration. @@ -272,6 +280,7 @@ pub trait ClassicPuzzle { /* BLANKET IMPLEMENTATIONS */ +// All N-player integer-utility games are also N-player real-utility games. impl RealUtility for G where G: IntegerUtility, @@ -287,6 +296,7 @@ where } } +// All N-player simple-utility games are also N-player integer-utility games. impl IntegerUtility for G where G: SimpleUtility, @@ -302,6 +312,7 @@ where } } +// All 2-player zero-sum games are also 2-player simple-utility games. impl SimpleUtility<2, B> for G where G: Sequential<2, B> + ClassicGame, @@ -317,6 +328,7 @@ where } } +// All puzzles are also 1-player simple-utility games. impl SimpleUtility<1, B> for G where G: ClassicPuzzle, diff --git a/src/test.rs b/src/test.rs index f1fb756..e5de37a 100644 --- a/src/test.rs +++ b/src/test.rs @@ -7,11 +7,10 @@ use anyhow::{bail, Context, Result}; use strum_macros::Display; -use std::{ - env, fs, - path::{self, PathBuf}, - sync::RwLock, -}; +use std::env; +use std::fs; +use std::path::PathBuf; +use std::sync::RwLock; /* CONSTANTS */ @@ -64,7 +63,7 @@ pub fn test_setting() -> Result { pub fn get_directory( data: DevelopmentData, module: PathBuf, -) -> Result { +) -> Result { let root = find_cargo_lock_directory() .context("Failed to find project root directory.")?; @@ -94,7 +93,7 @@ pub fn get_directory( /* HELPER FUNCTIONS */ /// Searches for a parent directory containing a `Cargo.lock` file. -fn find_cargo_lock_directory() -> Result { +fn find_cargo_lock_directory() -> Result { let _lock = DIRECTORY_LOCK.read().unwrap(); let mut cwd = env::current_dir()?; loop {