diff --git a/src/database/mod.rs b/src/database/mod.rs index 851792e..8ec7dea 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -9,7 +9,7 @@ use anyhow::Result; use std::path::{Path, PathBuf}; use crate::database::model::{Key, SequenceKey, Value}; -use crate::solver::RecordType; +use crate::game::model::PlayerCount; /* RE-EXPORTS */ @@ -30,6 +30,13 @@ pub mod volatile; pub mod vector; pub mod lsmt; +pub mod record { + pub mod mur; + pub mod sur; + pub mod rem; + pub mod dtr; +} + /* DEFINITIONS */ /// Indicates whether the database implementation should store the data it is @@ -43,6 +50,8 @@ pub enum Persistence { /// where each name is unique and the size is a number of bits. This is used to /// "interpret" the raw data within records into meaningful features. pub struct Schema { + attribute_count: usize, + table_name: String, attributes: Vec, record: Option, size: usize, @@ -79,6 +88,21 @@ pub enum Datatype { CSTR, } +/// A record layout that can be used to encode and decode the attributes stored +/// in serialized records. This is stored in database table schemas so that it +/// can be retrieved later for deserialization. +#[derive(Clone, Copy)] +pub enum RecordType { + /// Multi-Utility Remoteness record for a specific number of players. + MUR(PlayerCount), + /// Simple Utility Remoteness record for a specific number of players. + SUR(PlayerCount), + /// Remoteness record (no utilities). + REM, + /// Directory table record. + DTR, +} + /* DATABASE INTERFACES */ /// Represents the behavior of a Key-Value Store generic over a [`Record`] type. @@ -159,10 +183,25 @@ impl Schema { self.size } + /// Returns the number of attributes in the schema. + pub fn attribute_count(&self) -> usize { + self.attribute_count + } + + /// Returns the name of the table this schema belongs to. + pub fn table_name(&self) -> &str { + &self.table_name + } + /// Returns the record type associated with this schema, if any. - pub fn record(&self) -> Option { + pub fn datatype(&self) -> Option { self.record } + + /// Returns the attributes contained in this schema. + pub fn attributes(&self) -> &[Attribute] { + &self.attributes + } } impl Attribute { diff --git a/src/database/record/dtr.rs b/src/database/record/dtr.rs new file mode 100644 index 0000000..f23c60f --- /dev/null +++ b/src/database/record/dtr.rs @@ -0,0 +1,161 @@ +//! # Directory Table Record (DTR) Module +//! +//! TODO + +use anyhow::Result; +use bitvec::order::Msb0; +use bitvec::slice::BitSlice; +use bitvec::{bitarr, BitArr}; + +use crate::database::util::SchemaBuilder; +use crate::database::{Attribute, Datatype, Record, RecordType, Schema}; +use crate::util::min_ubits; + +/* CONSTANTS */ + +/// The maximum number of ASCII characters (bytes) that can be used for +/// attribute names. This is needed to determine the minimum bits needed to +/// persist attributes' names. +pub const MAX_ATTRIBUTE_NAME_BYTES: usize = 80; + +/// The highest number of record data types that can ever be added to the +/// `DataType` enumeration. This is necessary to recover the programmatic +/// representation of an attribute datatype from a serialized source, keeping +/// in mind an extent of backwards and forwards compatibility. +pub const MAX_SUPPORTED_DATA_TYPES: usize = 1024; + +/// The highest number of record type variants that can ever be added to the +/// `RecordType` enumeration. This is necessary to recover the programmatic +/// representation of a table record type from a serialized source, keeping in +/// mind an extent of backwards and forwards compatibility. +pub const MAX_SUPPORTED_RECORD_TYPES: usize = 1024; + +/// The maximum number of ASCII characters (bytes) that can be used for a table +/// name (including a null terminator). +pub const MAX_TABLE_NAME_BYTES: usize = 80; + +/// The highest number of attributes that can be present in a single table. This +/// needs to be known to fix the directory table record width at a constant. +pub const MAX_SCHEMA_ATTRIBUTES: usize = 128; + +/// The maximum bit length of a single attribute. This needs to be known to +/// determine the minimum bits needed to persist attribute sizes. +pub const MAX_ATTRIBUTE_SIZE: usize = 512; + +/// The total size of a directory table record in bits. About 10 kB. +pub const BUFFER_SIZE: usize = (MAX_TABLE_NAME_BYTES * 8) + + min_ubits(MAX_SUPPORTED_RECORD_TYPES as u64) + + min_ubits(MAX_SCHEMA_ATTRIBUTES as u64) + + (((MAX_ATTRIBUTE_NAME_BYTES * 8) + + min_ubits(MAX_ATTRIBUTE_SIZE as u64) + + min_ubits(MAX_SUPPORTED_DATA_TYPES as u64)) + * MAX_SCHEMA_ATTRIBUTES); + +/* SCHEMA GENERATOR */ + +/// Return the database table schema associated with a record instance. +pub fn schema(name: &str) -> Result { + let mut schema = SchemaBuilder::new(name) + .of(RecordType::DTR) + .add(Attribute::new( + "table_name", + Datatype::CSTR, + MAX_TABLE_NAME_BYTES * 8, + ))? + .add(Attribute::new( + "record_type", + Datatype::ENUM, + min_ubits(MAX_SUPPORTED_RECORD_TYPES as u64), + ))? + .add(Attribute::new( + "attr_count", + Datatype::UINT, + min_ubits(MAX_SCHEMA_ATTRIBUTES as u64), + ))?; + + for i in 0..MAX_SCHEMA_ATTRIBUTES { + let name_prefix = format!("attr{}", i); + schema = schema + .add(Attribute::new( + &format!("{}_name", name_prefix), + Datatype::CSTR, + MAX_ATTRIBUTE_NAME_BYTES * 8, + ))? + .add(Attribute::new( + &format!("{}_size", name_prefix), + Datatype::UINT, + min_ubits(MAX_ATTRIBUTE_SIZE as u64), + ))? + .add(Attribute::new( + &format!("{}_type", name_prefix), + Datatype::ENUM, + min_ubits(MAX_SUPPORTED_DATA_TYPES as u64), + ))?; + } + + Ok(schema.build()) +} + +/* RECORD IMPLEMENTATION */ + +/// TODO +pub struct RecordBuffer { + buf: BitArr!(for BUFFER_SIZE, in u8, Msb0), +} + +impl RecordBuffer { + fn new() -> Self { + Self { + buf: bitarr!(u8, Msb0; 0; BUFFER_SIZE), + } + } +} + +impl Record for RecordBuffer { + #[inline(always)] + fn raw(&self) -> &BitSlice { + &self.buf + } +} + +impl TryInto for Schema { + type Error = anyhow::Error; + + fn try_into(self) -> Result { + let mut buf = RecordBuffer::new(); + buf.set_datatype(self.datatype())?; + buf.set_table_name(&self.table_name())?; + buf.set_attribute_count(self.attribute_count())?; + + for attr in self.attributes() { + buf.set_attribute(attr)?; + } + + Ok(buf) + } +} + +impl TryInto for RecordBuffer { + type Error = anyhow::Error; + fn try_into(self) -> Result { + todo!() + } +} + +impl RecordBuffer { + fn set_table_name(&mut self, name: &str) -> Result<()> { + todo!() + } + + fn set_datatype(&mut self, variant: Option) -> Result<()> { + todo!() + } + + fn set_attribute_count(&mut self, count: usize) -> Result<()> { + todo!() + } + + fn set_attribute(&mut self, attribute: &Attribute) -> Result<()> { + todo!() + } +} diff --git a/src/solver/record/mur.rs b/src/database/record/mur.rs similarity index 98% rename from src/solver/record/mur.rs rename to src/database/record/mur.rs index fbc3814..21d0727 100644 --- a/src/solver/record/mur.rs +++ b/src/database/record/mur.rs @@ -10,11 +10,11 @@ use bitvec::order::Msb0; use bitvec::slice::BitSlice; use bitvec::{bitarr, BitArr}; +use crate::database::RecordType; use crate::database::{Attribute, Datatype, Record, Schema, SchemaBuilder}; use crate::game::model::{Player, PlayerCount}; use crate::solver::error::SolverError::RecordViolation; use crate::solver::model::{IUtility, Remoteness}; -use crate::solver::RecordType; use crate::util; /* CONSTANTS */ @@ -32,7 +32,7 @@ pub const UTILITY_SIZE: usize = 8; /// Return the database table schema associated with a record instance with /// a specific number of `players` under this record implementation. -pub fn schema(players: PlayerCount) -> Result { +pub fn schema(players: PlayerCount, name: &str) -> Result { if RecordBuffer::bit_size(players) > BUFFER_SIZE { bail!(RecordViolation { name: RecordType::MUR(players).to_string(), @@ -44,7 +44,7 @@ pub fn schema(players: PlayerCount) -> Result { ), }) } else { - let mut schema = SchemaBuilder::new().of(RecordType::MUR(players)); + let mut schema = SchemaBuilder::new(name).of(RecordType::MUR(players)); for i in 0..players { let name = &format!("P{i} utility"); diff --git a/src/solver/record/rem.rs b/src/database/record/rem.rs similarity index 97% rename from src/solver/record/rem.rs rename to src/database/record/rem.rs index 18bfa4b..babcbbc 100644 --- a/src/solver/record/rem.rs +++ b/src/database/record/rem.rs @@ -8,10 +8,10 @@ use bitvec::order::Msb0; use bitvec::slice::BitSlice; use bitvec::{bitarr, BitArr}; +use crate::database::RecordType; use crate::database::{Attribute, Datatype, Record, Schema, SchemaBuilder}; use crate::solver::error::SolverError::RecordViolation; use crate::solver::model::Remoteness; -use crate::solver::RecordType; use crate::util; /* CONSTANTS */ @@ -25,8 +25,8 @@ pub const BUFFER_SIZE: usize = 16; /* SCHEMA GENERATOR */ /// Return the database table schema associated with a record instance -pub fn schema() -> Result { - let mut schema = SchemaBuilder::new().of(RecordType::REM); +pub fn schema(name: &str) -> Result { + let mut schema = SchemaBuilder::new(name).of(RecordType::REM); let name = "State remoteness"; let data = Datatype::UINT; diff --git a/src/solver/record/sur.rs b/src/database/record/sur.rs similarity index 98% rename from src/solver/record/sur.rs rename to src/database/record/sur.rs index 2caf509..a579843 100644 --- a/src/solver/record/sur.rs +++ b/src/database/record/sur.rs @@ -10,11 +10,11 @@ use bitvec::order::Msb0; use bitvec::slice::BitSlice; use bitvec::{bitarr, BitArr}; +use crate::database::RecordType; use crate::database::{Attribute, Datatype, Record, Schema, SchemaBuilder}; use crate::game::model::{Player, PlayerCount}; use crate::solver::error::SolverError::RecordViolation; use crate::solver::model::{Remoteness, SUtility}; -use crate::solver::RecordType; use crate::util; /* CONSTANTS */ @@ -32,7 +32,7 @@ pub const UTILITY_SIZE: usize = 2; /// Return the database table schema associated with a record instance with /// a specific number of `players` under this record implementation. -pub fn schema(players: PlayerCount) -> Result { +pub fn schema(players: PlayerCount, name: &str) -> Result { if RecordBuffer::bit_size(players) > BUFFER_SIZE { bail!(RecordViolation { name: RecordType::SUR(players).to_string(), @@ -44,8 +44,7 @@ pub fn schema(players: PlayerCount) -> Result { ), }) } else { - let mut schema = SchemaBuilder::new().of(RecordType::SUR(players)); - + let mut schema = SchemaBuilder::new(name).of(RecordType::SUR(players)); for i in 0..players { let name = &format!("P{i} utility"); let data = Datatype::ENUM; diff --git a/src/database/util.rs b/src/database/util.rs index 3c96e46..d28ffcb 100644 --- a/src/database/util.rs +++ b/src/database/util.rs @@ -5,18 +5,18 @@ //! behavior; in particular, each database is to own a `util` module as needed, //! leaving this for cases where their functionality intersects. -use std::fmt::Display; - use anyhow::anyhow; use anyhow::Result; +use std::fmt::Display; use std::sync::Mutex; use crate::database::error::DatabaseError; +use crate::database::record; use crate::database::Attribute; use crate::database::Datatype; +use crate::database::RecordType; use crate::database::Schema; -use crate::solver::RecordType; /* DEFINITIONS */ @@ -24,6 +24,8 @@ use crate::solver::RecordType; /// provided attributes. This is here to help ensure schemas are not changed /// accidentally after being instantiated. pub struct SchemaBuilder { + attribute_count: usize, + table_name: String, attributes: Vec, record: Option, size: usize, @@ -46,8 +48,10 @@ pub struct KeySequencer { impl SchemaBuilder { /// Returns a new instance of a `SchemaBuilder`, which can be used to /// declaratively construct a new record `Schema`. - pub fn new() -> Self { + pub fn new(name: &str) -> Self { SchemaBuilder { + attribute_count: 0, + table_name: name.to_string(), attributes: Vec::new(), record: None, size: 0, @@ -58,6 +62,7 @@ impl SchemaBuilder { /// if adding `attr` to the schema would result in an invalid state. pub fn add(mut self, attr: Attribute) -> Result { self.check_attribute_validity(&attr)?; + self.attribute_count += 1; self.size += attr.size(); Ok(self) } @@ -71,6 +76,8 @@ impl SchemaBuilder { /// Constructs the schema using the current state of the `SchemaBuilder`. pub fn build(self) -> Schema { Schema { + attribute_count: self.attribute_count, + table_name: self.table_name, attributes: self.attributes, record: self.record, size: self.size, @@ -129,6 +136,19 @@ impl SchemaBuilder { } } +/* RECORD TYPE IMPLEMENTATION */ + +impl RecordType { + pub fn try_into_schema(self, name: &str) -> Result { + match self { + RecordType::MUR(players) => record::mur::schema(players, name), + RecordType::SUR(players) => record::sur::schema(players, name), + RecordType::REM => record::rem::schema(name), + RecordType::DTR => record::dtr::schema(name), + } + } +} + /* KEY SEQUENCER IMPLEMENTATION */ impl KeySequencer { @@ -196,3 +216,21 @@ impl<'a> Iterator for SchemaIterator<'a> { .get(self.index - 1) } } + +impl Display for RecordType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RecordType::MUR(players) => { + write!(f, "Real Utility Remoteness ({players} players)") + }, + RecordType::SUR(players) => { + write!( + f, + "Simple Utility Remoteness ({players} players)", + ) + }, + RecordType::REM => write!(f, "Remoteness (no utility)"), + RecordType::DTR => write!(f, "Directory Table Record"), + } + } +} diff --git a/src/database/volatile/mod.rs b/src/database/volatile/mod.rs index 29d9ccf..78d843b 100644 --- a/src/database/volatile/mod.rs +++ b/src/database/volatile/mod.rs @@ -15,15 +15,13 @@ use crate::database::volatile::resource::Request; use crate::database::volatile::resource::ResourceManager; use crate::database::volatile::transaction::Transaction; use crate::database::volatile::transaction::TransactionManager; -use crate::database::KVStore; +use crate::database::Schema; /* RE-EXPORTS */ pub use resource::Resource; pub use transaction::WorkingSet; -use super::Schema; - /* MODULES */ mod transaction; @@ -144,7 +142,7 @@ impl Database { // Ok(id) } - pub fn drop_resource(&self, id: ResourceID) -> Result<()> { + pub fn drop_resource(&self, name: &str) -> Result<()> { let directory = self .directory .expect("Database directory table found uninitialized."); @@ -154,8 +152,8 @@ impl Database { read: vec![], })?; - txn.drop_resource(id)?; let mut directory = txn.write(directory)?; + txn.drop_resource(id)?; todo!() // directory.remove(id.into()); // Ok(id) diff --git a/src/database/volatile/resource/manager.rs b/src/database/volatile/resource/manager.rs index 6b16f98..b90ac43 100644 --- a/src/database/volatile/resource/manager.rs +++ b/src/database/volatile/resource/manager.rs @@ -222,7 +222,7 @@ impl ResourceManager { .get(&id) .expect("Attempted to fetch non-existent resource lock."); - handles.add_writing(id, lock.clone()); + handles.add_write(id, lock.clone()); } for &id in request.read.iter() { @@ -231,7 +231,7 @@ impl ResourceManager { .get(&id) .expect("Attempted to fetch non-existent resource lock."); - handles.add_reading(id, lock.clone()); + handles.add_read(id, lock.clone()); } handles diff --git a/src/database/volatile/transaction/mod.rs b/src/database/volatile/transaction/mod.rs index a87ff57..2658b82 100644 --- a/src/database/volatile/transaction/mod.rs +++ b/src/database/volatile/transaction/mod.rs @@ -58,11 +58,11 @@ impl WorkingSet<'_> { } impl ResourceHandles { - pub fn add_reading(&mut self, id: ResourceID, lock: Arc>) { + pub fn add_read(&mut self, id: ResourceID, lock: Arc>) { self.read.insert(id, lock); } - pub fn add_writing(&mut self, id: ResourceID, lock: Arc>) { + pub fn add_write(&mut self, id: ResourceID, lock: Arc>) { self.write.insert(id, lock); } diff --git a/src/solver/algorithm/strong/acyclic.rs b/src/solver/algorithm/strong/acyclic.rs index 9a258f8..6d29934 100644 --- a/src/solver/algorithm/strong/acyclic.rs +++ b/src/solver/algorithm/strong/acyclic.rs @@ -4,14 +4,16 @@ use anyhow::{Context, Result}; +use crate::database::record::mur::RecordBuffer; use crate::database::volatile; use crate::database::KVStore; +use crate::database::RecordType; use crate::game::model::PlayerCount; +use crate::game::Information; use crate::game::{Bounded, Transition}; use crate::interface::IOMode; use crate::solver::model::{IUtility, Remoteness}; -use crate::solver::record::mur::RecordBuffer; -use crate::solver::{IntegerUtility, RecordType, Sequential}; +use crate::solver::{IntegerUtility, Sequential}; use crate::util::Identify; /* SOLVERS */ @@ -25,21 +27,30 @@ where + Bounded + IntegerUtility + Sequential - + Identify, + + Identify + + Information, { - let schema = RecordType::MUR(N) - .try_into() + let table_name = format!("{}_solution", G::info().name); + let db = volatile::Database::new()?; + let table_schema = RecordType::MUR(N) + .try_into_schema(&table_name) .context("Failed to create table schema for solver records.")?; - let db = volatile::Database::new()?; - let solution = db - .create_resource(schema) + match mode { + IOMode::Constructive => (), + IOMode::Overwrite => db + .drop_resource(&table_name) + .context("Failed to drop database table being overwritten.")?, + } + + let solution_table = db + .create_resource(table_schema) .context("Failed to create database table for solution set.")?; db.build_transaction() - .writing(solution) + .writing(solution_table) .action(|mut working_set| { - let mut t = working_set.get_writing(solution); + let mut t = working_set.get_writing(solution_table); backward_induction(&mut (*t), game) .context("Solving algorithm encountered an error.") }) diff --git a/src/solver/mod.rs b/src/solver/mod.rs index 722bc0c..806d99a 100644 --- a/src/solver/mod.rs +++ b/src/solver/mod.rs @@ -23,15 +23,6 @@ pub mod model; /* MODULES */ -/// Implementations of records that can be used by solving algorithms to store -/// or persist the information they compute about a game, and communicate it to -/// a database system. -pub mod record { - pub mod mur; - pub mod sur; - pub mod rem; -} - /// Implementations of algorithms that can consume game implementations and /// compute different features of interest associated with groups of states or /// particular states. @@ -63,21 +54,6 @@ pub mod algorithm { } } -/* SOLVER DATABASE RECORDS */ - -/// A record layout that can be used to encode and decode the attributes stored -/// in serialized records. This is stored in database table schemas so that it -/// can be retrieved later for deserialization. -#[derive(Clone, Copy)] -pub enum RecordType { - /// Multi-Utility Remoteness record for a specific number of players. - MUR(PlayerCount), - /// Simple Utility Remoteness record for a specific number of players. - SUR(PlayerCount), - /// Remoteness record (no utilities). - REM, -} - /* STRUCTURAL INTERFACES */ /// Indicates that a discrete game is played sequentially, which allows for diff --git a/src/solver/util.rs b/src/solver/util.rs index 1b7e96f..22071a9 100644 --- a/src/solver/util.rs +++ b/src/solver/util.rs @@ -3,44 +3,10 @@ //! This module makes room for common utility routines used throughout the //! `crate::solver` module. -use std::fmt::Display; use std::ops::Not; -use crate::database::Schema; use crate::solver::error::SolverError; use crate::solver::model::{IUtility, RUtility, SUtility}; -use crate::solver::{record, RecordType}; - -/* RECORD TYPE IMPLEMENTATIONS */ - -impl Display for RecordType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - RecordType::MUR(players) => { - write!(f, "Real Utility Remoteness ({players} players)") - }, - RecordType::SUR(players) => { - write!( - f, - "Simple Utility Remoteness ({players} players)", - ) - }, - RecordType::REM => write!(f, "Remoteness (no utility)"), - } - } -} - -impl TryInto for RecordType { - type Error = anyhow::Error; - - fn try_into(self) -> Result { - match self { - RecordType::MUR(players) => record::mur::schema(players), - RecordType::SUR(players) => record::sur::schema(players), - RecordType::REM => record::rem::schema(), - } - } -} /* CONVERSIONS INTO SIMPLE UTILITY */