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";
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct MetadataPerInstance {
pub ignore_unknown_instances: bool,
#[serde(serialize_with = "crate::path_serializer::serialize_option")]
pub source_path: Option<PathBuf>,
pub project_definition: Option<(String, ProjectNode)>,
}
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) {
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");
let mut path_to_snapshot = self.instances_per_path.descend(root_path, path);
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()))
},
None => {
snapshot_imfs_path(&context, &imfs, &path_to_snapshot, None)
},
};
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();
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)
}