Skip to content

Instantly share code, notes, and snippets.

@colelawrence
Last active September 9, 2020 14:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save colelawrence/43097f6bd1cadc15492c087732376d1d to your computer and use it in GitHub Desktop.
Save colelawrence/43097f6bd1cadc15492c087732376d1d to your computer and use it in GitHub Desktop.
`shipyard_app` plugin for adding a "tracked" unique which is reset across updates.
//! A small change tracker to wrap around a unique so you can determine if the value changes between updates.
use crate::prelude::*;
use std::{fmt, ops::Deref, ops::DerefMut};
pub struct TrackedUnique<T: ?Sized>(InnerTrackedState, T);
pub type TrackedUniqueView<'a, T> = UniqueView<'a, TrackedUnique<T>>;
pub type TrackedUniqueViewMut<'a, T> = UniqueViewMut<'a, TrackedUnique<T>>;
#[derive(PartialEq)]
enum InnerTrackedState {
New,
Modified,
NoChanges,
}
impl<T> TrackedUnique<T> {
pub fn new(value: T) -> Self {
TrackedUnique(InnerTrackedState::New, value)
}
pub fn reset_tracking(&mut self) {
self.0 = InnerTrackedState::NoChanges;
}
pub fn is_new_or_modified(&self) -> bool {
return self.0 != InnerTrackedState::NoChanges;
}
}
impl<T: ?Sized> TrackedUnique<T> {}
impl<T: ?Sized> Deref for TrackedUnique<T> {
type Target = T;
#[inline(always)]
fn deref(&self) -> &T {
&self.1
}
}
impl<T: ?Sized> DerefMut for TrackedUnique<T> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut T {
self.0 = InnerTrackedState::Modified;
&mut self.1
}
}
impl<T: ?Sized> AsRef<T> for TrackedUnique<T> {
fn as_ref(&self) -> &T {
&**self
}
}
impl<T: ?Sized> AsMut<T> for TrackedUnique<T> {
fn as_mut(&mut self) -> &mut T {
&mut **self
}
}
impl<T: ?Sized + fmt::Display> fmt::Display for TrackedUnique<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&**self, f)
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for TrackedUnique<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
/// Add [TrackedUnique] of `T` and reset tracking at the end of every update.
#[derive(Default)]
pub struct TrackedUniquePlugin<T: Clone + Send + Sync + 'static>(T);
impl<T: Clone + Send + Sync + 'static> TrackedUniquePlugin<T> {
pub fn new(initial_value: T) -> Self {
TrackedUniquePlugin(initial_value)
}
}
impl<T: Clone + Send + Sync + 'static> Plugin for TrackedUniquePlugin<T> {
fn build(&self, app: &mut AppBuilder) {
app.add_unique(TrackedUnique::new(self.0.clone()))
.add_systems_to_stage(stage::LAST, |wb| {
wb.with_system(system!(clear_tracked_unique::<T>));
});
}
}
fn clear_tracked_unique<T>(mut uvm_tracked_unique_t: UniqueViewMut<TrackedUnique<T>>) {
uvm_tracked_unique_t.reset_tracking();
}
//! # Atom Display Plugin
//!
//! The [AtomDisplayPlugin] will add the [AtomDisplay] to all entities with [ecs::EcsAtomKind].
//!
//! It uses the current [DisplayLanguage] to determine which language should be chosen
//! for displaying each atom. It will look up the [ecs::EcsAtomKind] info in the registry
//! to choose the most appropriate name for the atom kind for the language available.
use super::display_language_plugin::*;
use crate::prelude::*;
use storycore::registry::REGISTRY_LOOKUP;
/// Display text
///
/// Example: `[slack]`, `[@Person]`, would all have an [AtomDisplay] with "slack", "Person" values respectively.
/// Token is written as "once" or "when" or in another language even
///
/// 💠 Component (derived) for Atoms
#[derive(Clone, Debug, PartialEq)]
pub struct AtomDisplay(String);
impl AtomDisplay {
pub fn to_string(&self) -> String {
self.0.clone()
}
}
#[derive(Default)]
pub struct AtomDisplayPlugin(());
impl Plugin for AtomDisplayPlugin {
fn build(&self, app: &mut AppBuilder) {
app.update_pack::<ecs::EcsAtomKind>("to refresh correct displays for AtomDisplay")
.depends_on_unique::<Tracked<DisplayLanguage>>(
"to choose the correct language to display text for Atoms with",
)
.add_systems_to_stage(stage::POST_UPDATE, |workload_builder| {
workload_builder.with_system(system!(atom_display_indexing));
});
}
}
fn atom_display_indexing(
tuv_display_language: TrackedUniqueView<DisplayLanguage>,
v_atom_kind: View<ecs::EcsAtomKind>,
mut vm_atom_display: ViewMut<AtomDisplay>,
) {
if tuv_display_language.is_new_or_modified() {
// applied language is out of date, so update all
trace!(lang = ?*tuv_display_language, "applied language is out of date, so update all atom displays");
for (entity_id, updated_value) in v_atom_kind.iter().with_id() {
vm_atom_display.add_component_unchecked(
lookup_display(&updated_value, &tuv_display_language),
entity_id,
);
}
} else {
// only check changed atom kinds
// remove displays from deleted atom kinds
for (entity_id, _) in v_atom_kind.deleted() {
vm_atom_display.delete(*entity_id);
}
// update changed atom kinds
for (entity_id, updated_value) in v_atom_kind.inserted_or_modified().iter().with_id() {
vm_atom_display.add_component_unchecked(
lookup_display(&updated_value, &tuv_display_language),
entity_id,
);
}
}
}
/// NOTE: Consider how we'd use [DisplayLanguage] to propagate back up the "best" [AtomDisplay] info.
/// This might mean that [AtomDisplay] needs to have more granular information about the ordering depending
/// on language as well.
fn lookup_display(kind: &ecs::EcsAtomKind, _lang: &DisplayLanguage) -> AtomDisplay {
let found_opt: Option<String> = match kind {
ecs::EcsAtomKind::Service {
service_local_id: _,
service_registry_id,
} => REGISTRY_LOOKUP.get_service_display_name(&service_registry_id),
ecs::EcsAtomKind::ServiceAction {
action_registry_id,
depends_on_atom: _,
} => REGISTRY_LOOKUP.get_service_action_display_name(&action_registry_id),
ecs::EcsAtomKind::ServiceEvent {
depends_on_atom: _,
event_registry_id,
} => REGISTRY_LOOKUP.get_service_event_display_name(&event_registry_id),
ecs::EcsAtomKind::ServiceParam {
depends_on_atom: _,
field_registry_id,
field_value: _,
} => REGISTRY_LOOKUP.get_service_field_display_name(&field_registry_id),
ecs::EcsAtomKind::ValueConstant(constant) => Some(constant.display_text.clone()),
ecs::EcsAtomKind::ValueReference(_) | ecs::EcsAtomKind::ValueRefinement(_) => None,
};
AtomDisplay(found_opt.unwrap_or_else(|| {
warn!(?kind, ?_lang, "TODO: need to actually look up the atom kind in the registry to get the display based on user language");
format!("{:?}", kind)
}))
}
use crate::prelude::*;
/// Used as unique as well as a data type
#[derive(Clone, Debug, PartialEq)]
pub enum DisplayLanguage {
English,
}
impl Default for DisplayLanguage {
fn default() -> Self {
DisplayLanguage::English
}
}
#[derive(Default)]
pub struct DisplayLanguagePlugin {
initial_language: DisplayLanguage,
}
impl DisplayLanguagePlugin {
pub fn new_with_initial_language(initial_language: DisplayLanguage) -> Self {
DisplayLanguagePlugin { initial_language }
}
}
impl Plugin for DisplayLanguagePlugin {
fn build(&self, app: &mut AppBuilder) {
app.add_plugin(plugins::TrackedUniquePlugin::new(
self.initial_language.clone(),
));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment