use std::collections::HashMap;
use log::*;
use serde::Deserialize;
pub use crate::{
yarn_proto::Program,
utils::*,
value::YarnValue,
};
pub mod yarn_proto {
include!(concat!(env!("OUT_DIR"), "/yarn.rs"));
}
mod utils;
mod value;
#[derive(Debug, Deserialize)]
pub struct LineInfo {
pub id: String,
pub text: String,
pub file: String,
pub node: String,
#[serde(rename="lineNumber")]
pub line_number: u32,
}
#[derive(Debug, Clone)]
pub struct Line {
pub id: String,
pub substitutions: Vec<String>,
}
impl Line {
fn new(id: String, substitutions: Vec<String>) -> Self {
Self {
id,
substitutions,
}
}
}
pub struct YarnOption {
pub line: Line,
pub id: u32,
pub destination_node: String,
}
impl YarnOption {
fn new(line: Line, id: u32, destination_node: String) -> Self {
Self {
line,
id,
destination_node,
}
}
}
pub type ReturningFunction = dyn Fn(&[YarnValue]) -> YarnValue + Send + Sync;
pub type Function = dyn Fn(&[YarnValue]) + Send + Sync;
pub enum YarnFunction {
Void(&'static Function),
Returning(&'static ReturningFunction),
}
impl YarnFunction {
pub fn call(&self, params: &[YarnValue]) -> Option<YarnValue> {
match self {
Self::Void(func) => {
(func)(params);
None
}
Self::Returning(func) => {
let result = (func)(params);
Some(result)
}
}
}
}
enum ParamCount {
N(u8),
Variadic,
}
impl From<i8> for ParamCount {
fn from(val: i8) -> Self {
if val >= 0 {
Self::N(val as u8)
} else {
Self::Variadic
}
}
}
pub struct FunctionInfo {
param_count: ParamCount,
func: YarnFunction,
}
impl FunctionInfo {
pub fn new(param_count: i8, func: &'static Function) -> Self {
Self {
param_count: param_count.into(),
func: YarnFunction::Void(func),
}
}
pub fn new_returning(param_count: i8, func: &'static ReturningFunction) -> Self {
Self {
param_count: param_count.into(),
func: YarnFunction::Returning(func),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ExecutionState {
Stopped,
WaitingOnOptionSelection,
Suspended,
Running,
}
pub enum SuspendReason {
Line(Line),
Options(Vec<YarnOption>),
Command(String),
NodeChange {
start: String,
end: String,
},
DialogueComplete(String),
}
pub struct VmState {
pub current_node_name: String,
pub program_counter: isize,
pub current_options: Vec<(Line, String)>,
pub stack: Vec<YarnValue>,
}
impl VmState {
fn new() -> Self {
Self {
current_node_name: String::new(),
program_counter: 0,
current_options: Vec::new(),
stack: Vec::new(),
}
}
}
pub struct VirtualMachine {
pub state: VmState,
pub variable_storage: HashMap<String, YarnValue>,
pub library: HashMap<String, FunctionInfo>,
pub execution_state: ExecutionState,
pub program: Program,
}
impl VirtualMachine {
pub fn new(program: Program) -> Self {
let mut library = HashMap::new();
library.insert(
"Add".to_string(),
FunctionInfo::new_returning(2, &|parameters: &[YarnValue]| {
parameters[0].add(¶meters[1]).unwrap()
}),
);
library.insert(
"Minus".to_string(),
FunctionInfo::new_returning(2, &|parameters: &[YarnValue]| {
parameters[0].sub(¶meters[1]).unwrap()
}),
);
library.insert(
"UnaryMinus".to_string(),
FunctionInfo::new_returning(1, &|parameters: &[YarnValue]| {
parameters[0].neg()
}),
);
library.insert(
"Divide".to_string(),
FunctionInfo::new_returning(2, &|parameters: &[YarnValue]| {
parameters[0].div(¶meters[1]).unwrap()
}),
);
library.insert(
"Multiply".to_string(),
FunctionInfo::new_returning(2, &|parameters: &[YarnValue]| {
parameters[0].mul(¶meters[1]).unwrap()
}),
);
library.insert(
"Modulo".to_string(),
FunctionInfo::new_returning(2, &|parameters: &[YarnValue]| {
parameters[0].rem(¶meters[1]).unwrap()
}),
);
library.insert(
"EqualTo".to_string(),
FunctionInfo::new_returning(2, &|parameters: &[YarnValue]| {
(parameters[0] == parameters[1]).into()
}),
);
library.insert(
"NotEqualTo".to_string(),
FunctionInfo::new_returning(2, &|parameters: &[YarnValue]| {
(parameters[0] != parameters[1]).into()
}),
);
library.insert(
"GreaterThan".to_string(),
FunctionInfo::new_returning(2, &|parameters: &[YarnValue]| {
(parameters[0] > parameters[1]).into()
}),
);
library.insert(
"GreaterThanOrEqualTo".to_string(),
FunctionInfo::new_returning(2, &|parameters: &[YarnValue]| {
(parameters[0] >= parameters[1]).into()
}),
);
library.insert(
"LessThan".to_string(),
FunctionInfo::new_returning(2, &|parameters: &[YarnValue]| {
(parameters[0] < parameters[1]).into()
}),
);
library.insert(
"LessThanOrEqualTo".to_string(),
FunctionInfo::new_returning(2, &|parameters: &[YarnValue]| {
(parameters[0] <= parameters[1]).into()
}),
);
library.insert(
"And".to_string(),
FunctionInfo::new_returning(2, &|parameters: &[YarnValue]| {
(parameters[0].as_bool() && parameters[1].as_bool()).into()
}),
);
library.insert(
"Or".to_string(),
FunctionInfo::new_returning(2, &|parameters: &[YarnValue]| {
(parameters[0].as_bool() || parameters[1].as_bool()).into()
}),
);
library.insert(
"Xor".to_string(),
FunctionInfo::new_returning(2, &|parameters: &[YarnValue]| {
(parameters[0].as_bool() ^ parameters[1].as_bool()).into()
}),
);
library.insert(
"Not".to_string(),
FunctionInfo::new_returning(1, &|parameters: &[YarnValue]| {
(!parameters[0].as_bool()).into()
}),
);
Self {
state: VmState::new(),
variable_storage: HashMap::new(),
library,
execution_state: ExecutionState::Stopped,
program,
}
}
pub fn set_node(&mut self, node_name: &str) -> bool {
self.state = VmState::new();
self.state.current_node_name = node_name.to_string();
self.execution_state = ExecutionState::Suspended;
true
}
pub fn continue_dialogue(&mut self) -> SuspendReason {
if self.execution_state == ExecutionState::WaitingOnOptionSelection {
panic!("Cannot continue running dialogue. Still waiting on option selection.");
}
self.execution_state = ExecutionState::Running;
loop {
let instruction_count = if !self.state.current_node_name.is_empty() {
self.program.nodes[&self.state.current_node_name].instructions.len()
} else {
0
};
if self.state.program_counter as usize >= instruction_count {
let last_node = self.state.current_node_name.clone();
self.execution_state = ExecutionState::Stopped;
self.state = VmState::new();
return SuspendReason::DialogueComplete(last_node);
}
let current_instruction = {
let current_node = &self.program.nodes[&self.state.current_node_name];
current_node.instructions[self.state.program_counter as usize].clone()
};
let suspend = self.run_instruction(current_instruction);
self.state.program_counter += 1;
if let Some(suspend) = suspend {
return suspend;
}
}
}
pub fn set_selected_option(&mut self, selected_option_id: u32) {
let selected_option_id = selected_option_id as usize;
if self.execution_state != ExecutionState::WaitingOnOptionSelection {
panic!();
}
if selected_option_id >= self.state.current_options.len() {
panic!();
}
let destination_node = self.state.current_options[selected_option_id].1.clone();
self.state.stack.push(YarnValue::Str(destination_node));
self.state.current_options.clear();
self.execution_state = ExecutionState::Suspended;
debug!("Selected option: {}", selected_option_id);
}
fn run_instruction(&mut self, instruction: yarn_proto::Instruction) -> Option<SuspendReason> {
use yarn_proto::{
instruction::OpCode,
operand::Value,
};
let opcode = OpCode::from_i32(instruction.opcode)
.unwrap();
debug!("Running {:?} {:?}", opcode, instruction.operands);
match opcode {
OpCode::JumpTo => {
if let Some(Value::StringValue(label)) = &instruction.operands[0].value {
self.state.program_counter = self.find_instruction_point_for_label(label) - 1;
} else {
}
}
OpCode::Jump => {
if let Some(YarnValue::Str(label)) = self.state.stack.last() {
self.state.program_counter = self.find_instruction_point_for_label(label) - 1;
} else {
}
}
OpCode::RunLine => {
if let Some(Value::StringValue(string_key)) = &instruction.operands[0].value {
let mut substitutions = Vec::new();
if let Some(Value::FloatValue(expression_count)) = instruction.operands.get(1).and_then(|o| o.value.as_ref()) {
let expression_count = *expression_count as u32;
substitutions.resize(expression_count as usize, String::new());
for expression_index in (0..expression_count as usize).rev() {
let substitution = self.state.stack.pop().unwrap().as_string();
substitutions[expression_index] = substitution;
}
}
self.execution_state = ExecutionState::Suspended;
let line = Line::new(string_key.clone(), substitutions);
return Some(SuspendReason::Line(line));
} else {
}
}
OpCode::RunCommand => {
if let Some(Value::StringValue(command)) = &instruction.operands[0].value {
let mut command_text = command.clone();
if let Some(Value::FloatValue(expression_count)) = instruction.operands.get(1).and_then(|o| o.value.as_ref()) {
let expression_count = *expression_count as u32;
for expression_index in (0..expression_count).rev() {
let substitution = self.state.stack.pop().unwrap().as_string();
command_text = command_text.replacen(&format!("{{{}}}", expression_index), &substitution, 1);
}
}
self.execution_state = ExecutionState::Suspended;
return Some(SuspendReason::Command(command_text));
} else {
}
}
OpCode::AddOption => {
let line = if let Some(Value::StringValue(opt)) = instruction.operands.get(0).and_then(|o| o.value.as_ref()) {
let mut substitutions = Vec::new();
if let Some(Value::FloatValue(expression_count)) = instruction.operands.get(2).and_then(|o| o.value.as_ref()) {
let expression_count = *expression_count as u32;
substitutions.resize(expression_count as usize, String::new());
for expression_index in (0..expression_count as usize).rev() {
let substitution = self.state.stack.pop().unwrap().as_string();
substitutions[expression_index] = substitution;
}
}
Line::new(opt.clone(), substitutions)
} else {
panic!();
};
let node_name = if let Some(Value::StringValue(opt)) = instruction.operands.get(1).and_then(|o| o.value.as_ref()) {
opt.clone()
} else {
panic!();
};
self.state.current_options.push((line, node_name));
}
OpCode::ShowOptions => {
if self.state.current_options.is_empty() {
self.execution_state = ExecutionState::Stopped;
let last_node = self.state.current_node_name.clone();
self.state = VmState::new();
return Some(SuspendReason::DialogueComplete(last_node));
}
let mut options = Vec::new();
for (i, opt) in self.state.current_options.iter().enumerate() {
options.push(YarnOption::new(opt.0.clone(), i as u32, opt.1.clone()));
}
self.execution_state = ExecutionState::WaitingOnOptionSelection;
return Some(SuspendReason::Options(options));
}
OpCode::PushString => {
if let Some(Value::StringValue(val)) = &instruction.operands[0].value {
self.state.stack.push(YarnValue::Str(val.clone()));
} else {
}
}
OpCode::PushFloat => {
if let Some(Value::FloatValue(val)) = &instruction.operands[0].value {
self.state.stack.push(YarnValue::Number(*val));
} else {
}
}
OpCode::PushBool => {
if let Some(Value::BoolValue(val)) = &instruction.operands[0].value {
self.state.stack.push(YarnValue::Bool(*val));
} else {
}
}
OpCode::PushNull => {
self.state.stack.push(YarnValue::Null);
},
OpCode::JumpIfFalse => {
if let Some(val) = self.state.stack.last() {
if !val.as_bool() {
if let Some(Value::StringValue(label)) = &instruction.operands[0].value {
self.state.program_counter = self.find_instruction_point_for_label(label) - 1;
} else {
}
}
} else {
}
}
OpCode::Pop => {
self.state.stack.pop();
}
OpCode::CallFunc => {
if let Some(Value::StringValue(func_name)) = &instruction.operands[0].value {
if let Some(function) = self.library.get(func_name) {
let actual_param_count = self.state.stack.pop().unwrap().as_number() as u8;
let expected_param_count = match function.param_count {
ParamCount::N(n) => n,
ParamCount::Variadic => actual_param_count,
};
if expected_param_count != actual_param_count {
panic!(
"Function {} expected {}, but received {}",
func_name,
expected_param_count,
actual_param_count,
);
}
let result = if actual_param_count == 0 {
function.func.call(&[])
} else {
let mut parameters = vec![YarnValue::Null; actual_param_count as usize];
for i in (0..actual_param_count as usize).rev() {
let value = self.state.stack.pop().unwrap();
parameters[i] = value;
}
function.func.call(¶meters)
};
if let Some(result) = result {
self.state.stack.push(result);
}
} else {
}
} else {
}
}
OpCode::PushVariable => {
if let Some(Value::StringValue(var_name)) = &instruction.operands[0].value {
if let Some(val) = self.variable_storage.get(var_name) {
self.state.stack.push(val.clone());
} else {
self.state.stack.push(YarnValue::Null);
}
} else {
}
}
OpCode::StoreVariable => {
if let Some(Value::StringValue(var_name)) = &instruction.operands[0].value {
if let Some(val) = self.state.stack.last() {
self.variable_storage.insert(var_name.clone(), val.clone());
} else {
}
} else {
}
}
OpCode::Stop => {
self.execution_state = ExecutionState::Stopped;
let last_node = self.state.current_node_name.clone();
self.state = VmState::new();
return Some(SuspendReason::DialogueComplete(last_node));
}
OpCode::RunNode => {
if let Some(YarnValue::Str(node_name)) = self.state.stack.pop() {
let old_node = self.state.current_node_name.clone();
self.set_node(&node_name);
self.state.program_counter -= 1;
self.execution_state = ExecutionState::Suspended;
return Some(SuspendReason::NodeChange {
start: node_name.clone(),
end: old_node,
})
} else {
}
}
}
None
}
fn find_instruction_point_for_label(&self, label: &str) -> isize {
let instruction_point = self.program.nodes.get(&self.state.current_node_name)
.and_then(|node| node.labels.get(label));
if let Some(&instruction_point) = instruction_point {
instruction_point as isize
} else {
panic!("Unknown label {} in node {}", label, self.state.current_node_name);
}
}
}