Galactica/crates/content/src/lib.rs

291 lines
6.9 KiB
Rust
Raw Normal View History

2023-12-29 15:46:09 -08:00
#![warn(missing_docs)]
//! This subcrate is responsible for loading, parsing, validating game content,
//! which is usually stored in `./content`.
2023-12-30 16:57:03 -08:00
mod handle;
mod part;
2023-12-29 15:14:04 -08:00
mod util;
2023-12-24 23:03:00 -08:00
2023-12-30 16:57:03 -08:00
use anyhow::{Context, Result};
use std::{
collections::HashMap,
fs::File,
io::Read,
path::{Path, PathBuf},
};
2023-12-27 19:51:58 -08:00
use toml;
2023-12-25 09:01:12 -08:00
use walkdir::WalkDir;
2023-12-30 16:57:03 -08:00
pub use handle::{FactionHandle, TextureHandle};
pub use part::{Engine, EnginePoint, Faction, Gun, GunPoint, Ship, System, Texture};
2023-12-27 19:51:58 -08:00
mod syntax {
2023-12-30 16:57:03 -08:00
use anyhow::{bail, Result};
2023-12-27 19:51:58 -08:00
use serde::Deserialize;
2023-12-30 16:57:03 -08:00
use std::collections::HashMap;
use crate::part::{engine, faction, gun, ship, system, texture};
2023-12-27 19:51:58 -08:00
#[derive(Debug, Deserialize)]
pub struct Root {
2023-12-27 20:13:39 -08:00
pub gun: Option<HashMap<String, gun::syntax::Gun>>,
pub ship: Option<HashMap<String, ship::syntax::Ship>>,
pub system: Option<HashMap<String, system::syntax::System>>,
2023-12-28 17:04:41 -08:00
pub engine: Option<HashMap<String, engine::syntax::Engine>>,
pub texture: Option<HashMap<String, texture::syntax::Texture>>,
2023-12-30 16:57:03 -08:00
pub faction: Option<HashMap<String, faction::syntax::Faction>>,
}
impl Root {
pub fn new() -> Self {
Self {
gun: None,
ship: None,
system: None,
engine: None,
texture: None,
2023-12-30 16:57:03 -08:00
faction: None,
}
}
pub fn merge(&mut self, other: Root) -> Result<()> {
// Insert if not exists
// TODO: replace with a macro and try_insert once that is stable
if let Some(a) = other.gun {
if self.gun.is_none() {
self.gun = Some(a);
} else {
let sg = self.gun.as_mut().unwrap();
for (k, v) in a {
if sg.contains_key(&k) {
bail!("Repeated gun name {k}");
} else {
sg.insert(k, v);
}
}
}
}
if let Some(a) = other.ship {
if self.ship.is_none() {
self.ship = Some(a);
} else {
let sg = self.ship.as_mut().unwrap();
for (k, v) in a {
if sg.contains_key(&k) {
bail!("Repeated ship name {k}");
} else {
sg.insert(k, v);
}
}
}
}
if let Some(a) = other.system {
if self.system.is_none() {
self.system = Some(a);
} else {
let sg = self.system.as_mut().unwrap();
for (k, v) in a {
if sg.contains_key(&k) {
bail!("Repeated system name {k}");
} else {
sg.insert(k, v);
}
}
}
}
if let Some(a) = other.engine {
if self.engine.is_none() {
self.engine = Some(a);
} else {
let sg = self.engine.as_mut().unwrap();
for (k, v) in a {
if sg.contains_key(&k) {
bail!("Repeated engine name {k}");
} else {
sg.insert(k, v);
}
}
}
}
if let Some(a) = other.texture {
if self.texture.is_none() {
self.texture = Some(a);
} else {
let sg = self.texture.as_mut().unwrap();
for (k, v) in a {
if sg.contains_key(&k) {
bail!("Repeated texture name {k}");
} else {
sg.insert(k, v);
}
}
}
}
2023-12-30 16:57:03 -08:00
if let Some(a) = other.faction {
if self.faction.is_none() {
self.faction = Some(a);
} else {
let sg = self.faction.as_mut().unwrap();
for (k, v) in a {
if sg.contains_key(&k) {
bail!("Repeated faction name {k}");
} else {
sg.insert(k, v);
}
}
}
}
return Ok(());
}
2023-12-27 19:51:58 -08:00
}
}
trait Build {
type InputSyntax;
2023-12-27 19:51:58 -08:00
/// Build a processed System struct from raw serde data
fn build(root: Self::InputSyntax, ct: &mut Content) -> Result<()>
2023-12-27 19:51:58 -08:00
where
Self: Sized;
}
2023-12-28 17:06:33 -08:00
/// Represents generic game content, not connected to any game objects.
2023-12-27 19:51:58 -08:00
#[derive(Debug)]
pub struct Content {
2023-12-29 15:46:09 -08:00
/// Star systems
2023-12-30 16:57:03 -08:00
pub systems: Vec<part::system::System>,
2023-12-29 15:46:09 -08:00
/// Ship bodies
2023-12-30 16:57:03 -08:00
pub ships: Vec<part::ship::Ship>,
2023-12-29 15:46:09 -08:00
/// Gun outfits
2023-12-30 16:57:03 -08:00
pub guns: Vec<part::gun::Gun>,
2023-12-29 15:46:09 -08:00
/// Engine outfits
2023-12-30 16:57:03 -08:00
pub engines: Vec<part::engine::Engine>,
2023-12-27 19:51:58 -08:00
/// Textures
2023-12-30 16:57:03 -08:00
pub textures: Vec<part::texture::Texture>,
/// Factions
pub factions: Vec<part::faction::Faction>,
/// Map strings to texture handles
/// This is never used outside this crate.
2023-12-30 16:57:03 -08:00
texture_index: HashMap<String, handle::TextureHandle>,
/// The texture to use for starfield stars
2023-12-30 16:57:03 -08:00
starfield_handle: Option<handle::TextureHandle>,
/// Root directory for textures
texture_root: PathBuf,
/// Name of starfield texture
starfield_texture_name: String,
2023-12-27 20:13:39 -08:00
}
2023-12-27 19:51:58 -08:00
2023-12-27 20:13:39 -08:00
impl Content {
fn try_parse(path: &Path) -> Result<syntax::Root> {
let mut file_string = String::new();
let _ = File::open(path)?.read_to_string(&mut file_string);
let file_string = file_string.trim();
return Ok(toml::from_str(&file_string)?);
}
2023-12-25 09:01:12 -08:00
/// Get the texture handle for the starfield texture
pub fn get_starfield_handle(&self) -> TextureHandle {
match self.starfield_handle {
Some(h) => h,
None => unreachable!("Starfield texture hasn't been loaded yet!"),
}
}
/// Get a texture from a handle
2023-12-30 16:57:03 -08:00
pub fn get_texture(&self, h: TextureHandle) -> &Texture {
// In theory, this could fail if h has a bad index, but that shouldn't ever happen.
// The only TextureHandles that exist should be created by this crate.
return &self.textures[h.index];
2023-12-27 19:51:58 -08:00
}
2023-12-25 09:01:12 -08:00
2023-12-30 16:57:03 -08:00
/// Get a faction from a handle
pub fn get_faction(&self, h: FactionHandle) -> &Faction {
return &self.factions[h.index];
}
2023-12-29 15:46:09 -08:00
/// Load content from a directory.
pub fn load_dir(
path: PathBuf,
texture_root: PathBuf,
starfield_texture_name: String,
) -> Result<Self> {
let mut root = syntax::Root::new();
2023-12-27 19:51:58 -08:00
for e in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
if e.metadata().unwrap().is_file() {
// TODO: better warnings
match e.path().extension() {
Some(t) => {
if t.to_str() != Some("toml") {
println!("[WARNING] {e:#?} is not a toml file, skipping.");
continue;
}
}
None => {
println!("[WARNING] {e:#?} is not a toml file, skipping.");
continue;
}
}
let path = e.path();
let this_root = Self::try_parse(path)
.with_context(|| format!("Could not read {}", path.display()))?;
root.merge(this_root)
2023-12-27 19:51:58 -08:00
.with_context(|| format!("Could not parse {}", path.display()))?;
2023-12-25 09:01:12 -08:00
}
}
let mut content = Self {
systems: Vec::new(),
ships: Vec::new(),
guns: Vec::new(),
engines: Vec::new(),
textures: Vec::new(),
2023-12-30 16:57:03 -08:00
factions: Vec::new(),
texture_index: HashMap::new(),
starfield_handle: None,
texture_root,
starfield_texture_name,
};
// Order here matters, usually
if root.texture.is_some() {
2023-12-30 16:57:03 -08:00
part::texture::Texture::build(root.texture.take().unwrap(), &mut content)?;
}
if root.ship.is_some() {
2023-12-30 16:57:03 -08:00
part::ship::Ship::build(root.ship.take().unwrap(), &mut content)?;
}
if root.gun.is_some() {
2023-12-30 16:57:03 -08:00
part::gun::Gun::build(root.gun.take().unwrap(), &mut content)?;
}
if root.engine.is_some() {
2023-12-30 16:57:03 -08:00
part::engine::Engine::build(root.engine.take().unwrap(), &mut content)?;
}
if root.system.is_some() {
2023-12-30 16:57:03 -08:00
part::system::System::build(root.system.take().unwrap(), &mut content)?;
}
if root.faction.is_some() {
part::faction::Faction::build(root.faction.take().unwrap(), &mut content)?;
}
2023-12-27 19:51:58 -08:00
return Ok(content);
}
2023-12-25 09:01:12 -08:00
}