use std::path::{Path, PathBuf};
use glam::Vec3;
use crate::navigator::{Focus, Navigator, PortalNav, SkyNav, WorldLoader};
pub struct Loom<L: WorldLoader> {
nav: Navigator,
loader: L,
}
impl<L: WorldLoader> Loom<L> {
pub fn new(worlds_root: impl Into<PathBuf>, loader: L) -> Self {
Self {
nav: Navigator::new(worlds_root),
loader,
}
}
pub fn open(&mut self, locator: &str, anchor: Vec3) {
self.nav.open(locator, anchor);
}
pub fn load_file(&mut self, path: &Path, anchor: Vec3) -> Option<Vec3> {
self.nav.load_file(&mut self.loader, path, anchor)
}
pub fn update(&mut self, dt: f32, player_pos: Vec3) -> Option<Vec3> {
self.nav.update(dt, &mut self.loader, player_pos)
}
pub fn title(&self) -> Option<&str> {
self.nav.title()
}
pub fn description(&self) -> Option<&str> {
self.nav.world_description()
}
pub fn world_id(&self) -> &str {
self.nav.world_id()
}
pub fn sky(&self) -> Option<SkyNav> {
self.nav.sky()
}
pub fn portals(&self) -> &[PortalNav] {
self.nav.portals()
}
pub fn presence_relays(&self) -> &[String] {
self.nav.presence_relays()
}
pub fn focused(&self, player_pos: Vec3) -> Option<Focus> {
self.nav.focused(player_pos)
}
pub fn near_portal(&self, player_pos: Vec3) -> Option<(&str, &str)> {
self.nav.near_portal(player_pos)
}
pub fn is_traveling(&self) -> bool {
self.nav.is_traveling()
}
pub fn armed(&self) -> bool {
self.nav.armed()
}
pub fn last_error(&self) -> Option<&str> {
self.nav.last_error()
}
pub fn locator(&self) -> Option<&str> {
self.nav.locator()
}
pub fn world_source(&self) -> Option<&str> {
self.nav.world_source()
}
pub fn interact(&mut self, player_pos: Vec3) -> Vec<crate::behavior::Action> {
self.nav.interact(player_pos)
}
pub fn tick_behaviors(&mut self, dt: f32) -> Vec<crate::behavior::Action> {
self.nav.tick_behaviors(dt)
}
pub fn back(&mut self, anchor: Vec3) -> bool {
self.nav.back(anchor)
}
pub fn forward(&mut self, anchor: Vec3) -> bool {
self.nav.forward(anchor)
}
pub fn go_home(&mut self, anchor: Vec3) -> bool {
self.nav.go_home(anchor)
}
pub fn loader(&self) -> &L {
&self.loader
}
pub fn navigator(&self) -> &Navigator {
&self.nav
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::navigator::LoadedWorldMeta;
use infinite_manifest::WorldManifest;
struct CountLoader(u32);
impl WorldLoader for CountLoader {
fn load(
&mut self,
m: &WorldManifest,
_assets: &crate::assets::AssetSource,
anchor: Vec3,
) -> LoadedWorldMeta {
self.0 += 1;
LoadedWorldMeta {
title: m.world.title.clone(),
world_id: m.world.id.clone(),
spawn: anchor,
portals: vec![],
interactables: vec![],
sky: SkyNav::default(),
presence_relays: vec![],
colliders: vec![],
}
}
}
#[test]
fn facade_loads_a_local_world_through_the_owned_loader() {
let dir = std::env::temp_dir().join("loom_facade_test");
let _ = std::fs::create_dir_all(&dir);
let file = dir.join("world.json");
std::fs::write(
&file,
r#"{ "thread": "thread/0.1", "world": { "id": "w", "title": "Facade World" },
"spawns": [{ "name": "entry", "position": [0, 0, 2] }] }"#,
)
.unwrap();
let mut loom = Loom::new(&dir, CountLoader(0));
let spawn = loom.load_file(&file, Vec3::new(1.0, 0.0, 1.0));
assert_eq!(
spawn,
Some(Vec3::new(1.0, crate::navigator::FEET_DROP, 1.0))
);
assert_eq!(loom.title(), Some("Facade World"));
assert_eq!(loom.loader().0, 1, "the facade drove the owned loader");
}
}