diff --git a/Cargo.lock b/Cargo.lock index 9a15e36..b8bc90b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2522,9 +2522,9 @@ dependencies = [ [[package]] name = "refuse" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d52c239beebd4e207fcaab44b66275da64216b5929f60e2fed60fe91fccba3" +checksum = "b78bee46da05d3bc659f5f4243942bceaf5e81aebe353c55aff24f3b5f56e865" dependencies = [ "crossbeam-utils", "flume", @@ -2536,9 +2536,9 @@ dependencies = [ [[package]] name = "refuse-macros" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ae04ac2ecbcd81a68a7dc68b5c2d189020f8fd0b088a17f31df6438a489e9e" +checksum = "6ebe8a2c32f31b7b590cf24b6c0ebb49f872038bb721cddfd8c2e0ef1a7fe65e" dependencies = [ "attribute-derive", "manyhow 0.11.3", @@ -2549,12 +2549,13 @@ dependencies = [ [[package]] name = "refuse-pool" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b14531f83f81dab6956bad25849966415d2e3ac75c0e2ad813a8334b763b1e2b" +checksum = "8f8df1748d68ba190865a971e2a918d65261cdbc544ee9779699222bdb7950ec" dependencies = [ "ahash", "hashbrown", + "parking_lot", "refuse", ] diff --git a/Cargo.toml b/Cargo.toml index a66f304..aea1728 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,8 @@ cushy = { git = "https://github.com/khonsulabs/cushy" } alot = "0.3.1" crossbeam-utils = "0.8.19" parking_lot = "0.12.1" -refuse = "0.0.5" -refuse-pool = "0.0.5" +refuse = "0.0.6" +refuse-pool = "0.0.6" serde = { version = "1.0.195", features = ["derive", "rc"] } tracing = "0.1.40" tracing-subscriber = "0.3.18" diff --git a/muse-lang/src/compiler.rs b/muse-lang/src/compiler.rs index 8795a7e..6155e5c 100644 --- a/muse-lang/src/compiler.rs +++ b/muse-lang/src/compiler.rs @@ -24,6 +24,7 @@ use syntax::{ use self::syntax::StructureMember; use crate::runtime::symbol::Symbol; use crate::runtime::types::{self, BitcodeEnum, BitcodeStruct}; +use crate::runtime::value::Primitive; use crate::vm::bitcode::{ Access, Accessable, BinaryKind, BitcodeBlock, BitcodeFunction, FaultKind, Label, Op, OpDestination, ValueOrSource, @@ -157,7 +158,7 @@ impl Compiler { Err(err) => { let range = err.range(); self.errors.push(err.map(Error::SigilSyntax)); - Ranged::new(range, Expression::Literal(Literal::Nil)) + Ranged::new(range, Expression::Literal(Literal::default())) } } } @@ -877,7 +878,7 @@ impl<'a> Scope<'a> { for (variant, value) in e.variants.enclosed.iter().zip(0..) { variants.push(types::EnumVariant { name: variant.name.0.clone(), - value: ValueOrSource::UInt(value), + value: ValueOrSource::Primitive(Primitive::UInt(value)), }); } @@ -991,11 +992,11 @@ impl<'a> Scope<'a> { fn compile_literal(&mut self, literal: &Literal, dest: OpDestination) { match literal { - Literal::Nil => self.compiler.code.copy((), dest), - Literal::Bool(bool) => self.compiler.code.copy(*bool, dest), - Literal::Int(int) => self.compiler.code.copy(*int, dest), - Literal::UInt(int) => self.compiler.code.copy(*int, dest), - Literal::Float(float) => self.compiler.code.copy(*float, dest), + Literal::Primitive(Primitive::Nil) => self.compiler.code.copy((), dest), + Literal::Primitive(Primitive::Bool(bool)) => self.compiler.code.copy(*bool, dest), + Literal::Primitive(Primitive::Int(int)) => self.compiler.code.copy(*int, dest), + Literal::Primitive(Primitive::UInt(int)) => self.compiler.code.copy(*int, dest), + Literal::Primitive(Primitive::Float(float)) => self.compiler.code.copy(*float, dest), Literal::String(string) => { self.compiler.code.copy(string.clone(), Register(0)); self.compiler.code.call(Symbol::from("$.core.String"), 1); @@ -1369,11 +1370,19 @@ impl<'a> Scope<'a> { for entry in &entries.enclosed { self.compiler.code.set_current_source_range(pattern.range()); let key = match &entry.key.0 { - EntryKeyPattern::Nil => ValueOrSource::Nil, - EntryKeyPattern::Bool(value) => ValueOrSource::Bool(*value), - EntryKeyPattern::Int(value) => ValueOrSource::Int(*value), - EntryKeyPattern::UInt(value) => ValueOrSource::UInt(*value), - EntryKeyPattern::Float(value) => ValueOrSource::Float(*value), + EntryKeyPattern::Nil => ValueOrSource::Primitive(Primitive::Nil), + EntryKeyPattern::Bool(value) => { + ValueOrSource::Primitive(Primitive::Bool(*value)) + } + EntryKeyPattern::Int(value) => { + ValueOrSource::Primitive(Primitive::Int(*value)) + } + EntryKeyPattern::UInt(value) => { + ValueOrSource::Primitive(Primitive::UInt(*value)) + } + EntryKeyPattern::Float(value) => { + ValueOrSource::Primitive(Primitive::Float(*value)) + } EntryKeyPattern::String(value) => { self.compiler.code.copy(value.clone(), Register(0)); self.compiler.code.call(Symbol::from("$.core.String"), 1); @@ -2276,11 +2285,7 @@ impl<'a> Scope<'a> { fn compile_source(&mut self, source: &Ranged) -> ValueOrSource { match &source.0 { Expression::Literal(literal) => match literal { - Literal::Nil => ValueOrSource::Nil, - Literal::Bool(bool) => ValueOrSource::Bool(*bool), - Literal::Int(int) => ValueOrSource::Int(*int), - Literal::UInt(int) => ValueOrSource::UInt(*int), - Literal::Float(float) => ValueOrSource::Float(*float), + Literal::Primitive(p) => ValueOrSource::Primitive(*p), Literal::Regex(regex) => ValueOrSource::Regex(regex.clone()), Literal::String(_) => { ValueOrSource::Stack(self.compile_expression_into_temporary(source)) @@ -2324,7 +2329,7 @@ impl<'a> Scope<'a> { } Expression::Macro(_) | Expression::InfixMacro(_) => { // unreachable!("macros should be expanded already") - ValueOrSource::Nil + ValueOrSource::Primitive(Primitive::Nil) } } } @@ -2357,7 +2362,7 @@ impl<'a> Scope<'a> { .as_ref() .map_or(false, |f| f == &lookup.name.0) => { - ValueOrSource::Nil + ValueOrSource::Primitive(Primitive::Nil) } Expression::Lookup(lookup) if lookup.base.is_some() => { let base = lookup.base.as_ref().expect("just matched"); diff --git a/muse-lang/src/compiler/syntax.rs b/muse-lang/src/compiler/syntax.rs index 1652cd4..f6b34c6 100644 --- a/muse-lang/src/compiler/syntax.rs +++ b/muse-lang/src/compiler/syntax.rs @@ -13,6 +13,7 @@ use serde::{Deserialize, Serialize}; use self::token::{FormatString, FormatStringPart, Paired, RegexLiteral, Token, Tokens}; use crate::runtime::exception::Exception; use crate::runtime::symbol::Symbol; +use crate::runtime::value::Primitive; use crate::vm::{ExecutionError, VmContext}; pub mod token; @@ -681,7 +682,7 @@ impl Expression { let Some(mut expression) = expressions.pop() else { return Ranged::new( (SourceId::anonymous(), 0..0), - Expression::Literal(Literal::Nil), + Expression::Literal(Literal::default()), ); }; @@ -713,7 +714,7 @@ impl Expression { impl Default for Expression { fn default() -> Self { - Self::Literal(Literal::Nil) + Self::Literal(Literal::default()) } } @@ -775,19 +776,9 @@ impl TokenizeRanged for Expression { } /// A literal value. -#[derive(Default, Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum Literal { - /// The literal `nil`. - #[default] - Nil, - /// A boolean literal. - Bool(bool), - /// A signed 64-bit integer literal. - Int(i64), - /// An unsigned 64-bit integer literal. - UInt(u64), - /// A double-precision floating point number. - Float(f64), + Primitive(Primitive), /// A string literal. String(Symbol), /// A symbol literal (`:foo`). @@ -799,12 +790,16 @@ pub enum Literal { impl TokenizeRanged for Literal { fn tokenize_ranged(&self, range: SourceRange, tokens: &mut VecDeque>) { let token = match self { - Literal::Nil => Token::Identifier(Symbol::nil_symbol().clone()), - Literal::Bool(false) => Token::Identifier(Symbol::false_symbol().clone()), - Literal::Bool(true) => Token::Identifier(Symbol::true_symbol().clone()), - Literal::Int(value) => Token::Int(*value), - Literal::UInt(value) => Token::UInt(*value), - Literal::Float(float) => Token::Float(*float), + Literal::Primitive(Primitive::Nil) => Token::Identifier(Symbol::nil_symbol().clone()), + Literal::Primitive(Primitive::Bool(false)) => { + Token::Identifier(Symbol::false_symbol().clone()) + } + Literal::Primitive(Primitive::Bool(true)) => { + Token::Identifier(Symbol::true_symbol().clone()) + } + Literal::Primitive(Primitive::Int(value)) => Token::Int(*value), + Literal::Primitive(Primitive::UInt(value)) => Token::UInt(*value), + Literal::Primitive(Primitive::Float(float)) => Token::Float(*float), Literal::String(string) => Token::String(string.clone()), Literal::Symbol(symbol) => Token::Symbol(symbol.clone()), Literal::Regex(regex) => Token::Regex(regex.clone()), @@ -813,6 +808,12 @@ impl TokenizeRanged for Literal { } } +impl Default for Literal { + fn default() -> Self { + Self::Primitive(Primitive::Nil) + } +} + /// The syntax components of the label part of a labeled expression. #[derive(Debug, Clone, PartialEq)] pub struct Label { @@ -1700,7 +1701,7 @@ fn parse_from_reader( Ok(_) | Err(Ranged(ParseError::UnexpectedEof, _)) => { results.push(tokens.ranged( tokens.last_index..tokens.last_index, - Expression::Literal(Literal::Nil), + Expression::Literal(Literal::default()), )); break; } @@ -2126,7 +2127,7 @@ impl PrefixParselet for Break { { config.parse_expression(tokens)? } else { - tokens.ranged(tokens.last_index.., Expression::Literal(Literal::Nil)) + tokens.ranged(tokens.last_index.., Expression::Literal(Literal::default())) }; Ok(tokens.ranged( @@ -2196,7 +2197,7 @@ impl PrefixParselet for Return { { config.parse_expression(tokens)? } else { - tokens.ranged(tokens.last_index.., Expression::Literal(Literal::Nil)) + tokens.ranged(tokens.last_index.., Expression::Literal(Literal::default())) }; Ok(tokens.ranged( @@ -2359,15 +2360,15 @@ impl PrefixParselet for Term { match token.0 { Token::Int(value) => Ok(Ranged::new( token.1, - Expression::Literal(Literal::Int(value)), + Expression::Literal(Literal::Primitive(Primitive::Int(value))), )), Token::UInt(value) => Ok(Ranged::new( token.1, - Expression::Literal(Literal::UInt(value)), + Expression::Literal(Literal::Primitive(Primitive::UInt(value))), )), Token::Float(value) => Ok(Ranged::new( token.1, - Expression::Literal(Literal::Float(value)), + Expression::Literal(Literal::Primitive(Primitive::Float(value))), )), Token::String(string) => Ok(Ranged::new( token.1, @@ -2569,17 +2570,17 @@ macro_rules! impl_prefix_standalone_parselet { impl_prefix_standalone_parselet!( True, Token::Identifier(Symbol::true_symbol().clone()), - Expression::Literal(Literal::Bool(true)) + Expression::Literal(Literal::Primitive(Primitive::Bool(true))) ); impl_prefix_standalone_parselet!( False, Token::Identifier(Symbol::false_symbol().clone()), - Expression::Literal(Literal::Bool(false)) + Expression::Literal(Literal::Primitive(Primitive::Bool(false))) ); impl_prefix_standalone_parselet!( Nil, Token::Identifier(Symbol::nil_symbol().clone()), - Expression::Literal(Literal::Nil) + Expression::Literal(Literal::Primitive(Primitive::Nil)) ); struct Braces; @@ -2602,7 +2603,7 @@ fn parse_block( open_brace.range().source_id, open_brace.range().end()..close_brace.range().start, ), - Expression::Literal(Literal::Nil), + Expression::Literal(Literal::default()), ), open: open_brace, close: close_brace, @@ -2686,7 +2687,8 @@ impl Braces { left.range().start.., Expression::Binary(Box::new(BinaryExpression { left, - right: tokens.ranged(tokens.last_index.., Expression::Literal(Literal::Nil)), + right: tokens + .ranged(tokens.last_index.., Expression::Literal(Literal::default())), kind: BinaryKind::Chain, operator: semicolon, })), @@ -2842,7 +2844,10 @@ impl PrefixParselet for Braces { match tokens.peek_token() { Some(Token::Close(Paired::Brace)) => { tokens.next_or_eof()?; - return Ok(tokens.ranged(open.range().start.., Expression::Literal(Literal::Nil))); + return Ok(tokens.ranged( + open.range().start.., + Expression::Literal(Literal::default()), + )); } Some(Token::Char(',')) => { tokens.next_or_eof()?; @@ -3566,7 +3571,7 @@ impl PrefixParselet for Throw { { config.parse_expression(tokens)? } else { - tokens.ranged(tokens.last_index.., Expression::Literal(Literal::Nil)) + tokens.ranged(tokens.last_index.., Expression::Literal(Literal::default())) }; Ok(tokens.ranged( @@ -4345,18 +4350,21 @@ fn parse_pattern_kind( } Token::Identifier(name) if name == Symbol::true_symbol() => { tokens.next_or_eof()?; - Ranged::new(indicator.range(), PatternKind::Literal(Literal::Bool(true))) + Ranged::new( + indicator.range(), + PatternKind::Literal(Literal::Primitive(Primitive::Bool(true))), + ) } Token::Identifier(name) if name == Symbol::false_symbol() => { tokens.next_or_eof()?; Ranged::new( indicator.range(), - PatternKind::Literal(Literal::Bool(false)), + PatternKind::Literal(Literal::Primitive(Primitive::Bool(false))), ) } Token::Identifier(name) if name == Symbol::nil_symbol() => { tokens.next_or_eof()?; - Ranged::new(indicator.range(), PatternKind::Literal(Literal::Nil)) + Ranged::new(indicator.range(), PatternKind::Literal(Literal::default())) } Token::Identifier(name) => { tokens.next_or_eof()?; @@ -4373,21 +4381,21 @@ fn parse_pattern_kind( tokens.next_or_eof()?; Ranged::new( indicator.range(), - PatternKind::Literal(Literal::Int(*value)), + PatternKind::Literal(Literal::Primitive(Primitive::Int(*value))), ) } Token::UInt(value) => { tokens.next_or_eof()?; Ranged::new( indicator.range(), - PatternKind::Literal(Literal::UInt(*value)), + PatternKind::Literal(Literal::Primitive(Primitive::UInt(*value))), ) } Token::Float(value) => { tokens.next_or_eof()?; Ranged::new( indicator.range(), - PatternKind::Literal(Literal::Float(*value)), + PatternKind::Literal(Literal::Primitive(Primitive::Float(*value))), ) } Token::Regex(_) => { @@ -4601,7 +4609,7 @@ fn parse_variable( } else { ( None, - tokens.ranged(tokens.last_index.., Expression::Literal(Literal::Nil)), + tokens.ranged(tokens.last_index.., Expression::Literal(Literal::default())), ) }; diff --git a/muse-lang/src/lib.rs b/muse-lang/src/lib.rs index 184fe79..ad819cf 100644 --- a/muse-lang/src/lib.rs +++ b/muse-lang/src/lib.rs @@ -17,11 +17,12 @@ extern crate tracing; #[macro_use] mod mock_tracing; +#[macro_use] +pub mod runtime; + pub mod compiler; pub mod vm; -pub mod runtime; - #[cfg(test)] mod tests; diff --git a/muse-lang/src/runtime.rs b/muse-lang/src/runtime.rs index af04ce3..e67b3c1 100644 --- a/muse-lang/src/runtime.rs +++ b/muse-lang/src/runtime.rs @@ -1,5 +1,8 @@ //! Types that are used within the Muse language. +#[macro_use] +pub mod value; + pub mod exception; pub mod list; pub mod map; @@ -7,4 +10,3 @@ pub mod regex; pub mod string; pub mod symbol; pub mod types; -pub mod value; diff --git a/muse-lang/src/runtime/value.rs b/muse-lang/src/runtime/value.rs index a0bf6e4..1786244 100644 --- a/muse-lang/src/runtime/value.rs +++ b/muse-lang/src/runtime/value.rs @@ -17,6 +17,7 @@ pub type ValueHasher = ahash::AHasher; use kempt::Map; use parking_lot::Mutex; use refuse::{AnyRef, AnyRoot, CollectionGuard, ContainsNoRefs, MapAs, Ref, Root, Trace}; +use serde::{Deserialize, Serialize}; use crate::runtime::string::MuseString; use crate::runtime::symbol::{Symbol, SymbolList, SymbolRef}; @@ -29,7 +30,7 @@ use crate::vm::Function; use crate::vm::{Arity, ExecutionError, Fault, VmContext}; /// A primitive virtual machine value. -#[derive(Default, Clone, Copy, Debug)] +#[derive(Default, Clone, Copy, Debug, Serialize, Deserialize)] pub enum Primitive { /// A value representing nothing. #[default] @@ -840,6 +841,12 @@ impl Primitive { } } +impl PartialEq for Primitive { + fn eq(&self, other: &Self) -> bool { + self.equals(other).unwrap_or(false) + } +} + /// A Muse virtual machine value. #[derive(Clone, Copy, Debug)] pub enum Value { @@ -1423,14 +1430,11 @@ impl Value { #[cfg(feature = "dispatched")] pub(crate) fn as_source(&self, guard: &CollectionGuard<'_>) -> ValueOrSource { match self { - Value::Primitive(Primitive::Nil) => ValueOrSource::Nil, - Value::Primitive(Primitive::Bool(value)) => ValueOrSource::Bool(*value), - Value::Primitive(Primitive::Int(value)) => ValueOrSource::Int(*value), - Value::Primitive(Primitive::UInt(value)) => ValueOrSource::UInt(*value), - Value::Primitive(Primitive::Float(value)) => ValueOrSource::Float(*value), - Value::Symbol(value) => value - .upgrade(guard) - .map_or(ValueOrSource::Nil, ValueOrSource::Symbol), + Value::Primitive(p) => ValueOrSource::Primitive(*p), + Value::Symbol(value) => value.upgrade(guard).map_or( + ValueOrSource::Primitive(Primitive::Nil), + ValueOrSource::Symbol, + ), Value::Dynamic(value) => { if let Some(func) = value.downcast_ref::(guard) { ValueOrSource::Function(BitcodeFunction::from_function(func, guard)) @@ -1502,6 +1506,7 @@ impl_from!(Primitive, u32, UInt); impl_from!(Primitive, u64, UInt); impl_from!(Primitive, bool, Bool); +#[macro_export] macro_rules! impl_from_primitive { ($on:ident, $from:ty, $variant:ident) => { impl From<$from> for $on { diff --git a/muse-lang/src/vm.rs b/muse-lang/src/vm.rs index 35626c5..b03b9c3 100644 --- a/muse-lang/src/vm.rs +++ b/muse-lang/src/vm.rs @@ -44,9 +44,9 @@ use crate::runtime::regex::MuseRegex; use crate::runtime::symbol::{IntoOptionSymbol, Symbol, SymbolRef}; use crate::runtime::types::{BitcodeEnum, BitcodeStruct}; #[cfg(not(feature = "dispatched"))] -use crate::runtime::value::{ContextOrGuard, Primitive}; +use crate::runtime::value::ContextOrGuard; use crate::runtime::value::{ - CustomType, Dynamic, Rooted, RustType, StaticRustFunctionTable, Value, + CustomType, Dynamic, Primitive, Rooted, RustType, StaticRustFunctionTable, Value, }; pub mod bitcode; @@ -945,6 +945,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { Ordering::Equal => return Ok(StepResult::Complete), Ordering::Greater => return Err(Fault::InvalidInstructionAddress), }; + trace!("Executing {instruction:?}"); let next_instruction = StepResult::from(address.checked_add(1)); match instruction.execute(self) { Ok(ControlFlow::Continue(())) | Err(Fault::FrameChanged) => Ok(next_instruction), @@ -1911,11 +1912,7 @@ impl VmContext<'_, '_> { fn op_load(&mut self, code_index: usize, value: LoadedSource) -> Result { match value { - LoadedSource::Nil => Ok(Value::NIL), - LoadedSource::Bool(v) => Ok(Value::Primitive(Primitive::Bool(v))), - LoadedSource::Int(v) => Ok(Value::Primitive(Primitive::Int(v))), - LoadedSource::UInt(v) => Ok(Value::Primitive(Primitive::UInt(v))), - LoadedSource::Float(v) => Ok(Value::Primitive(Primitive::Float(v))), + LoadedSource::Primitive(p) => Ok(Value::Primitive(p)), LoadedSource::Symbol(v) => self.op_load_symbol(code_index, v).map(Value::Symbol), LoadedSource::Register(v) => Ok(self[v]), LoadedSource::Stack(v) => self @@ -2160,7 +2157,7 @@ pub enum Fault { IncorrectNumberOfArguments, /// A pattern could not be matched. PatternMismatch, - /// An unsupported operation was performed on [`Value::Nil`]. + /// An unsupported operation was performed on [`Value::NIL`]. OperationOnNil, /// A value was freed. /// @@ -2606,12 +2603,7 @@ impl CodeData { fn load_source(&mut self, source: &ValueOrSource, guard: &CollectionGuard) -> LoadedSource { match source { - ValueOrSource::Nil => LoadedSource::Nil, - ValueOrSource::Bool(bool) => LoadedSource::Bool(*bool), - - ValueOrSource::Int(int) => LoadedSource::Int(*int), - ValueOrSource::UInt(uint) => LoadedSource::UInt(*uint), - ValueOrSource::Float(float) => LoadedSource::Float(*float), + ValueOrSource::Primitive(p) => LoadedSource::Primitive(*p), ValueOrSource::Symbol(sym) => LoadedSource::Symbol(self.push_symbol(sym.clone())), ValueOrSource::Regex(regex) => LoadedSource::Regex(self.push_regex(regex, guard)), ValueOrSource::Register(reg) => LoadedSource::Register(*reg), @@ -3039,11 +3031,7 @@ impl LoadedBinary { #[derive(Debug, Clone, Copy, PartialEq)] enum LoadedSource { - Nil, - Bool(bool), - Int(i64), - UInt(u64), - Float(f64), + Primitive(Primitive), Symbol(usize), Register(Register), Function(usize), diff --git a/muse-lang/src/vm/bitcode.rs b/muse-lang/src/vm/bitcode.rs index 3103f58..a202c25 100644 --- a/muse-lang/src/vm/bitcode.rs +++ b/muse-lang/src/vm/bitcode.rs @@ -22,20 +22,12 @@ use crate::vm::Stack; /// A value or a source of a value. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ValueOrSource { - /// [`Value::Nil`](crate::runtime::value::Value::Nil) - Nil, - /// [`Value::Nil`](crate::runtime::value::Value::Bool) - Bool(bool), - /// [`Value::Nil`](crate::runtime::value::Value::Int) - Int(i64), - /// [`Value::Nil`](crate::runtime::value::Value::UInt) - UInt(u64), - /// [`Value::Nil`](crate::runtime::value::Value::Float) - Float(f64), - /// [`Value::Nil`](crate::runtime::value::Value::Symbol) + /// [`Value::Primitive`] + Primitive(Primitive), + /// [`Value::Symbol`] Symbol(Symbol), /// A regular expression literal. When loaded, it is compiled into a - /// [`MuseRegex`](crate::runtime::regex::MuseRegex). + /// [`MuseRegex`]. Regex(RegexLiteral), /// A function declaration. When loaded, it becomes a [`Function`]. Function(BitcodeFunction), @@ -59,11 +51,7 @@ impl ValueOrSource { /// This function does not support loading [`Label`]s. pub fn load(&self, vm: &VmContext<'_, '_>) -> Result { match self { - ValueOrSource::Nil => Ok(Value::NIL), - ValueOrSource::Bool(v) => Ok(Value::Primitive(Primitive::Bool(*v))), - ValueOrSource::Int(v) => Ok(Value::Primitive(Primitive::Int(*v))), - ValueOrSource::UInt(v) => Ok(Value::Primitive(Primitive::UInt(*v))), - ValueOrSource::Float(v) => Ok(Value::Primitive(Primitive::Float(*v))), + ValueOrSource::Primitive(p) => Ok(Value::Primitive(*p)), ValueOrSource::Symbol(v) => Ok(Value::Symbol(v.downgrade())), ValueOrSource::Regex(v) => MuseRegex::load(v, vm.guard()), ValueOrSource::Function(v) => Ok(Value::dynamic(v.to_function(vm.guard()), vm.guard())), @@ -85,7 +73,7 @@ impl ValueOrSource { impl From<()> for ValueOrSource { fn from(_value: ()) -> Self { - Self::Nil + Self::Primitive(Primitive::Nil) } } @@ -100,11 +88,7 @@ impl TryFrom for ValueOrSource { fn try_from(value: Literal) -> Result { match value { - Literal::Nil => Ok(ValueOrSource::Nil), - Literal::Bool(value) => Ok(ValueOrSource::Bool(value)), - Literal::Int(value) => Ok(ValueOrSource::Int(value)), - Literal::UInt(value) => Ok(ValueOrSource::UInt(value)), - Literal::Float(value) => Ok(ValueOrSource::Float(value)), + Literal::Primitive(p) => Ok(ValueOrSource::Primitive(p)), Literal::String(value) => Err(value), Literal::Symbol(value) => Ok(ValueOrSource::Symbol(value)), Literal::Regex(value) => Ok(ValueOrSource::Regex(value)), @@ -115,7 +99,7 @@ impl TryFrom for ValueOrSource { impl From for ValueOrSource { fn from(value: OpDestination) -> Self { match value { - OpDestination::Void => Self::Nil, + OpDestination::Void => Self::Primitive(Primitive::Nil), OpDestination::Register(dest) => Self::Register(dest), OpDestination::Stack(dest) => Self::Stack(dest), OpDestination::Label(dest) => Self::Label(dest), @@ -123,20 +107,20 @@ impl From for ValueOrSource { } } -impl_from!(ValueOrSource, i8, Int); -impl_from!(ValueOrSource, i16, Int); -impl_from!(ValueOrSource, i32, Int); -impl_from!(ValueOrSource, i64, Int); -impl_from!(ValueOrSource, u8, UInt); -impl_from!(ValueOrSource, u16, UInt); -impl_from!(ValueOrSource, u32, UInt); -impl_from!(ValueOrSource, u64, UInt); -impl_from!(ValueOrSource, f64, Float); +impl_from_primitive!(ValueOrSource, i8, Int); +impl_from_primitive!(ValueOrSource, i16, Int); +impl_from_primitive!(ValueOrSource, i32, Int); +impl_from_primitive!(ValueOrSource, i64, Int); +impl_from_primitive!(ValueOrSource, u8, UInt); +impl_from_primitive!(ValueOrSource, u16, UInt); +impl_from_primitive!(ValueOrSource, u32, UInt); +impl_from_primitive!(ValueOrSource, u64, UInt); +impl_from_primitive!(ValueOrSource, f64, Float); impl_from!(ValueOrSource, Symbol, Symbol); impl_from!(ValueOrSource, Register, Register); impl_from!(ValueOrSource, Stack, Stack); impl_from!(ValueOrSource, Label, Label); -impl_from!(ValueOrSource, bool, Bool); +impl_from_primitive!(ValueOrSource, bool, Bool); impl_from!(ValueOrSource, BitcodeFunction, Function); impl_from!(ValueOrSource, RegexLiteral, Regex); impl_from!(ValueOrSource, BitcodeStruct, Struct); @@ -250,12 +234,12 @@ pub enum Op { Throw(FaultKind), } -/// An IR [`Fault`](crate::vm::Fault). +/// An IR [`Fault`]. #[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Hash)] pub enum FaultKind { /// Create an exception from the contents of register 0. Exception, - /// Return a [`Fault::PatternMismatch`](crate::vm::Fault::PatternMismatch). + /// Return a [`Fault::PatternMismatch`]. PatternMismatch, } @@ -819,11 +803,7 @@ pub(super) fn trusted_loaded_source_to_value( code: &CodeData, ) -> ValueOrSource { match loaded { - LoadedSource::Nil => ValueOrSource::Nil, - LoadedSource::Bool(loaded) => ValueOrSource::Bool(*loaded), - LoadedSource::Int(loaded) => ValueOrSource::Int(*loaded), - LoadedSource::UInt(loaded) => ValueOrSource::UInt(*loaded), - LoadedSource::Float(loaded) => ValueOrSource::Float(*loaded), + LoadedSource::Primitive(p) => ValueOrSource::Primitive(*p), LoadedSource::Symbol(loaded) => ValueOrSource::Symbol(code.symbols[*loaded].clone()), LoadedSource::Register(loaded) => ValueOrSource::Register(*loaded), LoadedSource::Stack(loaded) => ValueOrSource::Stack(*loaded), diff --git a/muse-lang/src/vm/dispatched.rs b/muse-lang/src/vm/dispatched.rs index 6c544f4..5728779 100644 --- a/muse-lang/src/vm/dispatched.rs +++ b/muse-lang/src/vm/dispatched.rs @@ -960,7 +960,7 @@ impl Source for () { } fn as_source(&self, _guard: &CollectionGuard<'_>) -> ValueOrSource { - ValueOrSource::Nil + ValueOrSource::Primitive(Primitive::Nil) } } @@ -970,7 +970,7 @@ impl Source for bool { } fn as_source(&self, _guard: &CollectionGuard<'_>) -> ValueOrSource { - ValueOrSource::Bool(*self) + ValueOrSource::Primitive(Primitive::Bool(*self)) } } @@ -980,7 +980,7 @@ impl Source for i64 { } fn as_source(&self, _guard: &CollectionGuard<'_>) -> ValueOrSource { - ValueOrSource::Int(*self) + ValueOrSource::Primitive(Primitive::Int(*self)) } } @@ -990,7 +990,7 @@ impl Source for u64 { } fn as_source(&self, _guard: &CollectionGuard<'_>) -> ValueOrSource { - ValueOrSource::UInt(*self) + ValueOrSource::Primitive(Primitive::UInt(*self)) } } @@ -1000,7 +1000,7 @@ impl Source for f64 { } fn as_source(&self, _guard: &CollectionGuard<'_>) -> ValueOrSource { - ValueOrSource::Float(*self) + ValueOrSource::Primitive(Primitive::Float(*self)) } } @@ -1163,11 +1163,11 @@ impl Destination for Label { macro_rules! decode_source { ($decode:expr, $source:expr, $code:ident, $guard:ident, $next_fn:ident $(, $($arg:tt)*)?) => {{ match $decode { - ValueOrSource::Nil => $next_fn($source, $code, $guard $(, $($arg)*)?, ()), - ValueOrSource::Bool(source) => $next_fn($source, $code, $guard $(, $($arg)*)?, *source), - ValueOrSource::Int(source) => $next_fn($source, $code, $guard $(, $($arg)*)?, *source), - ValueOrSource::UInt(source) => $next_fn($source, $code, $guard $(, $($arg)*)?, *source), - ValueOrSource::Float(source) => $next_fn($source, $code, $guard $(, $($arg)*)?, *source), + ValueOrSource::Primitive(Primitive::Nil) => $next_fn($source, $code, $guard $(, $($arg)*)?, ()), + ValueOrSource::Primitive(Primitive::Bool(source)) => $next_fn($source, $code, $guard $(, $($arg)*)?, *source), + ValueOrSource::Primitive(Primitive::Int(source)) => $next_fn($source, $code, $guard $(, $($arg)*)?, *source), + ValueOrSource::Primitive(Primitive::UInt(source)) => $next_fn($source, $code, $guard $(, $($arg)*)?, *source), + ValueOrSource::Primitive(Primitive::Float(source)) => $next_fn($source, $code, $guard $(, $($arg)*)?, *source), ValueOrSource::Regex(source) => $next_fn($source, $code, $guard $(, $($arg)*)?, precompiled_regex(source, $guard)), ValueOrSource::Symbol(source) => $next_fn($source, $code, $guard $(, $($arg)*)?, source.clone()), ValueOrSource::Function(source) => $next_fn($source, $code, $guard $(, $($arg)*)?, source.to_function($guard)), @@ -1233,6 +1233,7 @@ decode_sd_simple!(match_negate, compile_negate, Negate); decode_sd!(match_declare_function, compile_declare_function, name: &Symbol, mutable: bool, access: Access); +#[allow(clippy::too_many_arguments)] fn compile_declare_function( _dest: &OpDestination, code: &mut CodeData, @@ -1541,7 +1542,7 @@ where parent: Some(parent), ..Module::default() }, - &mut *context.guard, + context.guard, )); vm.frames[vm.current_frame].module = ModuleId(module_index.get()); vm.frames[executing_frame].loading_module = Some(module_index); diff --git a/muse-reactor/src/lib.rs b/muse-reactor/src/lib.rs index a31040a..618ed5f 100644 --- a/muse-reactor/src/lib.rs +++ b/muse-reactor/src/lib.rs @@ -33,8 +33,8 @@ //! # Budget Pools //! //! Budget pools enable efficiently restricting groups of tasks to execution -//! budgets. Each time a task assigned to a [`BudgetPool`] exhausts its budget, -//! it requests additional budget from the pool. If no budget is available, the +//! budgets. Each time a task assigned to a budget pool exhausts its budget, it +//! requests additional budget from the pool. If no budget is available, the //! task is put to sleep and will automatically be resumed when the budget has //! been replenished. //! @@ -53,7 +53,7 @@ //! let task = pool.spawn_source("var i = 0; while i < 100 { i = i + 1; }; i").unwrap(); //! //! // Verify the task isn't able to complete. -//! assert!(task.try_join_for(Duration::from_secs(1)).is_none()); +//! assert!(task.join_for(Duration::from_secs(1)).is_none()); //! //! // Allocate enough budget. //! pool.increase_budget(1_000); @@ -64,12 +64,11 @@ //! RootedValue::Primitive(Primitive::Int(100)) //! ); //! ``` -#![allow(missing_docs)] use std::any::Any; use std::backtrace::Backtrace; use std::cell::Cell; use std::collections::VecDeque; -use std::fmt::{Debug, Write}; +use std::fmt::{Debug, Display, Write}; use std::future::Future; use std::marker::PhantomData; use std::num::NonZeroUsize; @@ -112,6 +111,7 @@ thread_local! { static PANIC_INFO: Cell)>> = const { Cell::new(None) }; } +/// A builder for a [`Reactor`]. #[must_use] pub struct Builder { vm_source: Option>>, @@ -128,6 +128,7 @@ impl Default for Builder { } impl Builder { + /// Returns a new builder for the default reactor settings. pub fn new() -> Self { Self { vm_source: None, @@ -138,6 +139,11 @@ impl Builder { } } + /// Customizes the process for which virtual machines are initialized. + /// + /// For convenience, [`NewVm`] is implemented for `Fn(&mut + /// CollectionGuard<'_>, &ReactorHandle)` where `Work` implements + /// [`WorkUnit`]. pub fn new_vm(self, new_vm: F) -> Builder where F: NewVm, @@ -156,16 +162,21 @@ impl Builder where Work: WorkUnit, { + /// Sets the number of threads to execute tasks across. pub fn threads(mut self, thread_count: usize) -> Self { self.threads = thread_count; self } + /// Sets the maximum number of work items in queue. + /// + /// By default, work queues are not limited. pub fn work_queue_limit(mut self, limit: usize) -> Self { self.work_queue_limit = Some(limit); self } + /// Spawns a reactor with the given settings and returns a handle to it. #[must_use] pub fn finish(self) -> ReactorHandle { PANIC_HOOK_INSTALL.get_or_init(|| { @@ -470,6 +481,7 @@ impl Dispatcher { } } +/// A multi-threaded executor for Muse workloads. pub struct Reactor { id: usize, receiver: Receiver>, @@ -479,11 +491,13 @@ pub struct Reactor { } impl Reactor { + /// Spawns the default executor and returns a handle to it. #[must_use] pub fn spawn() -> ReactorHandle { Self::build().finish() } + /// Returns a builder for a new reactor. pub fn build() -> Builder { Builder::new() } @@ -926,33 +940,20 @@ impl ResultDeadline for Instant { } } -impl Future for &'_ TaskHandle { - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut data = self.result.0.locked.lock(); - if let Some(result) = &data.result { - Poll::Ready(result.clone()) - } else { - let will_wake = data.wakers.iter().any(|w| w.will_wake(cx.waker())); - if !will_wake { - data.wakers.push(cx.waker().clone()); - } - Poll::Pending - } - } -} - #[derive(Default)] struct ResultHandleData { sync: Condvar, locked: Mutex, } +/// An error waiting for a task to execute. #[derive(Debug, Clone)] pub enum TaskError { + /// The task's execution was cancelled. Cancelled, + /// The source failed to compile. Compilation(Vec>), + /// An uncaught exception was raised while executing the task. Exception(RootedValue), } @@ -1092,6 +1093,7 @@ where } } +/// A handle to a task spawned in a reactor. #[derive(Trace)] pub struct TaskHandle { global_id: usize, @@ -1099,25 +1101,51 @@ pub struct TaskHandle { } impl TaskHandle { + /// Blocks the current thread until the task is finished. + /// + /// This function is not safe to execute from async code. [`TaskHandle`] + /// implements [`Future`] and can be awaited. pub fn join(&self) -> Result { self.result.recv(()) } + /// Checks if the task has executed, returning the result if it has. + /// + /// This function returns `None` if the task is pending execution or still + /// executing. + /// + /// This function is safe to call from both async and non-async code. #[must_use] pub fn try_join(&self) -> Option> { self.result.try_recv() } + /// Blocks the current thread until the task is finished or `deadline` has + /// passed. + /// + /// This function is not safe to execute from async code. [`TaskHandle`] + /// implements [`Future`] and can be awaited, and the future can be + /// cancelled using the async runtime's timeout functionality. #[must_use] - pub fn try_join_until(&self, deadline: Instant) -> Option> { + pub fn join_until(&self, deadline: Instant) -> Option> { self.result.recv(deadline) } + /// Blocks the current thread until the task is finished or `duration` has + /// elapsed. + /// + /// This function is not safe to execute from async code. [`TaskHandle`] + /// implements [`Future`] and can be awaited, and the future can be + /// cancelled using the async runtime's timeout functionality. #[must_use] - pub fn try_join_for(&self, duration: Duration) -> Option> { - self.try_join_until(Instant::now() + duration) + pub fn join_for(&self, duration: Duration) -> Option> { + self.join_until(Instant::now() + duration) } + /// Cancels the task if it is still running. + /// + /// Joining the task will return [`TaskError::Cancelled`] if the task was + /// successfully cancelled. pub fn cancel(&self) { let mut locked = self.result.0.locked.lock(); locked.cancelled = true; @@ -1169,9 +1197,34 @@ impl Debug for TaskHandle { } } +impl Future for &'_ TaskHandle { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut data = self.result.0.locked.lock(); + if let Some(result) = &data.result { + Poll::Ready(result.clone()) + } else { + let will_wake = data.wakers.iter().any(|w| w.will_wake(cx.waker())); + if !will_wake { + data.wakers.push(cx.waker().clone()); + } + Poll::Pending + } + } +} + +/// An operation could not be completed because the reactor is not running. #[derive(Debug)] pub struct ReactorShutdown; +impl Display for ReactorShutdown { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("reactor is not running") + } +} + +/// A handle to a spawned `Reactor`]. #[derive(Debug)] pub struct ReactorHandle { data: Arc>, @@ -1181,6 +1234,10 @@ impl ReactorHandle where Work: WorkUnit, { + /// Returns a new module containing the reactor's functionality. + /// + /// This module needs to be declared within `parent` before it can be + /// accessed. #[must_use] pub fn runtime_module_in( &self, @@ -1246,14 +1303,27 @@ where Ok(handle) } + /// Spawns `vm` in the reactor, returning a handle to the spawned task. + /// + /// [`Builder::new_vm`] can be used to customize what functionality is + /// available in every virtual machine. pub fn spawn(&self, vm: Vm) -> Result { self.spawn_spawnable(Spawnable::Spawn(vm), None) } + /// Spawns a task that compiles and executes `source`. + /// + /// [`Builder::new_vm`] can be used to customize how `source` is compiled + /// and what functionality is available in every virtual machine. pub fn spawn_source(&self, source: impl Into) -> Result { self.spawn_spawnable(Spawnable::SpawnSource(source.into()), None) } + /// Spawns a task that executes `code` after loading `args` to the virtual + /// machine registers. + /// + /// [`Builder::new_vm`] can be used to customize what functionality is + /// available in every virtual machine. pub fn spawn_call( &self, code: Code, @@ -1262,10 +1332,23 @@ where self.spawn_spawnable(Spawnable::SpawnCall(code, args), None) } + /// Spawns a task that executes `work`. + /// + /// This function allows a user-specified type to be spawned and converted + /// into a task using the [`WorkUnit`] trait. + /// + /// [`Builder::new_vm`] can be used to customize the `Work` generic and what + /// functionality is available in every virtual machine. pub fn spawn_work(&self, work: Work) -> Result { self.spawn_spawnable(Spawnable::SpawnWork(work), None) } + /// Shuts the reactor down. + /// + /// Only the first call to this function will do anything. All subsequent + /// calls will return `Ok(())` without blocking. The call that shuts the + /// reactor down will block the current thread until the reactor has shut + /// down or an error has occurred. pub fn shutdown(&self) -> Result<(), Box> { if self .data @@ -1287,6 +1370,7 @@ where Ok(()) } + /// Returns a new budget pool. pub fn create_budget_pool( &self, config: BudgetPoolConfig, @@ -1444,9 +1528,12 @@ enum Spawnable { SpawnWork(Work), } +/// An error while preparing and executing code. #[derive(Debug)] pub enum PrepareError { + /// One or more compilation errors. Compilation(Vec>), + /// An error occurred while executing code. Execution(ExecutionError), } @@ -1471,13 +1558,16 @@ impl From>> for PrepareError { } } +/// Creates new [`Vm`]s for a [`Reactor`]. pub trait NewVm: Send + Sync + 'static { + /// Returns a newly initialized virtual machine. fn new_vm( &self, guard: &mut CollectionGuard<'_>, reactor: &ReactorHandle, ) -> Result; + /// Returns a virtual machine that is prepared to execute `source`. fn compile_and_prepare( &self, source: &str, @@ -1490,6 +1580,8 @@ pub trait NewVm: Send + Sync + 'static { Ok(vm) } + /// Returns a virtual machine that is prepared to execute `code` with + /// `args`. fn prepare_call( &self, code: Code, @@ -1541,7 +1633,9 @@ impl NewVm for () { } } +/// A custom unit of work for a [`Reactor`]. pub trait WorkUnit: Sized + Send + 'static { + /// Initializes a new virtual machine for this unit of work. fn initialize( self, vms: &dyn NewVm, @@ -1563,6 +1657,7 @@ impl WorkUnit for Code { } } +/// A [`WorkUnit`] that can not be instantiated. pub enum NoWork {} impl WorkUnit for NoWork { @@ -1576,9 +1671,19 @@ impl WorkUnit for NoWork { } } +/// The unique identifier of a budget pool in a [`Reactor`]. +/// +/// IDs are only unique within the same reactor. #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] pub struct BudgetPoolId(NonZeroUsize); +/// A handle to a budget pool. +/// +/// Tasks spawned in a budget pool will only be able to execute while the budget +/// pool has remaining budget. +/// +/// When all handles to a budget pool have been dropped, all outstanding tasks +/// belonging to the budget pool will be cancelled. pub struct BudgetPoolHandle(Arc>); impl ContainsNoRefs for BudgetPoolHandle where Work: WorkUnit {} @@ -1592,17 +1697,32 @@ impl BudgetPoolHandle where Work: WorkUnit, { + /// Returns the id of this pool. #[must_use] pub fn id(&self) -> BudgetPoolId { self.0.pool.0.id } + /// Spawns `vm` in the reactor, returning a handle to the spawned task. + /// + /// [`Builder::new_vm`] can be used to customize what functionality is + /// available in every virtual machine. + /// + /// This task will execute with a shared budget and will be paused when no + /// budget is available in this pool. pub fn spawn(&self, vm: Vm) -> Result { self.0 .reactor .spawn_spawnable(Spawnable::Spawn(vm), Some(self.0.pool.0.id)) } + /// Spawns a task that compiles and executes `source`. + /// + /// [`Builder::new_vm`] can be used to customize how `source` is compiled + /// and what functionality is available in every virtual machine. + /// + /// This task will execute with a shared budget and will be paused when no + /// budget is available in this pool. pub fn spawn_source(&self, source: impl Into) -> Result { self.0.reactor.spawn_spawnable( Spawnable::SpawnSource(source.into()), @@ -1610,6 +1730,14 @@ where ) } + /// Spawns a task that executes `code` after loading `args` to the virtual + /// machine registers. + /// + /// [`Builder::new_vm`] can be used to customize what functionality is + /// available in every virtual machine. + /// + /// This task will execute with a shared budget and will be paused when no + /// budget is available in this pool. pub fn spawn_call( &self, code: Code, @@ -1620,21 +1748,37 @@ where .spawn_spawnable(Spawnable::SpawnCall(code, args), Some(self.0.pool.0.id)) } + /// Spawns a task that executes `work`. + /// + /// This function allows a user-specified type to be spawned and converted + /// into a task using the [`WorkUnit`] trait. + /// + /// [`Builder::new_vm`] can be used to customize the `Work` generic and what + /// functionality is available in every virtual machine. + /// + /// This task will execute with a shared budget and will be paused when no + /// budget is available in this pool. pub fn spawn_work(&self, work: Work) -> Result { self.0 .reactor .spawn_spawnable(Spawnable::SpawnWork(work), Some(self.0.pool.0.id)) } + /// Adds `amount` to the budget. + /// + /// This function cannot increase the budget above + /// [`BudgetPoolConfig::maximum`]. pub fn increase_budget(&self, amount: usize) { self.0.pool.increase_budget(amount); } + /// Returns the currently remaining budget. #[must_use] pub fn remaining_budget(&self) -> usize { self.0.pool.0.budget.load(Ordering::Relaxed) } + /// Returns a handle to the reactor this pool belongs to. #[must_use] pub fn reactor(&self) -> &ReactorHandle { &self.0.reactor @@ -1666,13 +1810,24 @@ impl Drop for BudgetPoolHandleData { } } +/// The settings for a budget pool in a [`Reactor`]. #[non_exhaustive] #[must_use] pub struct BudgetPoolConfig { + /// The maximum budget this pool can contain. + /// + /// If this is 0, there is no maximum budget. pub maximum: usize, + /// Each time a virtual machine runs out of budget, this is the amount of + /// budget it should be allocated. pub allocation_size: usize, + /// When the pool is initialized, this is the initial budget of the pool. pub start: usize, + /// When `recharge_amount` and `recharge_every` are non-zero, the budget is + /// replenished by `recharge_amount` each time `recharge_every` elapses. pub recharge_amount: usize, + /// When `recharge_amount` and `recharge_every` are non-zero, the budget is + /// replenished by `recharge_amount` each time `recharge_every` elapses. pub recharge_every: Duration, } @@ -1683,6 +1838,7 @@ impl Default for BudgetPoolConfig { } impl BudgetPoolConfig { + /// Returns the default budget pool configuration. pub const fn new() -> Self { Self { maximum: 0, @@ -1693,27 +1849,33 @@ impl BudgetPoolConfig { } } + /// Sets the starting budget for this pool. pub const fn starting_with(mut self, start: usize) -> Self { self.start = start; self } + /// Sets the maximum budget for this pool. pub const fn with_maximum(mut self, maximum: usize) -> Self { self.maximum = maximum; self } + /// Sets the amount to allocate for each budget request. pub const fn with_per_task_allocation(mut self, allocation_size: usize) -> Self { self.allocation_size = allocation_size; self } + /// Sets the budget to automatically replenish by `amount` every time + /// `recharge_every` elapses. pub const fn with_recharge(mut self, amount: usize, recharge_every: Duration) -> Self { self.recharge_amount = amount; self.recharge_every = recharge_every; self } + /// Returns true if this configuration automatically recharges. #[must_use] pub fn recharges(&self) -> bool { self.recharge_every > Duration::ZERO && self.recharge_amount > 0 diff --git a/muse-reactor/src/tests.rs b/muse-reactor/src/tests.rs index 4f240bb..6e134e4 100644 --- a/muse-reactor/src/tests.rs +++ b/muse-reactor/src/tests.rs @@ -149,7 +149,7 @@ fn task_cancellation() { // Spawn a task with an infinite loop let task = reactor.spawn_source("loop {}").unwrap(); // Wait a bit to make sure it's running. - assert!(task.try_join_for(Duration::from_secs(1)).is_none()); + assert!(task.join_for(Duration::from_secs(1)).is_none()); // Cancel the task. println!("Cancelling"); @@ -172,7 +172,7 @@ fn pool_cancellation() { .unwrap(); let task = pool.spawn_source("loop {}").unwrap(); // Wait a bit to make sure it's running. - assert!(task.try_join_for(Duration::from_secs(1)).is_none()); + assert!(task.join_for(Duration::from_secs(1)).is_none()); // Drop the pool, which should cause the task to be cancelled for running // out of budget.