From 001da3ca2fdadc4541ea18c360e9c4c8b0448083 Mon Sep 17 00:00:00 2001 From: projectmoon Date: Sun, 10 Mar 2024 14:39:32 +0100 Subject: [PATCH] Remove boxed dyn requirement on GBNF Limits. This commit removes the use of dynamic dispatch for GBNF rule limits by making use of some overly complicated generic trait bounds plus associated types, along with two new types. Instead of wrapping GBNF limits in a box dynamic trait object that produces GBNF limits, there are two types: GbnfLimitedPrimitive and GbnfLimitedComplex. These types contain the actual (not very complicated) logic to convert their contained limit-values into the GBNF limits, which then get converted to actual limits in the GBNF grammar. This removes heap allocation and dynamic dispatch, as well as making creation of limit objects more ergonomic. --- game/src/ai/prompts/execution_prompts.rs | 51 ++----- game/src/main.rs | 5 +- game/src/models/commands.rs | 10 +- gbnf/src/lib.rs | 135 +++--------------- gbnf/src/{limited.rs => limits/convert.rs} | 1 + gbnf/src/limits/definitions.rs | 157 +++++++++++++++++++++ gbnf/src/limits/mod.rs | 2 + gbnf_derive/src/lib.rs | 94 ++++++++---- 8 files changed, 265 insertions(+), 190 deletions(-) rename gbnf/src/{limited.rs => limits/convert.rs} (99%) create mode 100644 gbnf/src/limits/definitions.rs create mode 100644 gbnf/src/limits/mod.rs diff --git a/game/src/ai/prompts/execution_prompts.rs b/game/src/ai/prompts/execution_prompts.rs index b35427f..45c4cfa 100644 --- a/game/src/ai/prompts/execution_prompts.rs +++ b/game/src/ai/prompts/execution_prompts.rs @@ -7,6 +7,7 @@ use crate::models::world::items::Item; use crate::models::world::people::Person; use crate::models::world::scenes::{Exit, Prop, Scene, Stage}; use crate::models::Insertable; +use gbnf::prelude::limit::{GbnfLimitedComplex, GbnfLimitedPrimitive}; use itertools::Itertools; use strum::VariantNames; use tabled::settings::Style; @@ -75,19 +76,6 @@ impl<'a> From<&'a Exit> for ExitTableRow<'a> { } } -const COMMAND_EXECUTION_BNF: &'static str = r#" -root ::= CommandExecution -CommandEvent ::= "{" ws "\"eventName\":" ws string "," ws "\"appliesTo\":" ws string "," ws "\"parameter\":" ws string "}" -CommandExecution ::= "{" ws "\"valid\":" ws boolean "," ws "\"reason\":" ws string "," ws "\"narration\":" ws string "," ws "\"event\":" ws CommandEvent "}" -CommandExecutionlist ::= "[]" | "[" ws CommandExecution ("," ws CommandExecution)* "]" -string ::= "\"" ([^"]*) "\"" -boolean ::= "true" | "false" -ws ::= [ \t\n]* -number ::= [0-9]+ "."? [0-9]* -stringlist ::= "[" ws "]" | "[" ws string ("," ws string)* ws "]" -numberlist ::= "[" ws "]" | "[" ws string ("," ws number)* ws "]" -"#; - const COMMAND_EXECUTION_PROMPT: &'static str = r#" [INST] You are running a text-based adventure game. You have been given a command to execute. Your response must be in JSON. @@ -248,41 +236,25 @@ fn stage_info(stage: &Stage) -> String { } fn execution_gbnf_limit<'a>(stage: &'a Stage) -> RawCommandExecutionGbnfLimit { - // We will begin this implementation by simply setting the limits + // First version of this implementation is simply setting the limits // to all uuids in the scene. We might wind up getting rid of the // appliesTo field, leaving only parameter behind. - let people_keys = stage - .people - .iter() - .map(|p| &p._key) - .cloned() - .flatten(); - - let item_keys = stage - .items - .iter() - .map(|i| &i._key) - .cloned() - .flatten(); - - let curr_scene_key = stage.scene._key.iter(); + let people_keys = stage.people.iter().map(|p| &p._key).cloned().flatten(); + let item_keys = stage.items.iter().map(|i| &i._key).cloned().flatten(); let exit_keys = stage.scene.exits.iter().map(|e| &e.scene_key).cloned(); - let all_uuids = people_keys - .chain(item_keys) - //.chain(curr_scene_key) - .chain(exit_keys) - .collect_vec(); + let all_uuids = people_keys.chain(item_keys).chain(exit_keys).collect_vec(); + let mut applies_to = all_uuids.clone(); + applies_to.push("self".to_string()); - //let all_uuids = std::rc::Rc::new(all_uuids); let event_limit = RawCommandEventGbnfLimit { - applies_to: Box::new(all_uuids.clone()), - parameter: Box::new(all_uuids.clone()), + applies_to: GbnfLimitedPrimitive::new(applies_to), + parameter: GbnfLimitedPrimitive::new(all_uuids), }; RawCommandExecutionGbnfLimit { - event: Box::new(event_limit), + event: GbnfLimitedComplex::new(event_limit), } } @@ -299,9 +271,6 @@ pub fn execution_prompt(original_cmd: &str, stage: &Stage, cmd: &ParsedCommand) let limit = execution_gbnf_limit(stage); let grammar = RawCommandExecution::to_grammar_with_limit(limit); - println!("{}", grammar); - - //AiPrompt::new_with_grammar_and_size(&prompt, COMMAND_EXECUTION_BNF, 512) AiPrompt::new_with_grammar_and_size(&prompt, &grammar, 512) } diff --git a/game/src/main.rs b/game/src/main.rs index d34952f..165b04a 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -2,7 +2,10 @@ use ai::logic::AiLogic; use anyhow::Result; use config::Config; use game_loop::GameLoop; -use models::world::scenes::{root_scene_id, Stage}; +use models::{ + commands::RawCommandExecutionGbnfLimit, + world::scenes::{root_scene_id, Stage}, +}; use state::GameState; use std::{io::stdout, rc::Rc, str::FromStr, time::Duration}; diff --git a/game/src/models/commands.rs b/game/src/models/commands.rs index 12db19b..90d08ef 100644 --- a/game/src/models/commands.rs +++ b/game/src/models/commands.rs @@ -1,7 +1,5 @@ -use std::fmt::Display; - use gbnf::prelude::*; -use gbnf_derive::{Gbnf}; +use gbnf_derive::Gbnf; use serde::{Deserialize, Serialize}; use strum::{EnumString, EnumVariantNames}; use thiserror::Error; @@ -63,7 +61,7 @@ pub struct RawCommandExecution { pub reason: Option, pub narration: String, #[serde(skip_serializing_if = "Option::is_none")] - #[gbnf_limit] + #[gbnf_limit_complex] pub event: Option, } @@ -82,9 +80,9 @@ impl RawCommandExecution { #[serde(rename_all = "snake_case")] pub struct RawCommandEvent { pub event_name: String, - #[gbnf_limit] + #[gbnf_limit_primitive] pub applies_to: String, - #[gbnf_limit] + #[gbnf_limit_primitive] pub parameter: String, } diff --git a/gbnf/src/lib.rs b/gbnf/src/lib.rs index e782f0b..f8ae977 100644 --- a/gbnf/src/lib.rs +++ b/gbnf/src/lib.rs @@ -4,24 +4,31 @@ use std::{ ops::{Deref, DerefMut}, }; -use itertools::Itertools; -use limited::{Limited, LimitedGbnfComplex, LimitedGbnfField, LimitedGbnfPrimitive}; -use serde::de::DeserializeOwned; +use crate::limits::convert::{Limited, LimitedGbnfComplex, LimitedGbnfField, LimitedGbnfPrimitive}; use convert_case::{Case, Casing}; +use itertools::Itertools; +use serde::de::DeserializeOwned; -mod limited; +mod limits; + +pub mod limit { + pub use crate::limits::definitions::*; +} pub mod prelude { pub use crate::gbnf_field; pub use crate::gbnf_field_type; + pub use crate::limit; + pub use crate::limit::*; pub use crate::AsGbnf; + pub use crate::AsGbnfComplex; + pub use crate::AsGbnfLimit; pub use crate::AsGbnfPrimitive; pub use crate::AsGrammar; pub use crate::GbnfComplex; pub use crate::GbnfField; pub use crate::GbnfFieldType; pub use crate::GbnfLimit; - pub use crate::GbnfLimitedField; pub use crate::GbnfPrimitive; pub use crate::GbnfRule; pub use crate::GbnfRuleLevel; @@ -35,106 +42,8 @@ pub mod prelude { /// complex types). This allows the creation of a complex nested Limit /// struct which can hold the full structure of nested limits of /// multiple types that derive Gbnf and have limits. -pub trait GbnfLimitedField { - fn limit(self: Box) -> GbnfLimit; -} - -impl + 'static> From> for Box> { - fn from(value: Box) -> Self { - value - } -} - -impl From> for Box> { - fn from(value: Vec) -> Self { - Box::new(value) - } -} - -impl From<[T; N]> for Box> -where - T: ToString + AsGbnfPrimitive + 'static, -{ - fn from(value: [T; N]) -> Self { - Box::new(value) - } -} - -impl From>> for Box> { - fn from(value: Box>) -> Self { - value - } -} - -// Single pritive field types that can only have specific values. -impl GbnfLimitedField for Vec { - fn limit(self: Box) -> GbnfLimit { - GbnfLimit::Simple( - T::to_gbnf_primitive(), - self.into_iter().map(|v| v.to_string()).collect(), - ) - } -} - -impl GbnfLimitedField> for Vec { - fn limit(self: Box) -> GbnfLimit { - GbnfLimit::Simple( - T::to_gbnf_primitive(), - self.into_iter().map(|v| v.to_string()).collect(), - ) - } -} - -// List types that can only have specific values. -impl GbnfLimitedField> for Vec { - fn limit(self: Box) -> GbnfLimit { - GbnfLimit::Simple( - T::to_gbnf_primitive(), - self.into_iter().map(|v| v.to_string()).collect(), - ) - } -} - -impl GbnfLimitedField for [T; N] -where - T: ToString + AsGbnfPrimitive, -{ - fn limit(self: Box) -> GbnfLimit { - GbnfLimit::Simple( - T::to_gbnf_primitive(), - self.into_iter().map(|v| v.to_string()).collect(), - ) - } -} - -impl GbnfLimitedField> for [T; N] -where - T: ToString + AsGbnfPrimitive, -{ - fn limit(self: Box) -> GbnfLimit { - GbnfLimit::Simple( - T::to_gbnf_primitive(), - self.into_iter().map(|v| v.to_string()).collect(), - ) - } -} - -impl<'a> GbnfLimitedField for &'a [&'a str] { - fn limit(self: Box) -> GbnfLimit { - GbnfLimit::Simple( - String::to_gbnf_primitive(), - self.into_iter().map(|v| v.to_string()).collect(), - ) - } -} - -impl<'a> GbnfLimitedField for std::rc::Rc> { - fn limit(self: Box) -> GbnfLimit { - GbnfLimit::Simple( - String::to_gbnf_primitive(), - self.iter().map(|v| v.to_string()).collect(), - ) - } +pub trait AsGbnfLimit { + fn to_gbnf_limit(self) -> GbnfLimit; } /// A type of GBNF value limit, either simple (a list of values meant @@ -147,12 +56,6 @@ pub enum GbnfLimit { Complex(HashMap<&'static str, GbnfLimit>), } -impl From>> for GbnfLimit { - fn from(value: Box>) -> Self { - value.limit() - } -} - // Converts GBNF defintions (through the types below) into the grammar // rules. pub trait AsGrammar { @@ -180,6 +83,10 @@ pub trait AsGbnfPrimitive { fn to_gbnf_primitive() -> GbnfPrimitive; } +pub trait AsGbnfComplex { + fn to_gbnf_complex() -> GbnfComplex; +} + macro_rules! define_field_type { ($type:ty, $gbnf_type:expr) => { impl AsGbnf for $type { @@ -331,6 +238,8 @@ impl GbnfRule { ) } + /// Turn this rule into an option rule: main rule is an option type, and + /// dependent rule is the original rule itself. pub fn to_optional(self) -> GbnfRule { let option_token = self.name.option_token(); let option_rule_text = format!(r#"{} | "null""#, self.name); @@ -340,7 +249,7 @@ impl GbnfRule { } /// Turn this rule into a list rule: main rule is a list type, and - /// dependent rule is the original type/rule itself. + /// dependent rule is the original rule itself. pub fn to_list(self) -> GbnfRule { let list_name = self.name.list_token(); let list_rule_text = @@ -354,7 +263,7 @@ impl GbnfRule { list_rule } - /// Consume this rule to produce a list of rules consisting of + /// Consume this rule to produce an ordered list of rules consisting of /// this rule and its dependents. The rules in the list have no /// dependents. Useful for final output of a rules list. fn flatten(mut self) -> Vec { diff --git a/gbnf/src/limited.rs b/gbnf/src/limits/convert.rs similarity index 99% rename from gbnf/src/limited.rs rename to gbnf/src/limits/convert.rs index 7557f56..ae79acd 100644 --- a/gbnf/src/limited.rs +++ b/gbnf/src/limits/convert.rs @@ -4,6 +4,7 @@ use crate::{ }; use itertools::Itertools; + #[derive(Debug, Clone)] pub struct LimitedGbnfComplex<'a> { pub complex: &'a GbnfComplex, diff --git a/gbnf/src/limits/definitions.rs b/gbnf/src/limits/definitions.rs new file mode 100644 index 0000000..2eb6696 --- /dev/null +++ b/gbnf/src/limits/definitions.rs @@ -0,0 +1,157 @@ +use crate::AsGbnfComplex; +use crate::AsGbnfLimit; +use crate::AsGbnfPrimitive; +use crate::GbnfLimit; + +pub trait GbnfLimitType { + type Type; +} + +pub trait GbnfLimitStructMarker {} + +macro_rules! define_limit_marker { + ($field_type:ty, $value_type:ty) => { + impl GbnfLimitType for $field_type { + type Type = $value_type; + } + }; +} + +define_limit_marker!(String, String); +define_limit_marker!(i32, i32); + +impl GbnfLimitType for Vec +where + T: GbnfLimitType, +{ + type Type = ::Type; +} + +impl GbnfLimitType for Option { + type Type = T; +} + +pub trait GbnfLimitTypeContainer { + type ContainerType; +} + +impl GbnfLimitTypeContainer for String { + type ContainerType = Vec; +} + +impl GbnfLimitTypeContainer for i32 { + type ContainerType = Vec; +} + +impl GbnfLimitTypeContainer for Vec { + type ContainerType = Vec<::Type>; +} + +impl> GbnfLimitTypeContainer> for Vec { + type ContainerType = >::ContainerType; +} + +impl GbnfLimitTypeContainer for Option { + type ContainerType = ::Type; +} + +impl GbnfLimitTypeContainer> for Option { + type ContainerType = ::Type; +} + +pub struct GbnfLimitedPrimitive +where + T: GbnfLimitType + GbnfLimitTypeContainer, +{ + field_type: std::marker::PhantomData, + values: >::ContainerType, +} + +impl GbnfLimitedPrimitive +where + T: GbnfLimitType + GbnfLimitTypeContainer, +{ + pub fn new(values: >::ContainerType) -> GbnfLimitedPrimitive { + GbnfLimitedPrimitive { + field_type: std::marker::PhantomData, + values, + } + } +} + +impl AsGbnfLimit for GbnfLimitedPrimitive +where + T: AsGbnfPrimitive + + ToString + + GbnfLimitTypeContainer> + + GbnfLimitType, +{ + fn to_gbnf_limit(self) -> GbnfLimit { + let vals = self.values.into_iter().map(|v| v.to_string()); + GbnfLimit::Simple(::to_gbnf_primitive(), vals.collect()) + } +} + +impl AsGbnfLimit for GbnfLimitedPrimitive> +where + T: AsGbnfPrimitive + + ToString + + GbnfLimitTypeContainer> + + GbnfLimitType, + Vec: GbnfLimitType, +{ + fn to_gbnf_limit(self) -> GbnfLimit { + let vals = self.values.into_iter().map(|v| v.to_string()); + GbnfLimit::Simple(::to_gbnf_primitive(), vals.collect()) + } +} + +pub struct GbnfLimitedComplex +where + T: GbnfLimitType + GbnfLimitTypeContainer, +{ + field_type: std::marker::PhantomData, + values: >::ContainerType, +} + +impl GbnfLimitedComplex +where + T: GbnfLimitType + GbnfLimitTypeContainer, +{ + pub fn new(values: >::ContainerType) -> GbnfLimitedComplex { + GbnfLimitedComplex { + field_type: std::marker::PhantomData, + values, + } + } +} + +impl AsGbnfLimit for GbnfLimitedComplex +where + T: AsGbnfComplex + GbnfLimitType + GbnfLimitTypeContainer, + U: AsGbnfLimit + GbnfLimitStructMarker, +{ + fn to_gbnf_limit(self) -> GbnfLimit { + self.values.to_gbnf_limit() + } +} + +impl AsGbnfLimit for GbnfLimitedComplex> +where + T: AsGbnfComplex + GbnfLimitType + GbnfLimitTypeContainer, + U: AsGbnfLimit + GbnfLimitStructMarker, +{ + fn to_gbnf_limit(self) -> GbnfLimit { + self.values.to_gbnf_limit() + } +} + +impl AsGbnfLimit for GbnfLimitedComplex> +where + T: GbnfLimitType + GbnfLimitTypeContainer, + U: AsGbnfLimit + GbnfLimitStructMarker, +{ + fn to_gbnf_limit(self) -> GbnfLimit { + self.values.to_gbnf_limit() + } +} diff --git a/gbnf/src/limits/mod.rs b/gbnf/src/limits/mod.rs new file mode 100644 index 0000000..0b15afa --- /dev/null +++ b/gbnf/src/limits/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod convert; +pub mod definitions; diff --git a/gbnf_derive/src/lib.rs b/gbnf_derive/src/lib.rs index 8ba3573..9cbdc4f 100644 --- a/gbnf_derive/src/lib.rs +++ b/gbnf_derive/src/lib.rs @@ -3,11 +3,11 @@ use darling::{FromDeriveInput, FromField}; use proc_macro::{Span, TokenStream}; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote, ToTokens}; -use syn::parse_macro_input; +use syn::{parse_macro_input, Attribute}; use syn::{DeriveInput, Ident, LitStr}; #[derive(Clone, Debug, FromField)] -#[darling(forward_attrs(gbnf_limit))] +#[darling(forward_attrs(gbnf_limit_primitive, gbnf_limit_complex))] struct GbnfFieldDef { ident: Option, ty: syn::Type, @@ -15,14 +15,47 @@ struct GbnfFieldDef { attrs: Vec, } +enum LimitType { + LimitedPrimitive, + LimitedComplex, + NotLimited, +} + +impl GbnfFieldDef { + fn limit_type(&self) -> LimitType { + let limit_priv = self + .attrs + .iter() + .find(|attr| attr.path().get_ident().unwrap().to_string() == "gbnf_limit_primitive"); + + let limit_complex = self + .attrs + .iter() + .find(|attr| attr.path().get_ident().unwrap().to_string() == "gbnf_limit_complex"); + + match (limit_priv, limit_complex) { + (Some(_), None) => LimitType::LimitedPrimitive, + (None, Some(_)) => LimitType::LimitedComplex, + (Some(_), Some(_)) => panic!("cannot be both a primitive and complex limit"), + (None, None) => LimitType::NotLimited, + } + } +} + impl ToTokens for GbnfFieldDef { fn to_tokens(&self, tokens: &mut TokenStream2) { let ident = &self.ident; let ty = &self.ty; // TODO figure out how to get ident out let vis = &self.vis; + let wrapper_type = match self.limit_type() { + LimitType::LimitedPrimitive => quote! { GbnfLimitedPrimitive<#ty> }, + LimitType::LimitedComplex => quote! { GbnfLimitedComplex<#ty> }, + _ => panic!("somehow attempting to make a limited field without a helper attr"), + }; + let output = quote! { - #vis #ident: Box> + #vis #ident: #wrapper_type }; output.to_tokens(tokens); @@ -55,13 +88,18 @@ impl ToTokens for GbnfStructDef { } } +fn is_limited_field(attr: &Attribute) -> bool { + let ident = attr.path().get_ident().unwrap().to_string(); + ident == "gbnf_limit_primitive" || ident == "gbnf_limit_complex" +} + /// Find fields in the struct with a #[gbnf_limit] attribute. fn find_limited_fields(fields: &[GbnfFieldDef]) -> impl Iterator + '_ { fields.iter().filter(|field| { field .attrs .iter() - .find(|attr| attr.path().get_ident().unwrap().to_string() == "gbnf_limit") + .find(|attr| is_limited_field(attr)) .is_some() }) } @@ -72,7 +110,7 @@ fn find_non_limited_fields(fields: &[GbnfFieldDef]) -> impl Iterator GbnfComplex { + Self::to_gbnf().as_complex() + } + } + }; + if limit_struct_fields.len() > 0 { quote! { pub struct #limit_struct_name { #(#limit_struct_fields),* } - impl #limit_struct_name { - pub fn to_gbnf_limit(self) -> GbnfLimit { + impl GbnfLimitStructMarker for #limit_struct_name {} + + impl AsGbnfLimit for #limit_struct_name { + fn to_gbnf_limit(self) -> GbnfLimit { GbnfLimit::Complex( HashMap::from([ #(#from_assignments),* @@ -115,28 +163,12 @@ fn generate_to_grammar_impl(original_struct_name: &Ident, fields: &[GbnfFieldDef } } - impl GbnfLimitedField<#original_struct_name> for #limit_struct_name { - fn limit(self: Box<#limit_struct_name>) -> GbnfLimit { - self.to_gbnf_limit() - } + impl GbnfLimitType for #original_struct_name { + type Type = #limit_struct_name; } - impl GbnfLimitedField> for #limit_struct_name { - fn limit(self: Box<#limit_struct_name>) -> GbnfLimit { - self.to_gbnf_limit() - } - } - - impl From<#limit_struct_name> for Box> { - fn from(limit: #limit_struct_name) -> Self { - Box::new(limit) - } - } - - impl From<#limit_struct_name> for Box>> { - fn from(limit: #limit_struct_name) -> Self { - Box::new(limit) - } + impl GbnfLimitTypeContainer<#original_struct_name> for #original_struct_name { + type ContainerType = #limit_struct_name; } impl #original_struct_name { @@ -145,6 +177,8 @@ fn generate_to_grammar_impl(original_struct_name: &Ident, fields: &[GbnfFieldDef Self::to_gbnf().as_complex().to_grammar(Some(gbnf_limit)) } } + + #as_gbnf_complex_impl } } else { quote! { @@ -155,6 +189,8 @@ fn generate_to_grammar_impl(original_struct_name: &Ident, fields: &[GbnfFieldDef GRAMMAR.get_or_init(|| Self::to_gbnf().as_complex().to_grammar(None)) } } + + #as_gbnf_complex_impl } } } @@ -241,7 +277,7 @@ fn map_limited_gbnfs(fields: &[GbnfFieldDef]) -> impl Iterator TokenStream { let input = parse_macro_input!(input as DeriveInput); generate_gbnf(&input)