use std::collections::HashSet;
use std::sync::Arc;
use anyhow::{anyhow, Result};
use crate::frame::*;
use crate::generators::*;
use crate::instance::*;
use crate::renderer::render_entity;
use crate::repository::*;
use crate::semantics::*;
pub trait ValueAssigner {
fn new(name: String, value: serde_json::Value) -> Self;
}
pub trait EntityAssigner {
fn new(
name: String,
class_names: ClassNamesToRoll,
min: CardinalityValue,
max: CardinalityValue,
injectors: Injectors,
) -> Self;
}
pub trait RefInjectCommand {
fn make(name: String, path: Vec<String>) -> Arc<dyn InjectCommand + Send + Sync>;
}
#[derive(Clone)]
pub enum CardinalityValue {
Number(i32),
Variable(String),
Undefined,
}
#[derive(Clone)]
pub enum ClassNamesToRoll {
List(Vec<String>),
Indirect(String),
Unset(),
}
#[derive(Clone)]
pub struct AttrCommandAssigner {
pub name: String,
pub value: serde_json::Value,
}
impl ValueAssigner for AttrCommandAssigner {
fn new(name: String, value: serde_json::Value) -> Self {
AttrCommandAssigner { name, value }
}
}
impl AttrCommand for AttrCommandAssigner {
fn apply(
&self,
_ctx: &mut Context,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
if entity.as_object().unwrap().contains_key(&self.name) && self.value.is_boolean() {
log::warn!(
"Entity class {} has an override with value {} for attribute {} already set to {}",
entity["class"],
self.value,
self.name,
entity[&self.name]
);
}
entity[&self.name] = self.value.to_owned();
Ok(())
}
fn revert(
&self,
_ctx: &mut Context,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
entity.clear(&self.name);
Ok(())
}
fn value(&self) -> Option<String> {
Some(self.value.as_str().unwrap().to_string())
}
}
#[derive(Clone)]
pub struct AttrCommandWeakAssigner {
pub name: String,
pub value: serde_json::Value,
}
impl AttrCommand for AttrCommandWeakAssigner {
fn apply(
&self,
_ctx: &mut Context,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
entity[&self.name] = serde_json::Value::Null;
Ok(())
}
fn revert(
&self,
_ctx: &mut Context,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
entity.clear(&self.name);
Ok(())
}
fn value(&self) -> Option<String> {
Some(self.value.as_str().unwrap().to_string())
}
}
#[derive(Clone)]
pub struct AttrCommandDice {
pub name: String,
pub number_of_dice: i32,
pub dice_type: i32,
pub dice_modifier: i32,
}
impl AttrCommand for AttrCommandDice {
fn apply(
&self,
_ctx: &mut Context,
builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let mut total: i32 = 0;
for _ in 0..self.number_of_dice {
total += builder.randomizer.in_range(1, self.dice_type);
}
total += self.dice_modifier;
let entity = tx.load(euid)?;
entity[&self.name] = serde_json::to_value(total).unwrap();
Ok(())
}
fn revert(
&self,
_ctx: &mut Context,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
entity.clear(&self.name);
Ok(())
}
}
#[derive(Clone)]
pub struct AttrCommandPrerenderedAssigner {
pub name: String,
pub value: serde_json::Value,
}
impl AttrCommand for AttrCommandPrerenderedAssigner {
fn apply(
&self,
_ctx: &mut Context,
builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let ro_entity = tx.retrieve(euid)?;
let rendered = render_entity(builder.sandbox, tx, &ro_entity.value, true)?;
let prerendered = builder
.templating_env
.render_str(self.value.as_str().unwrap(), &rendered)?;
let entity = tx.load(euid)?;
entity[&self.name] = serde_json::Value::String(prerendered);
Ok(())
}
fn revert(
&self,
_ctx: &mut Context,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
entity.clear(&self.name);
Ok(())
}
fn value(&self) -> Option<String> {
Some(self.value.as_str().unwrap().to_string())
}
}
#[derive(Clone)]
pub struct AttrCommandRollEntity {
pub name: String,
pub class_names: ClassNamesToRoll,
pub min: CardinalityValue,
pub max: CardinalityValue,
pub injectors: Injectors,
}
impl EntityAssigner for AttrCommandRollEntity {
fn new(
name: String,
class_names: ClassNamesToRoll,
min: CardinalityValue,
max: CardinalityValue,
injectors: Injectors,
) -> Self {
AttrCommandRollEntity {
name,
class_names,
min,
max,
injectors,
}
}
}
impl AttrCommand for AttrCommandRollEntity {
fn apply(
&self,
ctx: &mut Context,
builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let (min, max) = match ctx {
Context::Appending(_) => (1, 1),
Context::Rerolling(_) => (1, 1),
Context::Rolling => (
resolve_value(builder, &self.min),
resolve_value(builder, &self.max),
),
_ => return Err(anyhow!("Invalid context when applying roll: {:#?}", ctx)),
};
let class_names = match &self.class_names {
ClassNamesToRoll::List(v) => v.clone(),
ClassNamesToRoll::Indirect(s) => {
let entity = tx.load(euid)?;
vec![entity[s].as_str().unwrap().to_string()]
}
ClassNamesToRoll::Unset() => unreachable!(),
};
let uid = {
let entity = tx.load(euid)?;
entity["uid"].as_str().unwrap().to_string()
};
let seq =
if max == 0 && min == 0 {
serde_json::json!([])
} else {
let n = if max - min > 0 {
builder.randomizer.in_range(min, max)
} else {
min
};
let mut ret: serde_json::Value = {
let entity = tx.load(euid)?;
match ctx {
Context::Appending(_) => entity[&self.name].clone(),
Context::Rerolling(_) => entity[&self.name].clone(),
Context::Rolling => serde_json::json!([]),
_ => return Err(anyhow!("Invalid context when applying roll: {:#?}", ctx)),
}
};
for _ in 0..n {
let actual_class_name = builder.randomizer.choose::<String>(&class_names);
let generated_uid =
roll(builder, tx, actual_class_name, &uid, Some(&self.injectors))?;
{
let entity = tx.load(&generated_uid)?;
entity["$parent"] = serde_json::json!({
"uid": &uid,
"attr": self.name,
});
tx.save(&generated_uid)?;
}
if let Context::Rerolling(payload) = ctx {
if let Some(index) = ret.as_array_mut().unwrap().iter().position(|value| {
value.as_str().unwrap() == payload.existing_uid.as_str()
}) {
ret.as_array_mut().unwrap()[index] =
serde_json::Value::from(generated_uid.clone());
payload.new_uid = Some(generated_uid.clone());
} else {
}
} else {
ret.as_array_mut()
.unwrap()
.push(serde_json::Value::from(generated_uid.clone()));
}
if let Context::Appending(payload) = ctx {
payload.appended_uid = Some(generated_uid.clone());
}
collect(builder, tx, &uid, generated_uid.as_str(), actual_class_name)?;
}
serde_json::json!(ret)
};
{
let entity = tx.load(euid)?;
entity[&self.name] = seq;
}
Ok(())
}
fn revert(
&self,
_ctx: &mut Context,
builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
if let Some(arr) = entity[&self.name].as_array() {
for euid in arr.clone() {
unroll(builder, tx, euid.as_str().unwrap(), Some(&self.injectors))?;
}
}
Ok(())
}
}
#[derive(Clone)]
pub struct AttrCommandRollFromList {
pub name: String,
pub list: Vec<serde_json::Value>,
}
impl AttrCommand for AttrCommandRollFromList {
fn apply(
&self,
_ctx: &mut Context,
builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
entity[&self.name] = builder.randomizer.choose(&self.list).to_owned();
Ok(())
}
fn revert(
&self,
_ctx: &mut Context,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
entity.clear(&self.name);
Ok(())
}
}
#[derive(Clone)]
pub struct AttrCommandContext {
pub name: String,
pub context_parent: String,
pub context_attr: String,
}
impl AttrCommand for AttrCommandContext {
fn apply(
&self,
_ctx: &mut Context,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
entity[&self.name] = serde_json::json!({
"type" : "context",
"spec" : {
"parent" : self.context_parent,
"attr" : self.context_attr
}
});
Ok(())
}
fn revert(
&self,
_ctx: &mut Context,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
entity.clear(&self.name);
Ok(())
}
}
#[derive(Clone)]
pub struct AttrCommandRollFromVariable {
pub name: String,
pub var: String,
}
impl AttrCommand for AttrCommandRollFromVariable {
fn apply(
&self,
_ctx: &mut Context,
builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let value = builder.sandbox.globals[&self.var]
.as_array()
.ok_or(anyhow!("Unable to find {}", self.var))?;
let entity = tx.load(euid)?;
entity[&self.name] = builder.randomizer.choose(value).to_owned();
Ok(())
}
fn revert(
&self,
_ctx: &mut Context,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
entity.clear(&self.name);
Ok(())
}
}
#[derive(Clone)]
pub struct AttrCommandUseEntity {
pub name: String,
pub class_names: ClassNamesToRoll,
pub min: CardinalityValue,
pub max: CardinalityValue,
injectors: Injectors,
}
impl EntityAssigner for AttrCommandUseEntity {
fn new(
name: String,
class_names: ClassNamesToRoll,
min: CardinalityValue,
max: CardinalityValue,
injectors: Injectors,
) -> Self {
AttrCommandUseEntity {
name,
class_names,
min,
max,
injectors,
}
}
}
impl AttrCommand for AttrCommandUseEntity {
fn apply(
&self,
ctx: &mut Context,
builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
if entity.is_missing(&self.name) {
entity[&self.name] =
serde_json::to_value(Vec::new() as Vec<serde_json::Value>).unwrap();
}
let (min, max) = match ctx {
Context::Appending(_) => (1, 1),
Context::Rolling => (
resolve_value(builder, &self.min),
resolve_value(builder, &self.max),
),
_ => return Err(anyhow!("Invalid context when applying use: {:#?}", ctx)),
};
let class_names = match &self.class_names {
ClassNamesToRoll::List(l) => l,
_ => unreachable!(),
};
for _ in 0..builder.randomizer.in_range(min, max) {
let cls = builder.randomizer.choose::<String>(class_names);
if let Ok(Some(selected_uid)) = use_collected(builder, tx, euid, cls) {
for injector in self.injectors.appenders.as_slice() {
injector.inject(builder, tx, &selected_uid, euid)?;
}
add_user_to_entity(tx, &selected_uid, euid, &self.name)?;
{
let entity = tx.load(euid)?;
let list = entity[&self.name].as_array_mut().unwrap();
list.push(serde_json::to_value(selected_uid)?);
}
}
}
Ok(())
}
fn revert(
&self,
ctx: &mut Context,
builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
if *ctx != Context::Unrolling {
return Err(anyhow!(
"Reverting a use entity command is only allowed with Context::Unrolling"
));
}
let entity = tx.load(euid)?;
let uids_in_use = entity[&self.name].as_array().unwrap();
for uid_in_use in uids_in_use.clone() {
let uid_in_use = uid_in_use.as_str().unwrap();
for injector in self.injectors.appenders.as_slice() {
injector.eject(builder, tx, uid_in_use, euid)?;
}
if let Ok(entity_in_use) = tx.load(uid_in_use) {
let entity_in_use_class_name = entity_in_use["class"].as_str().unwrap().to_string();
entity_in_use["$users"]
.as_array_mut()
.unwrap()
.retain(|user| !(user["uid"] == euid && user["attr"] == self.name));
tx.save(uid_in_use)?;
recycle(tx, euid, uid_in_use, &entity_in_use_class_name)?;
}
}
Ok(())
}
}
#[derive(Clone)]
pub struct AttrCommandPickEntity {
pub name: String,
pub class_names: ClassNamesToRoll,
pub min: CardinalityValue,
pub max: CardinalityValue,
injectors: Injectors,
}
impl EntityAssigner for AttrCommandPickEntity {
fn new(
name: String,
class_names: ClassNamesToRoll,
min: CardinalityValue,
max: CardinalityValue,
injectors: Injectors,
) -> Self {
AttrCommandPickEntity {
name,
class_names,
min,
max,
injectors,
}
}
}
impl AttrCommand for AttrCommandPickEntity {
fn apply(
&self,
ctx: &mut Context,
builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
if entity.is_missing(&self.name) {
entity[&self.name] =
serde_json::to_value(Vec::new() as Vec<serde_json::Value>).unwrap();
}
let (min, max) = match ctx {
Context::Appending(_) => (1, 1),
Context::Rolling => (
resolve_value(builder, &self.min),
resolve_value(builder, &self.max),
),
_ => return Err(anyhow!("Invalid context when applying pick: {:#?}", ctx)),
};
let class_names = match &self.class_names {
ClassNamesToRoll::List(l) => l,
_ => unreachable!(),
};
let mut uniqueness_check_set: HashSet<String> = HashSet::new();
for _ in 0..builder.randomizer.in_range(min, max) {
let cls = builder.randomizer.choose::<String>(class_names);
if let Ok(Some(selected_uid)) = pick_collected(builder, tx, euid, cls) {
if !uniqueness_check_set.insert(selected_uid.clone()) {
continue;
}
for injector in self.injectors.appenders.as_slice() {
injector.inject(builder, tx, &selected_uid, euid)?;
}
add_user_to_entity(tx, &selected_uid, euid, &self.name)?;
let entity = tx.load(euid)?;
let list = entity[&self.name].as_array_mut().unwrap();
list.push(serde_json::to_value(selected_uid)?);
}
}
Ok(())
}
fn revert(
&self,
ctx: &mut Context,
builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
) -> Result<()> {
if *ctx != Context::Unrolling {
return Err(anyhow!(
"Reverting a pick entity command is only allowed with Context::Unrolling"
));
}
let entity = tx.load(euid)?;
let uids_in_use = entity[&self.name].as_array().unwrap();
for uid_in_use in uids_in_use.clone() {
let uid_in_use = uid_in_use.as_str().unwrap();
for injector in self.injectors.appenders.as_slice() {
injector.eject(builder, tx, uid_in_use, euid)?;
}
if let Ok(entity_in_use) = tx.load(uid_in_use) {
entity_in_use["$users"]
.as_array_mut()
.unwrap()
.retain(|user| !(user["uid"] == euid && user["attr"] == self.name));
tx.save(uid_in_use)?;
}
}
Ok(())
}
}
#[derive(Clone)]
pub struct InjectCommandSetValue {
pub name: String,
pub value: serde_json::Value,
}
impl ValueAssigner for InjectCommandSetValue {
fn new(name: String, value: serde_json::Value) -> Self {
InjectCommandSetValue { name, value }
}
}
impl InjectCommand for InjectCommandSetValue {
fn inject(
&self,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
_caller: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
entity[&self.name] = self.value.clone();
Ok(())
}
fn eject(
&self,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
_caller: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
entity[&self.name] = serde_json::Value::from(false);
Ok(())
}
}
#[derive(Clone)]
pub struct InjectCommandDiceRoll {
pub name: String,
pub number_of_dice: i32,
pub dice_type: i32,
pub dice_modifier: i32,
}
impl InjectCommand for InjectCommandDiceRoll {
fn inject(
&self,
builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
_caller: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
let mut total: i32 = 0;
for _ in 0..self.number_of_dice {
total += builder.randomizer.in_range(1, self.dice_type);
}
total += self.dice_modifier;
entity[&self.name] = serde_json::to_value(total).unwrap();
Ok(())
}
fn eject(
&self,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
_caller: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
entity[&self.name] = serde_json::Value::from(false);
Ok(())
}
}
#[derive(Clone)]
pub struct InjectCommandRollFromList {
pub name: String,
pub list: Vec<serde_json::Value>,
}
impl InjectCommand for InjectCommandRollFromList {
fn inject(
&self,
builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
_caller: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
entity[&self.name] = builder.randomizer.choose(&self.list).to_owned();
Ok(())
}
fn eject(
&self,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
_caller: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
entity.clear(&self.name);
Ok(())
}
}
#[derive(Clone)]
pub struct InjectCommandCopyValue {
pub name: String,
pub path: Vec<String>,
}
impl RefInjectCommand for InjectCommandCopyValue {
fn make(name: String, path: Vec<String>) -> Arc<dyn InjectCommand + Send + Sync> {
Arc::new(InjectCommandCopyValue { name, path })
}
}
impl InjectCommand for InjectCommandCopyValue {
fn inject(
&self,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
caller: &str,
) -> Result<()> {
let value = {
if let Some((pointer_uid, pointer_attr_name)) = walk_path(caller, &self.path, tx)? {
let src = tx.load(pointer_uid.as_str().unwrap())?;
src[pointer_attr_name.as_str().unwrap()].clone()
} else {
serde_json::Value::from(false)
}
};
let entity = tx.load(euid)?;
entity[&self.name] = value;
Ok(())
}
fn eject(
&self,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
_caller: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
entity[&self.name] = serde_json::Value::from(false);
Ok(())
}
}
pub struct InjectCommandPtr {
pub name: String,
pub path: Vec<String>,
}
impl RefInjectCommand for InjectCommandPtr {
fn make(name: String, path: Vec<String>) -> Arc<dyn InjectCommand + Send + Sync> {
Arc::new(InjectCommandPtr { name, path })
}
}
impl InjectCommand for InjectCommandPtr {
fn inject(
&self,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
caller: &str,
) -> Result<()> {
if let Some((pointer_uid, pointer_attr_name)) = walk_path(caller, &self.path, tx)? {
let value = serde_json::json!({
"type": "pointer",
"spec": {
"uid": pointer_uid,
"attr": pointer_attr_name
}
});
let entity = tx.load(euid)?;
entity[&self.name] = value;
add_user_to_entity(tx, pointer_uid.as_str().unwrap(), euid, &self.name)?;
Ok(())
} else {
Err(anyhow!(
"walking an attribute path failed for {}",
self.name
))
}
}
fn eject(
&self,
_builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
_caller: &str,
) -> Result<()> {
let entity = tx.load(euid)?;
entity[&self.name] = serde_json::Value::from(false);
Ok(())
}
}
fn add_user_to_entity(
tx: &mut ReadWriteTransaction,
uid: &str,
user_uid: &str,
user_attr: &str,
) -> Result<()> {
let selected_entity = tx.load(uid)?;
if selected_entity.is_missing("$users") {
selected_entity["$users"] = serde_json::json!([]);
}
selected_entity["$users"]
.as_array_mut()
.unwrap()
.push(serde_json::json!({
"uid": user_uid,
"attr": user_attr,
}));
tx.save(uid)?;
Ok(())
}
fn walk_path(
starting_uid: &str,
path: &[String],
tx: &mut ReadWriteTransaction,
) -> Result<Option<(serde_json::Value, serde_json::Value)>> {
let mut pointer_uid: serde_json::Value = serde_json::json!(starting_uid);
let mut pointer_attr_name = serde_json::to_value(path.first()).unwrap();
for (index, path_part) in path.iter().enumerate() {
let src = tx.load(pointer_uid.as_str().unwrap())?;
let pointed_value = &src[path_part];
if index == path.len() - 1 {
pointer_attr_name = serde_json::to_value(path_part).unwrap();
} else if let Some(value_as_array) = pointed_value.as_array() {
if value_as_array.is_empty() {
return Ok(None);
}
pointer_uid = value_as_array.first().unwrap().clone();
} else {
pointer_attr_name = serde_json::to_value(path_part).unwrap();
}
}
Ok(Some((pointer_uid, pointer_attr_name)))
}
fn resolve_value(builder: &SandboxBuilder, cv: &CardinalityValue) -> i32 {
match cv {
CardinalityValue::Number(n) => *n,
CardinalityValue::Variable(v) => {
log::info!("{}", v);
builder.sandbox.globals.get(v).unwrap().as_i64().unwrap() as i32
}
CardinalityValue::Undefined => 1,
}
}