forked from rojo-rbx/rojo
289 lines
9.8 KiB
Rust
289 lines
9.8 KiB
Rust
use std::{
|
|
borrow::Cow,
|
|
collections::{HashSet, HashMap},
|
|
path::{Path, PathBuf},
|
|
str,
|
|
sync::{Arc, Mutex},
|
|
};
|
|
|
|
use rlua::Lua;
|
|
use serde::{Serialize, Deserialize};
|
|
use log::{info, trace, error};
|
|
use rbx_dom_weak::{RbxTree, RbxId};
|
|
|
|
use crate::{
|
|
project::{Project, ProjectNode},
|
|
message_queue::MessageQueue,
|
|
imfs::{Imfs, ImfsItem},
|
|
path_map::PathMap,
|
|
rbx_snapshot::{
|
|
SnapshotError,
|
|
SnapshotContext,
|
|
SnapshotPluginContext,
|
|
SnapshotPluginEntry,
|
|
snapshot_project_tree,
|
|
snapshot_project_node,
|
|
snapshot_imfs_path,
|
|
},
|
|
snapshot_reconciler::{InstanceChanges, reify_root, reconcile_subtree},
|
|
};
|
|
|
|
const INIT_SCRIPT: &str = "init.lua";
|
|
const INIT_SERVER_SCRIPT: &str = "init.server.lua";
|
|
const INIT_CLIENT_SCRIPT: &str = "init.client.lua";
|
|
|
|
/// `source_path` or `project_definition` or both must both be Some.
|
|
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
|
pub struct MetadataPerInstance {
|
|
pub ignore_unknown_instances: bool,
|
|
|
|
/// The path on the filesystem that the instance was read from the
|
|
/// filesystem if it came from the filesystem.
|
|
#[serde(serialize_with = "crate::path_serializer::serialize_option")]
|
|
pub source_path: Option<PathBuf>,
|
|
|
|
/// Information about the instance that came from the project that defined
|
|
/// it, if that's where it was defined.
|
|
///
|
|
/// A key-value pair where the key should be the name of the instance and
|
|
/// the value is the ProjectNode from the instance's project.
|
|
pub project_definition: Option<(String, ProjectNode)>,
|
|
}
|
|
|
|
/// Contains all of the state needed to update an `RbxTree` in real time using
|
|
/// the in-memory filesystem, as well as messaging to Rojo clients what
|
|
/// instances have actually updated at any point.
|
|
pub struct RbxSession {
|
|
tree: RbxTree,
|
|
|
|
instances_per_path: PathMap<HashSet<RbxId>>,
|
|
metadata_per_instance: HashMap<RbxId, MetadataPerInstance>,
|
|
message_queue: Arc<MessageQueue<InstanceChanges>>,
|
|
imfs: Arc<Mutex<Imfs>>,
|
|
}
|
|
|
|
impl RbxSession {
|
|
pub fn new(
|
|
project: Arc<Project>,
|
|
imfs: Arc<Mutex<Imfs>>,
|
|
message_queue: Arc<MessageQueue<InstanceChanges>>,
|
|
) -> Result<RbxSession, SnapshotError> {
|
|
let mut instances_per_path = PathMap::new();
|
|
let mut metadata_per_instance = HashMap::new();
|
|
|
|
let plugin_context = if cfg!(feature = "server-plugins") {
|
|
let lua = Lua::new();
|
|
let mut callback_key = None;
|
|
|
|
lua.context(|context| {
|
|
let callback = context.load(r#"
|
|
return function(snapshot)
|
|
print("got my snapshot:", snapshot)
|
|
print("name:", snapshot.name, "class name:", snapshot.className)
|
|
end"#)
|
|
.set_name("a cool plugin").unwrap()
|
|
.call::<(), rlua::Function>(()).unwrap();
|
|
|
|
callback_key = Some(context.create_registry_value(callback).unwrap());
|
|
});
|
|
|
|
let plugins = vec![
|
|
SnapshotPluginEntry {
|
|
file_name_filter: String::new(),
|
|
callback: callback_key.unwrap(),
|
|
}
|
|
];
|
|
|
|
Some(SnapshotPluginContext { lua, plugins })
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let context = SnapshotContext {
|
|
plugin_context,
|
|
};
|
|
|
|
let tree = {
|
|
let temp_imfs = imfs.lock().unwrap();
|
|
reify_initial_tree(
|
|
&project,
|
|
&context,
|
|
&temp_imfs,
|
|
&mut instances_per_path,
|
|
&mut metadata_per_instance,
|
|
)?
|
|
};
|
|
|
|
Ok(RbxSession {
|
|
tree,
|
|
instances_per_path,
|
|
metadata_per_instance,
|
|
message_queue,
|
|
imfs,
|
|
})
|
|
}
|
|
|
|
fn path_created_or_updated(&mut self, path: &Path) {
|
|
// TODO: Track paths actually updated in each step so we can ignore
|
|
// redundant changes.
|
|
let mut changes = InstanceChanges::default();
|
|
|
|
{
|
|
let imfs = self.imfs.lock().unwrap();
|
|
let root_path = imfs.get_root_for_path(path)
|
|
.expect("Path was outside in-memory filesystem roots");
|
|
|
|
// Find the closest instance in the tree that currently exists
|
|
let mut path_to_snapshot = self.instances_per_path.descend(root_path, path);
|
|
|
|
// If this is a file that might affect its parent if modified, we
|
|
// should snapshot its parent instead.
|
|
match path_to_snapshot.file_name().unwrap().to_str() {
|
|
Some(INIT_SCRIPT) | Some(INIT_SERVER_SCRIPT) | Some(INIT_CLIENT_SCRIPT) => {
|
|
path_to_snapshot.pop();
|
|
},
|
|
_ => {},
|
|
}
|
|
|
|
trace!("Snapshotting path {}", path_to_snapshot.display());
|
|
|
|
let instances_at_path = self.instances_per_path.get(&path_to_snapshot)
|
|
.expect("Metadata did not exist for path")
|
|
.clone();
|
|
|
|
let context = SnapshotContext {
|
|
plugin_context: None,
|
|
};
|
|
|
|
for instance_id in &instances_at_path {
|
|
let instance_metadata = self.metadata_per_instance.get(&instance_id)
|
|
.expect("Metadata for instance ID did not exist");
|
|
|
|
let maybe_snapshot = match &instance_metadata.project_definition {
|
|
Some((instance_name, project_node)) => {
|
|
snapshot_project_node(&context, &imfs, &project_node, Cow::Owned(instance_name.clone()))
|
|
// .unwrap_or_else(|_| panic!("Could not generate instance snapshot for path {}", path_to_snapshot.display()))
|
|
},
|
|
None => {
|
|
snapshot_imfs_path(&context, &imfs, &path_to_snapshot, None)
|
|
// .unwrap_or_else(|_| panic!("Could not generate instance snapshot for path {}", path_to_snapshot.display()))
|
|
},
|
|
};
|
|
|
|
let snapshot = match maybe_snapshot {
|
|
Ok(Some(snapshot)) => snapshot,
|
|
Ok(None) => {
|
|
trace!("Path resulted in no snapshot being generated.");
|
|
return;
|
|
},
|
|
Err(err) => {
|
|
error!("Rojo couldn't turn one of the project's files into Roblox instances.");
|
|
error!("Any changes to the file have been ignored.");
|
|
error!("{}", err);
|
|
return;
|
|
},
|
|
};
|
|
|
|
trace!("Snapshot: {:#?}", snapshot);
|
|
|
|
reconcile_subtree(
|
|
&mut self.tree,
|
|
*instance_id,
|
|
&snapshot,
|
|
&mut self.instances_per_path,
|
|
&mut self.metadata_per_instance,
|
|
&mut changes,
|
|
);
|
|
}
|
|
}
|
|
|
|
if changes.is_empty() {
|
|
trace!("No instance changes triggered from file update.");
|
|
} else {
|
|
trace!("Pushing changes: {}", changes);
|
|
self.message_queue.push_messages(&[changes]);
|
|
}
|
|
}
|
|
|
|
pub fn path_created(&mut self, path: &Path) {
|
|
info!("Path created: {}", path.display());
|
|
self.path_created_or_updated(path);
|
|
}
|
|
|
|
pub fn path_updated(&mut self, path: &Path) {
|
|
info!("Path updated: {}", path.display());
|
|
|
|
{
|
|
let imfs = self.imfs.lock().unwrap();
|
|
|
|
// If the path doesn't exist or is a directory, we don't care if it
|
|
// updated
|
|
match imfs.get(path) {
|
|
Some(ImfsItem::Directory(_)) => {
|
|
trace!("Updated path was a directory, ignoring.");
|
|
return;
|
|
},
|
|
None => {
|
|
trace!("Updated path did not exist in IMFS, ignoring.");
|
|
return;
|
|
},
|
|
Some(ImfsItem::File(_)) => {},
|
|
}
|
|
}
|
|
|
|
self.path_created_or_updated(path);
|
|
}
|
|
|
|
pub fn path_removed(&mut self, path: &Path) {
|
|
info!("Path removed: {}", path.display());
|
|
self.instances_per_path.remove(path);
|
|
self.path_created_or_updated(path);
|
|
}
|
|
|
|
pub fn path_renamed(&mut self, from_path: &Path, to_path: &Path) {
|
|
info!("Path renamed from {} to {}", from_path.display(), to_path.display());
|
|
self.instances_per_path.remove(from_path);
|
|
self.path_created_or_updated(from_path);
|
|
self.path_created_or_updated(to_path);
|
|
}
|
|
|
|
pub fn get_tree(&self) -> &RbxTree {
|
|
&self.tree
|
|
}
|
|
|
|
pub fn get_all_instance_metadata(&self) -> &HashMap<RbxId, MetadataPerInstance> {
|
|
&self.metadata_per_instance
|
|
}
|
|
|
|
pub fn get_instance_metadata(&self, id: RbxId) -> Option<&MetadataPerInstance> {
|
|
self.metadata_per_instance.get(&id)
|
|
}
|
|
}
|
|
|
|
pub fn construct_oneoff_tree(project: &Project, imfs: &Imfs) -> Result<RbxTree, SnapshotError> {
|
|
let mut instances_per_path = PathMap::new();
|
|
let mut metadata_per_instance = HashMap::new();
|
|
let context = SnapshotContext {
|
|
plugin_context: None,
|
|
};
|
|
|
|
reify_initial_tree(project, &context, imfs, &mut instances_per_path, &mut metadata_per_instance)
|
|
}
|
|
|
|
fn reify_initial_tree(
|
|
project: &Project,
|
|
context: &SnapshotContext,
|
|
imfs: &Imfs,
|
|
instances_per_path: &mut PathMap<HashSet<RbxId>>,
|
|
metadata_per_instance: &mut HashMap<RbxId, MetadataPerInstance>,
|
|
) -> Result<RbxTree, SnapshotError> {
|
|
let snapshot = match snapshot_project_tree(&context, imfs, project)? {
|
|
Some(snapshot) => snapshot,
|
|
None => panic!("Project did not produce any instances"),
|
|
};
|
|
|
|
let mut changes = InstanceChanges::default();
|
|
let tree = reify_root(&snapshot, instances_per_path, metadata_per_instance, &mut changes);
|
|
|
|
Ok(tree)
|
|
} |