Clean up and document code throughout the server

This commit is contained in:
Lucien Greathouse
2019-01-15 12:38:31 -08:00
parent c421fd0b25
commit b87943e39d
6 changed files with 60 additions and 93 deletions

View File

@@ -7,7 +7,6 @@ extern crate serde_derive;
#[cfg(test)]
extern crate tempfile;
// pub mod roblox_studio;
pub mod commands;
pub mod fs_watcher;
pub mod imfs;

View File

@@ -19,6 +19,10 @@ pub fn get_listener_id() -> ListenerId {
ListenerId(LAST_ID.fetch_add(1, Ordering::SeqCst))
}
/// A message queue with persistent history that can be subscribed to.
///
/// Definitely non-optimal, but a simple design that works well for the
/// synchronous web server Rojo uses, Rouille.
#[derive(Default)]
pub struct MessageQueue<T> {
messages: RwLock<Vec<T>>,

View File

@@ -9,8 +9,9 @@ struct PathMapNode<T> {
children: HashSet<PathBuf>,
}
/// A map from paths to instance IDs, with a bit of additional data that enables
/// removing a path and all of its child paths from the tree more quickly.
/// A map from paths to another type, like instance IDs, with a bit of
/// additional data that enables removing a path and all of its child paths from
/// the tree more quickly.
#[derive(Debug, Serialize)]
pub struct PathMap<T> {
nodes: HashMap<PathBuf, PathMapNode<T>>,
@@ -71,6 +72,14 @@ impl<T> PathMap<T> {
Some(root_value)
}
/// Traverses the route between `start_path` and `target_path` and returns
/// the path closest to `target_path` in the tree.
///
/// This is useful when trying to determine what paths need to be marked as
/// altered when a change to a path is registered. Depending on the order of
/// FS events, a file remove event could be followed by that file's
/// directory being removed, in which case we should process that
/// directory's parent.
pub fn descend(&self, start_path: &Path, target_path: &Path) -> PathBuf {
let relative_path = target_path.strip_prefix(start_path)
.expect("target_path did not begin with start_path");

View File

@@ -12,7 +12,8 @@ use rbx_tree::RbxValue;
pub static PROJECT_FILENAME: &'static str = "roblox-project.json";
// Serde is silly.
// Methods used for Serde's default value system, which doesn't support using
// value literals directly, only functions that return values.
const fn yeah() -> bool {
true
}
@@ -21,6 +22,40 @@ const fn is_true(value: &bool) -> bool {
*value
}
/// SourceProject is the format that users author projects on-disk. Since we
/// want to do things like transforming paths to be absolute before handing them
/// off to the rest of Rojo, we use this intermediate struct.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct SourceProject {
name: String,
tree: SourceProjectNode,
#[serde(skip_serializing_if = "Option::is_none")]
serve_port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
serve_place_ids: Option<HashSet<u64>>,
}
impl SourceProject {
/// Consumes the SourceProject and yields a Project, ready for prime-time.
pub fn into_project(self, project_file_location: &Path) -> Project {
let tree = self.tree.into_project_node(project_file_location);
Project {
name: self.name,
tree,
serve_port: self.serve_port,
serve_place_ids: self.serve_place_ids,
file_location: PathBuf::from(project_file_location),
}
}
}
/// Similar to SourceProject, the structure of nodes in the project tree is
/// slightly different on-disk than how we want to handle them in the rest of
/// Rojo.
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
enum SourceProjectNode {
@@ -44,6 +79,7 @@ enum SourceProjectNode {
}
impl SourceProjectNode {
/// Consumes the SourceProjectNode and turns it into a ProjectNode.
pub fn into_project_node(self, project_file_location: &Path) -> ProjectNode {
match self {
SourceProjectNode::Instance { class_name, mut children, properties, ignore_unknown_instances } => {
@@ -78,31 +114,7 @@ impl SourceProjectNode {
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct SourceProject {
name: String,
tree: SourceProjectNode,
#[serde(skip_serializing_if = "Option::is_none")]
serve_port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
serve_place_ids: Option<HashSet<u64>>,
}
impl SourceProject {
pub fn into_project(self, project_file_location: &Path) -> Project {
let tree = self.tree.into_project_node(project_file_location);
Project {
name: self.name,
tree,
serve_port: self.serve_port,
serve_place_ids: self.serve_place_ids,
file_location: PathBuf::from(project_file_location),
}
}
}
/// Error returned by Project::load_exact
#[derive(Debug, Fail)]
pub enum ProjectLoadExactError {
#[fail(display = "IO error: {}", _0)]
@@ -112,6 +124,7 @@ pub enum ProjectLoadExactError {
JsonError(#[fail(cause)] serde_json::Error),
}
/// Error returned by Project::load_fuzzy
#[derive(Debug, Fail)]
pub enum ProjectLoadFuzzyError {
#[fail(display = "Project not found")]
@@ -133,6 +146,7 @@ impl From<ProjectLoadExactError> for ProjectLoadFuzzyError {
}
}
/// Error returned by Project::init_place and Project::init_model
#[derive(Debug, Fail)]
pub enum ProjectInitError {
AlreadyExists(PathBuf),
@@ -150,6 +164,7 @@ impl fmt::Display for ProjectInitError {
}
}
/// Error returned by Project::save
#[derive(Debug, Fail)]
pub enum ProjectSaveError {
#[fail(display = "JSON error: {}", _0)]
@@ -340,7 +355,7 @@ impl Project {
// TODO: Check for specific error kinds, convert 'not found' to Result.
let location_metadata = fs::metadata(start_location).ok()?;
// If this is a file, we should assume it's the config we want
// If this is a file, assume it's the config the user was looking for.
if location_metadata.is_file() {
return Some(start_location.to_path_buf());
} else if location_metadata.is_dir() {

View File

@@ -1,63 +0,0 @@
//! Interactions with Roblox Studio's installation, including its location and
//! mechanisms like PluginSettings.
#![allow(dead_code)]
use std::path::PathBuf;
#[cfg(all(not(debug_assertions), not(feature = "bundle-plugin")))]
compile_error!("`bundle-plugin` feature must be set for release builds.");
#[cfg(feature = "bundle-plugin")]
static PLUGIN_RBXM: &'static [u8] = include_bytes!("../target/plugin.rbxmx");
#[cfg(target_os = "windows")]
pub fn get_install_location() -> Option<PathBuf> {
use std::env;
let local_app_data = env::var("LocalAppData").ok()?;
let mut location = PathBuf::from(local_app_data);
location.push("Roblox");
Some(location)
}
#[cfg(target_os = "macos")]
pub fn get_install_location() -> Option<PathBuf> {
unimplemented!();
}
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
pub fn get_install_location() -> Option<PathBuf> {
// Roblox Studio doesn't install on any other platforms!
None
}
pub fn get_plugin_location() -> Option<PathBuf> {
let mut location = get_install_location()?;
location.push("Plugins/Rojo.rbxmx");
Some(location)
}
#[cfg(feature = "bundle-plugin")]
pub fn install_bundled_plugin() -> Option<()> {
use std::fs::File;
use std::io::Write;
info!("Installing plugin...");
let mut file = File::create(get_plugin_location()?).ok()?;
file.write_all(PLUGIN_RBXM).ok()?;
Some(())
}
#[cfg(not(feature = "bundle-plugin"))]
pub fn install_bundled_plugin() -> Option<()> {
info!("Skipping plugin installation, bundle-plugin not set.");
Some(())
}

View File

@@ -25,6 +25,7 @@ digraph RojoTree {
];
"#;
/// Compiles DOT source to SVG by invoking dot on the command line.
pub fn graphviz_to_svg(source: &str) -> String {
let mut child = Command::new("dot")
.arg("-Tsvg")
@@ -42,6 +43,7 @@ pub fn graphviz_to_svg(source: &str) -> String {
String::from_utf8(output.stdout).expect("Failed to parse stdout as UTF-8")
}
/// A Display wrapper struct to visualize an RbxSession as SVG.
pub struct VisualizeRbxSession<'a>(pub &'a RbxSession);
impl<'a> fmt::Display for VisualizeRbxSession<'a> {
@@ -81,6 +83,7 @@ fn visualize_rbx_node(session: &RbxSession, id: RbxId, output: &mut fmt::Formatt
Ok(())
}
/// A Display wrapper struct to visualize an Imfs as SVG.
pub struct VisualizeImfs<'a>(pub &'a Imfs);
impl<'a> fmt::Display for VisualizeImfs<'a> {