Created
July 18, 2019 04:29
-
-
Save tinaun/05547a38324ee300ecd857de8f89c03e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! deserialization from horrible xml | |
//! | |
//! | |
mod helpers; | |
use quick_xml::Reader; | |
use quick_xml::events::{Event, BytesStart}; | |
use error::{Error, Result}; | |
use std::path::{PathBuf, Path}; | |
use std::io::{BufReader}; | |
use std::collections::HashMap; | |
use std::fs::File; | |
use openbound::{Assets, Asset, AssetType, Openbound}; | |
pub use self::helpers::*; | |
type Classes = HashMap<String, Vec<Event<'static>>>; | |
struct FileVisitor { | |
name: String, | |
level_root: PathBuf, | |
deps: Vec<PathBuf>, | |
classes: Classes, | |
reader: Reader<BufReader<File>>, | |
depth: u8, | |
jump_back: bool, // FIXME: refactor to remove this | |
expand: bool, | |
class: Option<String>, | |
ev_idx: usize, | |
} | |
impl FileVisitor { | |
fn new<T: AsRef<Path>, U: AsRef<Path>>(root: T, path: U) -> Result<Self> { | |
Self::with_classes(root, path, HashMap::new()) | |
} | |
fn with_classes<T: AsRef<Path>, U:AsRef<Path>>( | |
root: T, | |
path: U, | |
classes: Classes) | |
-> Result<Self> | |
{ | |
let reader = Reader::from_file(root.as_ref().join(path.as_ref()))?; | |
let name = path.as_ref().file_name() | |
.and_then(|f| f.to_str()) | |
.unwrap_or("").to_string(); | |
println!("parsing {}", name); | |
Ok(FileVisitor { | |
name, | |
deps: vec![], | |
classes, | |
level_root: root.as_ref().to_path_buf(), | |
reader, | |
depth: 0, | |
jump_back: false, | |
expand: true, | |
class: None, | |
ev_idx: 0, | |
}) | |
} | |
// panics if event is not Start or Empty | |
fn expand_class<'a>(&mut self, e: &Event<'a>) -> Result<Option<Event<'static>>> { | |
if self.expand { | |
let buf = e.unwrap(); | |
if let Ok(class_name) = buf.get_attr_owned("class", &self.reader) { | |
println!("expanding class: {:?}", class_name); | |
if let Some(class) = self.classes.get(&class_name) { | |
self.class = Some(class_name); | |
self.ev_idx = 0; | |
let start = class[0].is_start(); | |
let ev = class[0].unwrap(); | |
if ev.name() != buf.name() { | |
return Err(Error::Level( | |
format!("mismatched classes\nexpected: {:?} found: {:?}", | |
ev.name(), buf.name()) | |
)); | |
} else { | |
let other = buf.attributes().map(|a| a.unwrap()); | |
let ev = ev.clone().with_attributes(other).into_owned(); | |
if start { | |
return Ok(Some(Event::Start(ev))); | |
} else { | |
return Ok(Some(Event::Empty(ev))); | |
} | |
} | |
} | |
} | |
} | |
Ok(None) | |
} | |
fn read_tag_at_depth<'a>(&mut self, temp_buf: &'a mut Vec<u8>, depth: u8) -> Result<Option<Event<'static>>> { | |
loop { | |
let next = if self.class.is_none() { | |
self.reader.read_event(temp_buf).map(|e| e.into_owned())? | |
} else { | |
self.ev_idx += 1; // if in a class, get events from the class | |
let c = self.class.as_ref().unwrap(); | |
let c = self.classes.get(c).unwrap(); | |
if self.ev_idx == c.len() { | |
self.class = None; | |
continue; | |
} | |
c[self.ev_idx].clone() | |
}; | |
if self.jump_back { | |
self.depth -= 1; | |
self.jump_back = false; | |
} | |
match next { | |
e @ Event::Start(_) => { | |
self.depth += 1; | |
match &e { | |
&Event::Start(ref buf) => { | |
//println!("open: {:?} {} {}", String::from_utf8_lossy(&buf), depth, self.depth); | |
if buf.name() != b"sburb" && self.depth == 1 { | |
return Err(Error::Level(format!("Root tag of {} must be <sburb>", self.name))); | |
} else if self.depth == 1 { | |
if let Ok(attr) = buf.get_attr("levelPath") { | |
let value = attr.unescaped_value()?; | |
let value = self.reader.decode(value.as_ref()); | |
self.level_root.push(value.as_ref()); | |
println!("new root: {}", self.level_root.display()); | |
} | |
} else if buf.name() == b"dependencies" { | |
self.parse_deps(temp_buf)?; | |
continue; | |
} else if buf.name() == b"classes" { | |
self.expand = false; | |
} | |
}, | |
_ => {}, | |
} | |
if let Some(e) = self.expand_class(&e)? { | |
return Ok(Some(e)); | |
} else { | |
return Ok(Some(e)); | |
} | |
}, | |
Event::Empty(buf) => { | |
self.depth += 1; | |
self.jump_back = true; | |
let e = Event::Empty(buf); | |
let e = self.expand_class(&e)?.unwrap_or(e); | |
if let &Event::Start(_) = &e { | |
self.jump_back = false; | |
}; | |
return Ok(Some(e)); | |
}, | |
e @ Event::Text(_) => { | |
return Ok(Some(e)); | |
} | |
Event::End(buf) => { | |
//println!("close: {:?} {} {}", String::from_utf8_lossy(&buf), depth, self.depth); | |
if buf.name() == b"classes" { | |
self.expand = true; | |
} | |
self.depth -= 1; | |
if self.depth < depth { | |
return Ok(None); | |
} | |
}, | |
Event::Eof if self.deps.len() == 0 => return Ok(None), | |
Event::Eof => { | |
let new_dep = self.deps.pop().unwrap(); | |
let name = new_dep.file_name() | |
.and_then(|f| f.to_str()) | |
.unwrap_or("").to_string(); | |
println!("parsing {}", name); | |
let reader = Reader::from_file(&new_dep)?; | |
temp_buf.clear(); | |
self.reader = reader; | |
}, | |
_ => {}, | |
} | |
} | |
} | |
fn read_tag<'a>(&mut self, temp_buf: &'a mut Vec<u8>) -> Result<Option<Event<'a>>> { | |
self.read_tag_at_depth(temp_buf, 0) | |
} | |
/// calls f on each element named 'name' and is a a direct child of | |
/// the current element | |
fn parse_children<F>(&mut self, temp_buf: &mut Vec<u8>, mut f: F) -> Result<()> | |
where F: FnMut(BytesStart, &Reader<BufReader<File>>) -> Result<()> | |
{ | |
let layer = self.depth + 1; | |
while let Some(ev) = self.read_tag_at_depth(temp_buf, layer)? { | |
match ev { | |
Event::Start(buf) | Event::Empty(buf) => { | |
println!("{:?} {} {}", String::from_utf8_lossy(&buf), layer, self.depth); | |
f(buf, &self.reader)?; | |
}, | |
_ => {}, | |
} | |
} | |
Ok(()) | |
} | |
fn parse_deps(&mut self, temp_buf: &mut Vec<u8>) -> Result<()> { | |
let start = self.depth; | |
while self.depth >= start { | |
if let Some(Event::Start(ref buf)) = self.read_tag(temp_buf)? { | |
if buf.name() == b"dependency" { | |
if let Some(Event::Text(ref buf)) = self.read_tag(temp_buf)? { | |
let buf = buf.unescaped()?; | |
let buf = self.reader.decode(buf.as_ref()); | |
self.deps.push(self.level_root.join(buf.as_ref())); | |
println!("{:?}", self.deps); | |
} | |
} | |
} | |
} | |
temp_buf.clear(); | |
Ok(()) | |
} | |
fn parse_assets(&mut self, temp_buf: &mut Vec<u8>, assets: &mut Assets) -> Result<()> { | |
let start = self.depth; | |
while self.depth >= start { | |
if let Some(Event::Start(ref buf)) = self.read_tag(temp_buf)? { | |
if buf.name() == b"asset" { | |
let asset_name = buf.get_attr_owned("name", &self.reader)?; | |
let ty = match buf.get_attr("type")?.unescaped_value()?.as_ref() { | |
b"graphic" => { Some(AssetType::Graphic) }, | |
b"audio" => { Some(AssetType::Audio) }, | |
b"font" => { Some(AssetType::Font) }, | |
b"path" => { Some(AssetType::Path) }, | |
_ => { None }, | |
}; | |
let src = self.reader.read_text(buf.name(), &mut vec![])?; | |
self.depth -= 1; | |
//println!("self.depth {} {}", self.depth, src); | |
ty.map(|ty| { | |
assets.map.insert(asset_name, Asset { | |
src, ty, | |
loaded: None, | |
}); | |
}); | |
} | |
} | |
} | |
temp_buf.clear(); | |
Ok(()) | |
} | |
fn parse_classes(&mut self, classes: &mut Classes) -> Result<()> { | |
let start = self.depth; | |
let mut read_buf = vec![]; | |
let mut events = vec![]; | |
let mut current_class = None; | |
while self.depth >= start { | |
match self.reader.read_event(&mut read_buf)? { | |
Event::Empty(buf) => { | |
if self.depth == start { | |
let class_name = buf.get_attr_owned("class", &self.reader)?; | |
println!("class: {}", class_name); | |
events.push(Event::Empty(buf).into_owned()); | |
let mut old_events = vec![]; | |
::std::mem::swap(&mut events, &mut old_events); | |
classes.insert(class_name, old_events); | |
} else { | |
events.push(Event::Empty(buf).into_owned()); | |
} | |
}, | |
Event::Start(buf) => { | |
if self.depth == start { | |
let class_name = buf.get_attr_owned("class", &self.reader)?; | |
println!("class: {}", class_name); | |
current_class = Some(class_name); | |
} | |
events.push(Event::Start(buf).into_owned()); | |
self.depth += 1; | |
}, | |
Event::End(buf) => { | |
self.depth -= 1; | |
if self.depth == start { | |
let mut old_events = vec![]; | |
::std::mem::swap(&mut events, &mut old_events); | |
current_class.take().map(|c| { | |
classes.insert(c , old_events); | |
}); | |
} else { | |
events.push(Event::End(buf).into_owned()); | |
} | |
}, | |
e => { | |
if self.depth > start { | |
events.push(e.into_owned()); | |
} | |
} | |
} | |
read_buf.clear(); | |
} | |
Ok(()) | |
} | |
} | |
pub fn from_root(dir: PathBuf, root: Option<PathBuf>) -> Result<Openbound> { | |
let root = root.unwrap_or(PathBuf::from("levels/openbound/openbound.xml")); | |
let mut reader = FileVisitor::new(&dir, &root)?; | |
let mut temp_buf = vec![]; | |
// we need to run through the file tree twice - once for collecting assets + classes, the next for | |
// everything else - who the fuck decided to use adhoc mixin classes here | |
let mut assets = None; | |
let mut classes = HashMap::new(); | |
let mut name = "[unnamed]".to_string(); | |
let mut start_character = None; | |
while let Some(ev) = reader.read_tag(&mut temp_buf)? { | |
match ev { | |
Event::Start(buf) => { | |
if buf.name() == b"sburb" && assets.is_none() { | |
let attr = buf.get_attr("resourcePath")?; | |
start_character = buf.get_attr_owned("char", &reader.reader).ok(); | |
let value = attr.unescaped_value()?; | |
let value = reader.reader.decode(value.as_ref()); | |
if let Ok(v) = buf.get_attr_owned("name", &reader.reader) { | |
name = v; | |
} | |
assets = Some(Assets::new(dir.join(value.as_ref()))); | |
} else if buf.name() == b"assets" { | |
assets.as_mut().map(|a| reader.parse_assets(&mut temp_buf, a)); | |
} else if buf.name() == b"classes" { | |
reader.parse_classes(&mut classes)?; | |
} | |
}, | |
_ => {} | |
} | |
} | |
let mut reader = FileVisitor::with_classes(dir, root, classes)?; | |
let assets = assets.ok_or(Error::Level("invalid asset path!".to_string()))?; | |
let mut hud = HashMap::new(); | |
let mut sprites = HashMap::new(); | |
let mut map = String::new(); | |
let mut paths = Vec::new(); | |
let mut size = [0, 0]; | |
temp_buf.clear(); | |
while let Some(ev) = reader.read_tag(&mut temp_buf)? { | |
match ev { | |
Event::Start(buf) => { | |
if buf.name() == b"room" { | |
if let Ok(m) = buf.get_attr_owned("walkableMap", &reader.reader) { | |
map = m; | |
} | |
if let Ok(x) = buf.get_attr_int("width", &reader.reader) { | |
size[0] = x; | |
} | |
if let Ok(y) = buf.get_attr_int("height", &reader.reader) { | |
size[1] = y; | |
} | |
} else if (buf.name() == b"sprite" || buf.name() == b"character") && reader.expand { | |
let (name, mut sprite) = if buf.name() == b"sprite" { | |
parse_sprite(buf, &reader.reader)? | |
} else { | |
parse_character(buf, &reader.reader)? | |
}; | |
println!("found sprite {}", name); | |
reader.parse_children(&mut temp_buf, |buf, rdr| { | |
match buf.name() { | |
b"animation" => { | |
if let Ok(_) = buf.get_attr("sliced") { | |
println!("skipping sliced animation..."); | |
return Ok(()); | |
} | |
let name = buf.get_attr_owned("name", rdr).ok(); | |
let anim = Animation::parse(buf, rdr)?; | |
let name = name.unwrap_or(anim.sprite.clone()); | |
println!("animation: {}", name); | |
sprite.animations.insert(name, anim); | |
} | |
b"action" => { | |
let name = buf.get_attr_owned("name", rdr).ok(); | |
println!("action: {:?}", name); | |
sprite.actions.push(name.unwrap_or(String::new())); | |
} | |
_ => {}, | |
} | |
Ok(()) | |
})?; | |
sprites.insert(name, sprite); | |
} else if buf.name() == b"dialogsprites"{ | |
reader.parse_children(&mut temp_buf, |buf, rdr| { | |
if buf.name() != b"animation" { | |
return Ok(()); | |
} | |
if let Ok(_) = buf.get_attr("sliced") { | |
println!("skipping sliced animation..."); | |
return Ok(()); | |
} | |
let name = buf.get_attr_owned("name", rdr)?; | |
println!("dialogsprite: {}", name); | |
let anim = Animation::parse(buf, rdr)?; | |
hud.insert(name, anim); | |
Ok(()) | |
})?; | |
} else if buf.name() == b"paths" { | |
reader.parse_children(&mut temp_buf, |buf, rdr| { | |
if buf.name() != b"motionpath" { | |
return Ok(()); | |
} | |
let path = helpers::parse_motion_path(buf, rdr, &assets)?; | |
paths.push(path); | |
Ok(()) | |
})?; | |
} | |
}, | |
_ => {} | |
} | |
} | |
Ok(Openbound { | |
name, | |
assets, | |
sprites, | |
hud, | |
start_character, | |
map, | |
size, | |
paths, | |
}) | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment