Skip to content

Commit

Permalink
Merge pull request #161 from yarn-slinger/book
Browse files Browse the repository at this point in the history
Write some docs
  • Loading branch information
janhohenheim authored Jan 22, 2024
2 parents 72a38d6 + e93f6bf commit ffc3761
Show file tree
Hide file tree
Showing 57 changed files with 843 additions and 110 deletions.
4 changes: 2 additions & 2 deletions crates/bevy_plugin/src/development_file_generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ pub(crate) fn development_file_generation_plugin(app: &mut App) {
app.register_type::<DevelopmentFileGeneration>();
}

/// The kind of development experience you wish when creating yarn files and dealing with missing localizations.
/// The kind of development experience you wish when creating Yarn files and dealing with missing localizations.
/// Defaults to [`DevelopmentFileGeneration::TRY_FULL`] in debug builds, [`DevelopmentFileGeneration::None`] otherwise.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[reflect(Debug, Default, PartialEq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum DevelopmentFileGeneration {
/// The recommended setting for a development environment:
/// - Generates line IDs for all lines in loaded yarn files and writes them back to disk.
/// - Generates line IDs for all lines in loaded Yarn files and writes them back to disk.
/// - Generates new strings files for all languages that are missing them, filling them with the lines found in the Yarn files.
/// - Adds new lines to strings files when they have been added to a loaded Yarn file.
/// - Marks lines in strings files that have been changed since they were translated by appending "NEEDS UPDATE" to the respective line texts.
Expand Down
9 changes: 5 additions & 4 deletions crates/bevy_plugin/src/dialogue_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,12 +320,13 @@ impl DialogueRunner {
self.text_provider.as_ref()
}

/// Returns the registered [`AssetProvider`] for the given type.
/// Returns the registered [`AssetProvider`] of the given type if it was previously registered with [`DialogueRunnerBuilder::add_asset_provider`].
#[must_use]
pub fn asset_provider<T: 'static>(&self) -> Option<&dyn AssetProvider> {
pub fn asset_provider<T: 'static>(&self) -> Option<&T> {
self.asset_providers
.get(&TypeId::of::<T>())
.map(|p| p.as_ref())
.values()
.filter_map(|p| p.as_any().downcast_ref())
.next()
}

/// Iterates over all registered [`AssetProvider`]s.
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_plugin/src/dialogue_runner/builder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::default_impl::{MemoryVariableStore, StringsFileTextProvider};
use crate::default_impl::{MemoryVariableStorage, StringsFileTextProvider};
use crate::line_provider::SharedTextProvider;
use crate::prelude::*;
use bevy::prelude::*;
Expand Down Expand Up @@ -41,7 +41,7 @@ impl DialogueRunnerBuilder {
#[must_use]
pub(crate) fn from_yarn_project(yarn_project: &YarnProject) -> Self {
Self {
variable_storage: Box::new(MemoryVariableStore::new()),
variable_storage: Box::new(MemoryVariableStorage::new()),
text_provider: SharedTextProvider::new(StringsFileTextProvider::from_yarn_project(
yarn_project,
)),
Expand All @@ -54,7 +54,7 @@ impl DialogueRunnerBuilder {
}
}

/// Replaces the [`VariableStorage`] used by the [`DialogueRunner`]. By default, this is a [`MemoryVariableStore`].
/// Replaces the [`VariableStorage`] used by the [`DialogueRunner`]. By default, this is a [`MemoryVariableStorage`].
#[must_use]
pub fn with_variable_storage(mut self, storage: Box<dyn VariableStorage>) -> Self {
self.variable_storage = storage;
Expand Down
44 changes: 24 additions & 20 deletions crates/bevy_plugin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,39 +53,43 @@
//!
//! ```no_run
//! // src/main.rs
//! use bevy::prelude::*;
//! use bevy::{prelude::*, asset::ChangeWatcher, utils::Duration};
//! use bevy_yarn_slinger::prelude::*;
//! // Use the example dialogue view to see the dialogue in action. Requires the `bevy_yarn_slinger_example_dialogue_view` crate.
//! // use bevy_yarn_slinger_example_dialogue_view::prelude::*;
//!
//! fn main() {
//! let mut app = App::new();
//! app.add_plugins(DefaultPlugins)
//! // Register the Yarn Slinger plugin using its default settings, which will look for Yarn files in the "dialogue" folder
//! // If this app should support Wasm or Android, we cannot load files without specifying them, so use the following instead.
//! // .add_plugins(YarnSlingerPlugin::with_yarn_source(YarnFileSource::file("dialogue/hello_world.yarn")))
//! .add_plugins(YarnSlingerPlugin::new())
//! app.add_plugins((
//! DefaultPlugins.set(AssetPlugin {
//! // Activate hot reloading
//! watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(200)),
//! ..default()
//! }),
//! // Add the Yarn Slinger plugin.
//! // As soon as this plugin is built, a Yarn project will be compiled
//! // from all Yarn files found under assets/dialog/*.yarn
//! YarnSlingerPlugin::new(),
//! // Initialize the bundled example UI. Requires the `bevy_yarn_slinger_example_dialogue_view` crate.
//! // .add_plugins(ExampleYarnSlingerDialogueViewPlugin::new())
//! .add_systems(
//! Update,
//! (
//! setup_camera.on_startup(),
//! // Spawn dialogue runner once the Yarn project has finished compiling
//! spawn_dialogue_runner.run_if(resource_added::<YarnProject>()),
//! )
//! )
//! .run();
//! // ExampleYarnSlingerDialogueViewPlugin::new(),
//! ))
//! // Setup a 2D camera so we can see the text
//! .add_systems(Startup, setup_camera)
//! // Spawn the dialog as soon as the Yarn project finished compiling
//! .add_systems(
//! Update,
//! spawn_dialogue_runner.run_if(resource_added::<YarnProject>()),
//! )
//! .run();
//! }
//!
//! fn setup_camera(mut commands: Commands) {
//! commands.spawn(Camera2dBundle::default());
//! }
//!
//! fn spawn_dialogue_runner(mut commands: Commands, project: Res<YarnProject>) {
//! // Create a dialogue runner from the project
//! let mut dialogue_runner = project.create_dialogue_runner();
//! // Immediately show the dialogue to the player by starting at the "Start" node
//! // Start the dialog at the node with the title "Start"
//! dialogue_runner.start_node("Start");
//! commands.spawn(dialogue_runner);
//! }
Expand Down Expand Up @@ -116,7 +120,7 @@ pub mod default_impl {
pub use crate::line_provider::{
file_extensions, FileExtensionAssetProvider, StringsFileTextProvider,
};
pub use yarn_slinger::runtime::{MemoryVariableStore, StringTableTextProvider};
pub use yarn_slinger::runtime::{MemoryVariableStorage, StringTableTextProvider};
}

pub mod events {
Expand Down Expand Up @@ -164,7 +168,7 @@ pub use yarn_slinger::prelude::{
};

pub mod deferred_loading {
//! Contains types needed for the deferred loading functionality, which is used when the list of yarn files is not immediately available at startup.
//! Contains types needed for the deferred loading functionality, which is used when the list of Yarn files is not immediately available at startup.
pub use crate::plugin::DeferredYarnSlingerPlugin;
pub use crate::project::LoadYarnProjectEvent;
}
120 changes: 115 additions & 5 deletions crates/bevy_plugin/src/localization/strings_file/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,14 @@ impl StringsFile {
// This record's text was not translated, so we can safely overwrite it with the new text
other_record.text.clone()
};
let comment = combine_comments(&record.comment, &other_record.comment);

changed = true;
*record = other_record;
record.text = text;
*record = StringsFileRecord {
text,
comment,
..other_record
};
} else if single_yarn_file {
removed_lines.push(id.clone());
changed = true;
Expand Down Expand Up @@ -223,6 +227,27 @@ fn records_equal_except_for_text(lhs: &StringsFileRecord, rhs: &StringsFileRecor
}
const UPDATE_PREFIX: &str = "(NEEDS UPDATE) ";

fn combine_comments(full_old_comment: &str, new_metadata: &str) -> String {
let translator_comment = extract_translator_comment(full_old_comment);
let new_metadata = (!new_metadata.is_empty()).then_some(new_metadata);
[translator_comment, new_metadata]
.into_iter()
.filter_map(|s| s)
.collect::<Vec<_>>()
.join(LINE_METADATA_PREFIX_SEPARATOR)
}

fn extract_translator_comment(comment: &str) -> Option<&str> {
let mut split = comment.split(LINE_METADATA_PREFIX);
split
.next()
.filter(|s| !s.is_empty())
.map(|s| s.trim_end_matches(LINE_METADATA_PREFIX_SEPARATOR))
}

const LINE_METADATA_PREFIX: &str = "Line metadata: ";
const LINE_METADATA_PREFIX_SEPARATOR: &str = ", ";

#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub(crate) struct StringsFileRecord {
/// The language that the line is written in.
Expand All @@ -248,11 +273,11 @@ pub(crate) struct StringsFileRecord {
/// the line's text as it appeared in the base localization CSV file.
///
/// When a new StringTableEntry is created in a localized CSV file for a
/// .yarn file, the Lock value is copied over from the base CSV file,
/// .Yarn file, the Lock value is copied over from the base CSV file,
/// and used for the translated entry.
///
/// Because the base localization CSV is regenerated every time the
/// .yarn file is imported, the base localization Lock value will change
/// .Yarn file is imported, the base localization Lock value will change
/// if a line's text changes. This means that if the base lock and
/// translated lock differ, the translated line is out of date, and
/// needs to be updated.
Expand Down Expand Up @@ -295,6 +320,91 @@ fn read_comments(metadata: impl IntoIterator<Item = String>) -> String {
if cleaned_metadata.is_empty() {
String::new()
} else {
format!("Line metadata: {}", cleaned_metadata.join(" "))
format!("{LINE_METADATA_PREFIX}{}", cleaned_metadata.join(" "))
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn combines_comments_without_change() {
let old = "Foo, Line metadata: Bar";
let new = "Line metadata: Bar";
let combined = combine_comments(old, new);
assert_eq!(old, &combined)
}

#[test]
fn combines_comments_with_deletion() {
let old = "Foo, Line metadata: Bar";
let new = "";
let combined = combine_comments(old, new);
assert_eq!("Foo", &combined)
}

#[test]
fn combines_comments_with_insertion() {
let old = "Foo, Line metadata: Bar";
let new = "Line metadata: Bar, Baz";
let combined = combine_comments(old, new);
assert_eq!("Foo, Line metadata: Bar, Baz", &combined)
}

#[test]
fn combines_comments_with_change() {
let old = "Foo, Line metadata: Bar";
let new = "Line metadata: Baz";
let combined = combine_comments(old, new);
assert_eq!("Foo, Line metadata: Baz", &combined)
}

#[test]
fn combines_comments_without_meta() {
let old = "Foo";
let new = "";
let combined = combine_comments(old, new);
assert_eq!(old, &combined)
}

#[test]
fn combines_comments_with_new_meta() {
let old = "Foo";
let new = "Line metadata: Bar";
let combined = combine_comments(old, new);
assert_eq!("Foo, Line metadata: Bar", &combined)
}

#[test]
fn combines_comments_with_only_same_meta() {
let old = "Line metadata: Bar";
let new = "Line metadata: Bar";
let combined = combine_comments(old, new);
assert_eq!(old, &combined)
}

#[test]
fn combines_empty_comments() {
let old = "";
let new = "";
let combined = combine_comments(old, new);
assert_eq!(old, &combined)
}

#[test]
fn combines_comments_with_only_new_meta() {
let old = "";
let new = "Line metadata: Bar";
let combined = combine_comments(old, new);
assert_eq!(new, &combined)
}

#[test]
fn combines_comments_with_only_changed_meta() {
let old = "Line metadata: Bar";
let new = "Line metadata: Baz";
let combined = combine_comments(old, new);
assert_eq!(new, &combined)
}
}
8 changes: 4 additions & 4 deletions crates/bevy_plugin/src/localization/strings_file/updating.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,11 @@ fn update_all_strings_files_for_string_table(
Ok(new_strings_file) => new_strings_file,
Err(e) => {
if project.development_file_generation == DevelopmentFileGeneration::Full {
debug!("Updating \"{}\" soon (lang: {language}) because the following yarn files were changed or loaded but do not have full line IDs yet: {file_names}",
debug!("Updating \"{}\" soon (lang: {language}) because the following Yarn files were changed or loaded but do not have full line IDs yet: {file_names}",
strings_file_path.display())
} else {
error!(
"Tried to update \"{}\" (lang: {language}) because the following yarn files were changed or loaded: {file_names}, but couldn't because: {e}",
"Tried to update \"{}\" (lang: {language}) because the following Yarn files were changed or loaded: {file_names}, but couldn't because: {e}",
strings_file_path.display(),
);
}
Expand All @@ -111,7 +111,7 @@ fn update_all_strings_files_for_string_table(
dirty_paths.insert((strings_file_handle, strings_file_path));

info!(
"Updated \"{}\" (lang: {language}) because the following yarn files were changed or loaded: {file_names}",
"Updated \"{}\" (lang: {language}) because the following Yarn files were changed or loaded: {file_names}",
strings_file_path.display(),
);
}
Expand Down Expand Up @@ -144,7 +144,7 @@ fn lint_strings_file(
.map(|asset_path| format!("at {}", asset_path.path().display()))
.unwrap_or_else(|| "created at runtime".to_owned());
warn!(
"Strings file {source} contains the following strings for yarn files were not found in the project: {superfluous_file_names}. \
"Strings file {source} contains the following strings for Yarn files were not found in the project: {superfluous_file_names}. \
Either you forgot to add these files to the project or the strings belonged to files that were deleted. \
You may want to delete these entries from the strings file manually. Yarn Slinger will not do this for you because it may lead to loss of work.",
);
Expand Down
16 changes: 8 additions & 8 deletions crates/bevy_plugin/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod yarn_file_source;

/// The plugin that provides all Yarn Slinger functionality.
/// In general, you'll want to create this by searching for Yarn files in "assets/dialogue", which [`YarnSlingerPlugin::new`] does under the hood.
/// You can also provide a list of yarn files to load via [`YarnSlingerPlugin::with_yarn_sources`].
/// You can also provide a list of Yarn files to load via [`YarnSlingerPlugin::with_yarn_sources`].
/// If you however do not know the paths to any files nor have them in-memory at the start of the program,
/// use [`YarnSlingerPlugin::deferred`] instead to later load the files by sending a [`LoadYarnProjectEvent`].
///
Expand Down Expand Up @@ -42,9 +42,9 @@ impl YarnSlingerPlugin {
/// Otherwise this panics since Bevy cannot query folders on these platforms.
/// Use [`YarnSlingerPlugin::with_yarn_source`] or [`YarnSlingerPlugin::with_yarn_sources`] there instead.
///
/// All yarn files will be shared across [`DialogueRunner`]s.
/// All Yarn files will be shared across [`DialogueRunner`]s.
/// If [hot reloading](https://bevy-cheatbook.github.io/assets/hot-reload.html) is turned on,
/// these yarn files will be recompiled if they change during runtime.
/// these Yarn files will be recompiled if they change during runtime.
///
/// Calling this is equivalent to calling [`YarnSlingerPlugin::with_yarn_source`] with a [`YarnFileSource::folder`] of `"dialogue"`.
#[must_use]
Expand All @@ -63,9 +63,9 @@ impl YarnSlingerPlugin {
}

/// Creates a new plugin that loads Yarn files from the given sources.
/// All yarn files will be shared across [`DialogueRunner`]s.
/// All Yarn files will be shared across [`DialogueRunner`]s.
/// If [hot reloading](https://bevy-cheatbook.github.io/assets/hot-reload.html) is turned on,
/// these yarn files will be recompiled if they change during runtime.
/// these Yarn files will be recompiled if they change during runtime.
///
/// See [`YarnFileSource`] for more information on where Yarn files can be loaded from.
///
Expand All @@ -90,9 +90,9 @@ impl YarnSlingerPlugin {
}

/// Creates a new plugin that loads Yarn files from the given source.
/// All yarn files will be shared across [`DialogueRunner`]s.
/// All Yarn files will be shared across [`DialogueRunner`]s.
/// If [hot reloading](https://bevy-cheatbook.github.io/assets/hot-reload.html) is turned on,
/// these yarn files will be recompiled if they change during runtime.
/// these Yarn files will be recompiled if they change during runtime.
///
/// See [`YarnFileSource`] for more information on where Yarn files can be loaded from.
///
Expand Down Expand Up @@ -169,7 +169,7 @@ impl Plugin for YarnSlingerPlugin {
}

/// The deferred version of [`YarnSlingerPlugin`]. Created by [`YarnSlingerPlugin::deferred`].
/// Will not load any yarn files until a [`LoadYarnProjectEvent`] is sent.
/// Will not load any Yarn files until a [`LoadYarnProjectEvent`] is sent.
#[derive(Debug)]
#[non_exhaustive]
pub struct DeferredYarnSlingerPlugin;
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_plugin/src/plugin/yarn_file_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ impl YarnFileSource {
.load_folder(path)
.unwrap_or_else(|e| {
panic!(
"Failed to load Yarn file folder {path}: {e}",
"Failed to load Yarn file folder {path}: {e}.\nHelp: Does the folder exist under the assets directory?",
path = path.display()
)
})
Expand All @@ -97,7 +97,7 @@ impl YarnFileSource {
.collect();
if handles.is_empty() {
warn!("No Yarn files found in the assets subdirectory {path}, so Yarn Slinger won't be able to do anything this run. \
Help: Add some yarn files to get started.", path = path.display());
Help: Add some Yarn files to get started.", path = path.display());
}
handles
}
Expand Down
Loading

0 comments on commit ffc3761

Please sign in to comment.