diff --git a/rustorio-derive/src/lib.rs b/rustorio-derive/src/lib.rs index acc39cc..77effad 100644 --- a/rustorio-derive/src/lib.rs +++ b/rustorio-derive/src/lib.rs @@ -15,7 +15,8 @@ impl ToTokens for Crate { let found_crate = proc_macro_crate::crate_name("rustorio-engine").expect("Failed to get crate name"); match found_crate { - FoundCrate::Itself => quote! {crate}.to_tokens(tokens), + // Emit the crate name for doctests. + FoundCrate::Itself => quote! {rustorio_engine}.to_tokens(tokens), FoundCrate::Name(name) => { let crate_ident = Ident::new(&name, Span::call_site()); quote! {::#crate_ident}.to_tokens(tokens); diff --git a/rustorio-engine/src/recipe.rs b/rustorio-engine/src/recipe.rs index 9a8bbcc..9d8fdc1 100644 --- a/rustorio-engine/src/recipe.rs +++ b/rustorio-engine/src/recipe.rs @@ -48,6 +48,14 @@ pub trait MultiBundle: Sized + std::fmt::Debug { ) -> impl Iterator; } +/// Multiply the amounts in a bundle tuple by the given constant. +pub trait MultiBundleMultiply: MultiBundle { + /// Bundle tuple identical to `Self` except with all amounts multiplied by `N`. + type Multiplied: MultiBundle; +} +/// Transform a tuple of bundles by multiplying all bundle quantities by `N`. +pub type MultiplyMultiBundle = >::Multiplied; + // Special untupled case, for e.g. tech recipes that don't return a tuple. impl MultiBundle for Bundle { type AsResources = (Resource,); @@ -75,6 +83,14 @@ impl MultiBundle for Bundle { } } +impl MultiBundleMultiply for Bundle +where + // See https://github.com/rust-lang/rust/issues/145069 for why `Copy` + [(); { N1 * N } as usize]: Copy, +{ + type Multiplied = Bundle; +} + macro_rules! replace_expr { ($_t:tt, $($sub:tt)*) => { $($sub)* @@ -162,6 +178,19 @@ macro_rules! impl_multi_bundle { .into_iter() } } + + impl< + const N: u32, + $($ty: ResourceType, const $amount: u32),* + > MultiBundleMultiply for ($(Bundle<$ty, $amount>,)*) + where + // See https://github.com/rust-lang/rust/issues/145069 for why `Copy` + $( + [(); { $amount * N } as usize]: Copy, + )* + { + type Multiplied = ($(Bundle<$ty, { $amount * N }>,)*); + } }; } @@ -225,4 +254,55 @@ pub trait HandRecipe: std::fmt::Debug + Sealed + Recipe { tick.advance_by(Self::TIME); Self::OutputBundle::new_bundle(creation_token()) } + + /// Crafts the recipe `N` times by consuming `N` input bundles and producing `N` output bundles. + /// Advances the provided `Tick` by `N` times the recipe's time. + /// + /// Note: Call this function using an explicit `N`: `MyRecipe::craft_n::<42>(..)`; `N` can't be + /// inferred and omitting it may give rise to confusing errors. + fn craft_n( + tick: &mut Tick, + inputs: MultiplyMultiBundle, + ) -> MultiplyMultiBundle + where + Self::InputBundle: MultiBundleMultiply, + Self::OutputBundle: MultiBundleMultiply, + { + let token = creation_token(); + let _ = inputs; + tick.advance_by(Self::TIME * N as u64); + <>::Multiplied as MultiBundle>::new_bundle( + token, + ) + } +} + +#[test] +fn test_handcrafting() { + use rustorio_derive::Recipe; + + use crate as rustorio_engine; // For the derive macros + + crate::resource_type!(Copper); + crate::resource_type!(CopperWire); + + #[derive(Debug, Clone, Copy, Recipe)] + #[recipe_doc] + #[recipe_inputs( + (1, Copper), + )] + #[recipe_outputs( + (2, CopperWire), + )] + #[recipe_ticks(1)] + pub struct CopperWireRecipe; + impl Sealed for CopperWireRecipe {} + impl HandRecipe for CopperWireRecipe {} + + let mut tick = Tick::start(10000); + let mut copper: Resource = crate::resource(creation_token(), 42); + let _: (Bundle,) = + CopperWireRecipe::craft(&mut tick, (copper.bundle().unwrap(),)); + let _: (Bundle,) = + CopperWireRecipe::craft_n::<5>(&mut tick, (copper.bundle().unwrap(),)); } diff --git a/rustorio-engine/src/tick.rs b/rustorio-engine/src/tick.rs index fd2b6ce..5da9b39 100644 --- a/rustorio-engine/src/tick.rs +++ b/rustorio-engine/src/tick.rs @@ -61,7 +61,7 @@ impl TickSnapshot { /// # Examples /// /// Let's say we have two furnaces the we want to fill with `iron_ore` and `copper_ore` respectively, and then advance time so they can smelt the ore into ingots: -/// ``` +/// ```ignore /// // Add ore to the furnaces at the current tick /// furnace1.inputs(&tick).0 += iron_ore; /// furnace2.inputs(&tick).0 += copper_ore;