use std::collections::BTreeMap;
use std::convert::Infallible;
use serde_json::Value;
use crate::avatar::AvatarDef;
use crate::behavior::BehaviorDef;
use crate::creation::CreationDef;
use crate::entity::{EntityPatch, WorldEntity};
use crate::identity::{EntityId, EntityRef};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntityRefScope {
Entity,
Avatar,
Creation,
}
impl EntityRefScope {
pub fn as_str(self) -> &'static str {
match self {
Self::Entity => "entity",
Self::Avatar => "avatar",
Self::Creation => "creation",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntityRefKind {
Bindable,
Id,
}
impl EntityRefKind {
pub fn as_str(self) -> &'static str {
match self {
Self::Bindable => "bindable",
Self::Id => "id",
}
}
}
pub struct EntityRefField {
pub scope: EntityRefScope,
pub path: &'static [&'static str],
pub kind: EntityRefKind,
}
pub const ENTITY_REFS: &[EntityRefField] = &[
EntityRefField {
scope: EntityRefScope::Entity,
path: &["behaviors", "*", "LookAt", "target"],
kind: EntityRefKind::Bindable,
},
EntityRefField {
scope: EntityRefScope::Entity,
path: &["behaviors", "*", "Orbit", "center"],
kind: EntityRefKind::Bindable,
},
EntityRefField {
scope: EntityRefScope::Entity,
path: &["ext-cinematography", "camera", "aim_entity"],
kind: EntityRefKind::Bindable,
},
EntityRefField {
scope: EntityRefScope::Entity,
path: &["ext-cinematography", "camera", "focus_entity"],
kind: EntityRefKind::Bindable,
},
EntityRefField {
scope: EntityRefScope::Entity,
path: &["parent"],
kind: EntityRefKind::Bindable,
},
EntityRefField {
scope: EntityRefScope::Avatar,
path: &["model_entity"],
kind: EntityRefKind::Bindable,
},
EntityRefField {
scope: EntityRefScope::Creation,
path: &["entities", "*"],
kind: EntityRefKind::Id,
},
];
pub fn refs_of(scope: EntityRefScope) -> impl Iterator<Item = &'static EntityRefField> {
ENTITY_REFS.iter().filter(move |field| field.scope == scope)
}
pub fn refs_of_kind(
scope: EntityRefScope,
kind: EntityRefKind,
) -> impl Iterator<Item = &'static EntityRefField> {
refs_of(scope).filter(move |field| field.kind == kind)
}
pub(crate) fn walk_ref_path<E>(
value: &mut Value,
path: &[&str],
f: &mut impl FnMut(&mut Value) -> Result<(), E>,
) -> Result<(), E> {
let Some((key, rest)) = path.split_first() else {
return Ok(());
};
if *key == "*" {
if let Value::Array(elements) = value {
for element in elements {
if rest.is_empty() {
f(element)?;
} else {
walk_ref_path(element, rest, f)?;
}
}
}
return Ok(());
}
let Some(next) = value.get_mut(*key) else {
return Ok(());
};
if rest.is_empty() {
f(next)
} else {
walk_ref_path(next, rest, f)
}
}
pub(crate) enum RefSlot<'a> {
Id(&'a mut EntityId),
Ref(&'a mut EntityRef),
Json(&'a mut Value),
}
impl RefSlot<'_> {
pub(crate) fn remap(self, remapped: &BTreeMap<u64, u64>) {
let held = match self {
Self::Id(id) => Some(id),
Self::Ref(EntityRef::Id(id)) => Some(id),
Self::Ref(EntityRef::Name(_)) => None,
Self::Json(value) => {
if let Some(id) = value.as_u64()
&& let Some(&to) = remapped.get(&id)
{
*value = Value::from(to);
}
None
}
};
if let Some(id) = held
&& let Some(&to) = remapped.get(&id.0)
{
*id = EntityId(to);
}
}
}
pub(crate) trait RefWalk {
fn scope() -> EntityRefScope;
fn walk<E>(
&mut self,
path: &[&str],
f: &mut dyn FnMut(RefSlot<'_>) -> Result<(), E>,
) -> Result<(), E>;
}
pub(crate) fn each_ref<R: RefWalk>(root: &mut R, mut f: impl FnMut(RefSlot<'_>)) {
for field in refs_of(R::scope()) {
root.walk(field.path, &mut |slot| {
f(slot);
Ok::<(), Infallible>(())
})
.unwrap_or_else(|never| match never {});
}
}
impl RefWalk for WorldEntity {
fn scope() -> EntityRefScope {
EntityRefScope::Entity
}
fn walk<E>(
&mut self,
path: &[&str],
f: &mut dyn FnMut(RefSlot<'_>) -> Result<(), E>,
) -> Result<(), E> {
match path {
["parent"] => {
if let Some(id) = &mut self.parent {
f(RefSlot::Id(id))?;
}
}
["behaviors", "*", variant, leaf] => {
for behavior in &mut self.behaviors {
if let Some(slot) = behavior_slot(behavior, variant, leaf) {
f(slot)?;
}
}
}
[key, rest @ ..] => {
if let Some(block) = self.extra.get_mut(*key) {
walk_ref_path(block, rest, &mut |leaf| f(RefSlot::Json(leaf)))?;
}
}
[] => {}
}
Ok(())
}
}
impl RefWalk for EntityPatch {
fn scope() -> EntityRefScope {
EntityRefScope::Entity
}
fn walk<E>(
&mut self,
path: &[&str],
f: &mut dyn FnMut(RefSlot<'_>) -> Result<(), E>,
) -> Result<(), E> {
match path {
["parent"] => {
if let Some(Some(id)) = &mut self.parent {
f(RefSlot::Id(id))?;
}
}
["behaviors", "*", variant, leaf] => {
for behavior in self.behaviors.iter_mut().flatten() {
if let Some(slot) = behavior_slot(behavior, variant, leaf) {
f(slot)?;
}
}
}
[key, rest @ ..] => {
if let Some(Some(block)) = self.extra.get_mut(*key) {
walk_ref_path(block, rest, &mut |leaf| f(RefSlot::Json(leaf)))?;
}
}
[] => {}
}
Ok(())
}
}
impl RefWalk for AvatarDef {
fn scope() -> EntityRefScope {
EntityRefScope::Avatar
}
fn walk<E>(
&mut self,
path: &[&str],
f: &mut dyn FnMut(RefSlot<'_>) -> Result<(), E>,
) -> Result<(), E> {
if let ["model_entity"] = path
&& let Some(model) = &mut self.model_entity
{
f(RefSlot::Ref(model))?;
}
Ok(())
}
}
impl RefWalk for CreationDef {
fn scope() -> EntityRefScope {
EntityRefScope::Creation
}
fn walk<E>(
&mut self,
path: &[&str],
f: &mut dyn FnMut(RefSlot<'_>) -> Result<(), E>,
) -> Result<(), E> {
if let ["entities", "*"] = path {
for id in &mut self.entities {
f(RefSlot::Id(id))?;
}
}
Ok(())
}
}
fn behavior_slot<'a>(
behavior: &'a mut BehaviorDef,
variant: &str,
leaf: &str,
) -> Option<RefSlot<'a>> {
match (variant, leaf, behavior) {
("Orbit", "center", BehaviorDef::Orbit { center: Some(center), .. }) => {
Some(RefSlot::Ref(center))
}
("LookAt", "target", BehaviorDef::LookAt { target }) => Some(RefSlot::Ref(target)),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_listed_path_visits_a_slot_in_a_full_root() {
fn visits<R: RefWalk>(root: &mut R) -> Vec<(String, usize)> {
refs_of(R::scope())
.map(|field| {
let mut visited = 0;
root.walk(field.path, &mut |_| {
visited += 1;
Ok::<(), Infallible>(())
})
.unwrap_or_else(|never| match never {});
(field.path.join("/"), visited)
})
.collect()
}
let camera_block =
serde_json::json!({"camera": {"aim_entity": 2, "focus_entity": 2}});
let mut entity = WorldEntity::new(1, "e");
entity.parent = Some(EntityId(2));
entity.behaviors = vec![
BehaviorDef::LookAt {
target: EntityRef::id(2),
},
BehaviorDef::Orbit {
center: Some(EntityRef::id(2)),
center_point: None,
radius: 2.0,
speed: 10.0,
axis: [0.0, 1.0, 0.0],
phase: 0.0,
tilt: 0.0,
},
];
entity
.extra
.insert("ext-cinematography".into(), camera_block.clone());
for (path, visited) in visits(&mut entity) {
assert!(visited > 0, "entity path {path} visited nothing");
}
let mut patch = EntityPatch {
parent: Some(Some(EntityId(2))),
behaviors: Some(vec![
BehaviorDef::LookAt {
target: EntityRef::id(2),
},
BehaviorDef::Orbit {
center: Some(EntityRef::id(2)),
center_point: None,
radius: 2.0,
speed: 10.0,
axis: [0.0, 1.0, 0.0],
phase: 0.0,
tilt: 0.0,
},
]),
..Default::default()
};
patch
.extra
.insert("ext-cinematography".into(), Some(camera_block));
for (path, visited) in visits(&mut patch) {
assert!(visited > 0, "patch path {path} visited nothing");
}
let mut avatar = AvatarDef {
model_entity: Some(EntityRef::id(2)),
..Default::default()
};
for (path, visited) in visits(&mut avatar) {
assert!(visited > 0, "avatar path {path} visited nothing");
}
let mut creation = CreationDef {
id: crate::identity::CreationId(1),
name: "c".into(),
semantic_category: None,
bbox_half: [0.0; 3],
entities: vec![EntityId(2)],
parts: Vec::new(),
};
for (path, visited) in visits(&mut creation) {
assert!(visited > 0, "creation path {path} visited nothing");
}
}
#[test]
fn the_json_walk_matches_walk_ref_path_semantics() {
let mut value = serde_json::json!({
"behaviors": [{"Orbit": {"center": "sun"}}, {"Spin": {}}],
"parent": "sun",
});
walk_ref_path(&mut value, &["behaviors", "*", "Orbit", "center"], &mut |leaf| {
*leaf = serde_json::json!(1);
Ok::<(), Infallible>(())
})
.unwrap_or_else(|never| match never {});
walk_ref_path(&mut value, &["missing", "key"], &mut |_| -> Result<(), Infallible> {
panic!("a missing key walks to nothing")
})
.unwrap_or_else(|never| match never {});
assert_eq!(value["behaviors"][0]["Orbit"]["center"], 1);
assert_eq!(value["parent"], "sun", "a path not walked is untouched");
}
#[test]
fn an_extension_leaf_remaps_its_id_and_leaves_a_name_alone() {
let remapped: BTreeMap<u64, u64> = [(1, 7)].into_iter().collect();
let mut id = serde_json::json!(1);
RefSlot::Json(&mut id).remap(&remapped);
assert_eq!(id, 7);
let mut other = serde_json::json!(3);
RefSlot::Json(&mut other).remap(&remapped);
assert_eq!(other, 3, "an id outside the remap stays");
let mut name = serde_json::json!("maya");
RefSlot::Json(&mut name).remap(&remapped);
assert_eq!(name, "maya", "names bind at ingestion, a merge moves ids");
}
}