use crate::prelude::*;
use bevy::prelude::*;
use bevy::utils::{HashMap, HashSet};
pub(crate) use compilation::{
RecompileLoadedYarnFilesEvent, YarnFilesBeingLoaded, YarnProjectConfigToLoad,
};
use std::fmt::Debug;
use std::iter;
mod compilation;
pub(crate) fn project_plugin(app: &mut App) {
app.fn_plugin(compilation::project_compilation_plugin)
.add_event::<LoadYarnProjectEvent>();
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, SystemSet)]
pub(crate) struct CompilationSystemSet;
#[derive(Resource)]
pub struct YarnProject {
pub(crate) yarn_files: HashSet<Handle<YarnFile>>,
pub(crate) compilation: Compilation,
pub(crate) localizations: Option<Localizations>,
pub(crate) asset_server: AssetServer,
pub(crate) metadata: HashMap<LineId, Vec<String>>,
pub(crate) watching_for_changes: bool,
pub(crate) development_file_generation: DevelopmentFileGeneration,
}
impl Debug for YarnProject {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("YarnProject")
.field("yarn_files", &self.yarn_files)
.field("compilation", &self.compilation)
.field("localizations", &self.localizations)
.field("asset_server", &())
.field("metadata", &self.metadata)
.field("watching_for_changes", &self.watching_for_changes)
.finish()
}
}
impl YarnProject {
pub fn yarn_files(&self) -> impl Iterator<Item = &Handle<YarnFile>> {
self.yarn_files.iter()
}
pub fn compilation(&self) -> &Compilation {
&self.compilation
}
pub fn localizations(&self) -> Option<&Localizations> {
self.localizations.as_ref()
}
pub fn create_dialogue_runner(&self) -> DialogueRunner {
self.build_dialogue_runner().build()
}
pub fn build_dialogue_runner(&self) -> DialogueRunnerBuilder {
DialogueRunnerBuilder::from_yarn_project(self)
}
pub fn line_metadata(&self, line_id: &LineId) -> Option<&[String]> {
self.metadata.get(line_id).map(|v| v.as_slice())
}
pub fn headers_for_node(&self, node_name: &str) -> Option<HashMap<&str, Vec<&str>>> {
self.compilation
.program
.as_ref()
.unwrap()
.nodes
.get(node_name)?
.headers
.iter()
.fold(HashMap::new(), |mut map: HashMap<_, Vec<_>>, header| {
map.entry(header.key.as_str())
.or_default()
.push(header.value.as_str());
map
})
.into()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Event)]
pub struct LoadYarnProjectEvent {
pub(crate) localizations: Option<Localizations>,
pub(crate) yarn_files: HashSet<YarnFileSource>,
pub(crate) development_file_generation: DevelopmentFileGeneration,
}
impl Default for LoadYarnProjectEvent {
fn default() -> Self {
Self {
localizations: None,
yarn_files: HashSet::from([YarnFileSource::Folder(DEFAULT_ASSET_DIR.into())]),
development_file_generation: default(),
}
}
}
impl LoadYarnProjectEvent {
#[must_use]
pub fn new() -> Self {
#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
{
Self::default()
}
#[cfg(any(target_arch = "wasm32", target_os = "android"))]
{
panic!(
"LoadYarnProjectEvent::new() is not supported on this platform because it tries to load files from the \"dialogue\" directory in the assets folder. \
However, this platform does not allow loading a file without naming it explicitly. \
Use `LoadYarnProjectEvent::with_yarn_source` or `LoadYarnProjectEvent::with_yarn_sources` instead.")
}
}
#[must_use]
pub fn with_yarn_sources<T, U>(yarn_files: T) -> Self
where
T: IntoIterator<Item = U>,
U: Into<YarnFileSource>,
{
let yarn_files = yarn_files
.into_iter()
.map(|yarn_file| yarn_file.into())
.collect();
Self {
localizations: None,
yarn_files,
development_file_generation: default(),
}
}
#[must_use]
pub fn with_yarn_source(yarn_file_source: impl Into<YarnFileSource>) -> Self {
Self::with_yarn_sources(iter::once(yarn_file_source))
}
#[must_use]
pub fn add_yarn_source(mut self, yarn_file: impl Into<YarnFileSource>) -> Self {
self.yarn_files.insert(yarn_file.into());
self
}
#[must_use]
pub fn add_yarn_sources(
mut self,
yarn_files: impl IntoIterator<Item = impl Into<YarnFileSource>>,
) -> Self {
self.yarn_files
.extend(yarn_files.into_iter().map(|yarn_file| yarn_file.into()));
self
}
#[must_use]
pub fn with_localizations(mut self, localizations: impl Into<Option<Localizations>>) -> Self {
let localizations = localizations.into();
self.localizations = localizations;
self
}
#[must_use]
pub fn with_development_file_generation(
mut self,
development_file_generation: DevelopmentFileGeneration,
) -> Self {
self.development_file_generation = development_file_generation;
if cfg!(any(target_arch = "wasm32", target_os = "android")) {
assert_eq!(self.development_file_generation, DevelopmentFileGeneration::None,
"Failed to build Yarn Spinner plugin: On `DevelopmentFileGeneration::None` is supported on this platform.");
}
self
}
}
impl<T, U> From<T> for LoadYarnProjectEvent
where
T: IntoIterator<Item = U>,
U: Into<YarnFileSource>,
{
fn from(yarn_files: T) -> Self {
Self::with_yarn_sources(yarn_files)
}
}
#[derive(Debug, Clone, Resource, Default)]
pub(crate) struct WatchingForChanges(pub(crate) bool);
pub(crate) const DEFAULT_ASSET_DIR: &str = "dialogue";