Skip to content

Commit

Permalink
Add propagating var id change into properties.
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrej33 committed Dec 10, 2024
1 parent 0303825 commit 6c1d04f
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,33 @@ use crate::app::event::Event;
use crate::app::state::{Consumed, SessionHelper};
use crate::app::{AeonError, DynError};
use crate::sketchbook::data_structs::{
LayoutNodeData, LayoutNodeDataPrototype, ModelData, VariableData, VariableWithLayoutData,
ChangeIdData, LayoutNodeData, LayoutNodeDataPrototype, ModelData, VariableData,
VariableWithLayoutData,
};
use crate::sketchbook::event_utils::{
make_reversible, mk_model_event, mk_model_state_change, mk_stat_prop_event,
};
use crate::sketchbook::event_utils::{make_reversible, mk_model_event, mk_model_state_change};
use crate::sketchbook::ids::VarId;
use crate::sketchbook::layout::NodePosition;
use crate::sketchbook::model::{ModelState, UpdateFn, Variable};
use crate::sketchbook::JsonSerde;

/* Constants for event path segments in `ModelState` related to variables. */

// add new propared variable (and potentially change its position)
// add new propared variable (+ and potentially change its position)
const ADD_VAR_PATH: &str = "add";
// add new default variable
const ADD_DEFAULT_VAR_PATH: &str = "add_default";
// add new variable (without any additional changes)
const ADD_RAW_VAR_PATH: &str = "add_raw";
// remove variable (removing all its regulations and so on)
// remove variable (+ removing all its regulations and so on)
const REMOVE_VAR_PATH: &str = "remove";
// set variable's data (name and annotation)
const SET_DATA_PATH: &str = "set_data";
// set variable's id
// set variable's id (+ update all static props)
const SET_ID_PATH: &str = "set_id";
// set variable's id
const SET_ID_RAW_PATH: &str = "set_id_raw";
// set variable's update fn
const SET_UPDATE_FN_PATH: &str = "set_update_fn";

Expand Down Expand Up @@ -187,7 +192,7 @@ impl ModelState {
// Note this check is performed also later by the manager, we just want to detect this ASAP.
if self.is_var_contained_in_updates(&var_id) {
return AeonError::throw(format!(
"Cannot remove variable `{var_id}`, it is still contained in an update function."
"Cannot remove variable `{var_id}`, it is still contained in some update functions."
));
}

Expand Down Expand Up @@ -266,6 +271,27 @@ impl ModelState {
reverse_event.payload = Some(original_data.to_json_str());
Ok(make_reversible(state_change, event, reverse_event))
} else if Self::starts_with(SET_ID_PATH, at_path).is_some() {
// get the payload - a string for the "new_id"
let new_id = Self::clone_payload_str(event, component_name)?;
if var_id.as_str() == new_id.as_str() {
return Ok(Consumed::NoChange);
}

// now we must handle the event itself, and all potential static property changes
let mut event_list = Vec::new();
// the raw event of changing the var id (payload stays the same)
let var_id_event_path = ["variable", var_id.as_str(), "set_id_raw"];
let var_id_event = mk_model_event(&var_id_event_path, Some(&new_id));
event_list.push(var_id_event);

// event for modifying all corresponding static property (we do it via single event)
// note we have checked that `var_id` and `new_id` are different
let id_change_data = ChangeIdData::new(var_id.as_str(), &new_id).to_json_str();
let prop_event = mk_stat_prop_event(&["set_var_id_everywhere"], Some(&id_change_data));
event_list.push(prop_event);
event_list.reverse(); // has to be reversed
Ok(Consumed::Restart(event_list))
} else if Self::starts_with(SET_ID_RAW_PATH, at_path).is_some() {
// get the payload - string for "new_id"
let new_id = Self::clone_payload_str(event, component_name)?;
if var_id.as_str() == new_id.as_str() {
Expand All @@ -281,7 +307,7 @@ impl ModelState {
let state_change = mk_model_state_change(&["variable", "set_id"], &model_data);

// prepare the reverse event (the reverse event is as usual)
let reverse_at_path = ["variable", new_id.as_str(), "set_id"];
let reverse_at_path = ["variable", new_id.as_str(), "set_id_raw"];
let reverse_event = mk_model_event(&reverse_at_path, Some(var_id.as_str()));
Ok(make_reversible(state_change, event, reverse_event))
} else if Self::starts_with(SET_UPDATE_FN_PATH, at_path).is_some() {
Expand Down
40 changes: 39 additions & 1 deletion src-tauri/src/sketchbook/properties/_manager/_impl_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::sketchbook::ids::{
};
use crate::sketchbook::model::{Essentiality, Monotonicity};
use crate::sketchbook::properties::dynamic_props::are_same_dyn_variant;
use crate::sketchbook::properties::static_props::are_same_stat_variant;
use crate::sketchbook::properties::static_props::{are_same_stat_variant, StatPropertyType};
use crate::sketchbook::properties::{
DynPropIterator, DynProperty, PropertyManager, StatPropIterator, StatProperty,
};
Expand Down Expand Up @@ -360,6 +360,44 @@ impl PropertyManager {
self.stat_properties.remove(id).unwrap();
Ok(())
}

/// Go through all static properties that are automatically generated from the regulation
/// graph and make their IDs consistent with the variables they reference.
///
/// This is useful after we change the variable's ID, e.g., to ensure that monotonicity
/// properties still have IDs like `monotonicity_REGULATOR_TARGET`.
pub fn make_generated_reg_prop_ids_consistent(&mut self) {
// list of old-new IDs that must be changed
let mut id_change_list: Vec<(StatPropertyId, StatPropertyId)> = Vec::new();
for (prop_id, prop) in self.stat_properties.iter() {
match prop.get_prop_data() {
StatPropertyType::RegulationEssential(p) => {
// this template always has both fields, we can unwrap
let expected_id = StatProperty::get_essentiality_prop_id(
p.input.as_ref().unwrap(),
p.target.as_ref().unwrap(),
);
if prop_id != &expected_id {
id_change_list.push((prop_id.clone(), expected_id.clone()));
}
}
StatPropertyType::RegulationMonotonic(p) => {
// this template always has both fields, we can unwrap
let expected_id = StatProperty::get_monotonicity_prop_id(
p.input.as_ref().unwrap(),
p.target.as_ref().unwrap(),
);
if prop_id != &expected_id {
id_change_list.push((prop_id.clone(), expected_id.clone()));
}
}
_ => {}
}
}
for (current_id, new_id) in id_change_list {
self.set_stat_id(&current_id, new_id).unwrap();
}
}
}

/// Internal assertion utilities.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::sketchbook::event_utils::{
make_refresh_event, make_reversible, mk_dyn_prop_event, mk_dyn_prop_state_change,
mk_stat_prop_event, mk_stat_prop_state_change,
};
use crate::sketchbook::ids::{DynPropertyId, StatPropertyId};
use crate::sketchbook::ids::{DynPropertyId, StatPropertyId, VarId};
use crate::sketchbook::properties::dynamic_props::SimpleDynPropertyType;
use crate::sketchbook::properties::static_props::SimpleStatPropertyType;
use crate::sketchbook::properties::{DynProperty, PropertyManager, StatProperty};
Expand All @@ -26,6 +26,8 @@ const ADD_DEFAULT_PATH: &str = "add_default";
const REMOVE_PATH: &str = "remove";
// set ID of a property
const SET_ID_PATH: &str = "set_id";
// change variable ID in all static properties referencing that variable
const SET_VAR_ID_EVERYWHERE_PATH: &str = "set_var_id_everywhere";
// set content of a property
const SET_CONTENT_PATH: &str = "set_content";
// refresh all dynamic properties
Expand Down Expand Up @@ -67,6 +69,46 @@ impl SessionState for PropertyManager {
} else if Self::starts_with(ADD_PATH, at_path).is_some() {
Self::assert_path_length(at_path, 1, component_name)?;
self.event_add_static(event)
} else if Self::starts_with(SET_VAR_ID_EVERYWHERE_PATH, at_path).is_some() {
Self::assert_path_length(at_path, 1, component_name)?;
// get the payload - json string encoding the ID change data
let payload = Self::clone_payload_str(event, component_name)?;
let change_id_data = ChangeIdData::from_json_str(&payload)?;
let old_var_id = VarId::new(&change_id_data.original_id)?;
let new_var_id = VarId::new(&change_id_data.new_id)?;

// change values of all properties that reference this variable (ignoring the rest)
for (_, prop) in self.stat_properties.iter_mut() {
let _ = prop.set_var_id_if_present(old_var_id.clone(), new_var_id.clone());
}
self.make_generated_reg_prop_ids_consistent();

// the state change is just a list of all static properties
let mut properties_list: Vec<StatPropertyData> = self
.stat_properties
.iter()
.map(|(id, prop)| StatPropertyData::from_property(id, prop))
.collect();
properties_list.sort_by(|a, b| a.id.cmp(&b.id));
let state_change = Event {
path: vec![
"sketch".to_string(),
"properties".to_string(),
"static".to_string(),
"set_var_id_everywhere".to_string(),
],
payload: Some(serde_json::to_string(&properties_list)?),
};

// the reverse change the opposite ID exchange
let reverse_id_change_data =
ChangeIdData::new(&change_id_data.new_id, &change_id_data.original_id);

// prepare the reverse event (setting the original ID back)
let payload = reverse_id_change_data.to_json_str();
let reverse_event =
mk_stat_prop_event(&[SET_VAR_ID_EVERYWHERE_PATH], Some(&payload));
Ok(make_reversible(state_change, event, reverse_event))
} else {
Self::assert_path_length(at_path, 2, component_name)?;
let prop_id_str = at_path.first().unwrap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,25 @@ impl StatProperty {
))
}
}

/// If the property is referencing the given variable (as either regulator or target),
/// set that variable to the new value.
///
/// If not applicable, return `Err`.
pub fn set_var_id_if_present(&mut self, old_id: VarId, new_id: VarId) -> Result<(), String> {
let (reg_var, target_var) = self.get_regulator_and_target()?;
if let Some(var_id) = reg_var {
if var_id == old_id {
self.set_input_var(new_id.clone())?;
}
}
if let Some(var_id) = target_var {
if var_id == old_id {
self.set_target_var(new_id)?;
}
}
Ok(())
}
}

/// Observing static properties.
Expand Down Expand Up @@ -534,6 +553,28 @@ impl StatProperty {
}
Ok(())
}

/// Get property's sub-fields for regulator variable and target variable, where applicable.
/// If not applicable, return `Err`.
pub fn get_regulator_and_target(&mut self) -> Result<(Option<VarId>, Option<VarId>), String> {
match &mut self.variant {
StatPropertyType::RegulationMonotonic(prop) => {
Ok((prop.input.clone(), prop.target.clone()))
}
StatPropertyType::RegulationMonotonicContext(prop) => {
Ok((prop.input.clone(), prop.target.clone()))
}
StatPropertyType::RegulationEssential(prop) => {
Ok((prop.input.clone(), prop.target.clone()))
}
StatPropertyType::RegulationEssentialContext(prop) => {
Ok((prop.input.clone(), prop.target.clone()))
}
other_variant => Err(format!(
"{other_variant:?} does not have fields for both regulator and target variable."
)),
}
}
}

/// Static methods to automatically generate IDs to encode regulation properties.
Expand Down
4 changes: 4 additions & 0 deletions src/aeon_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,9 @@ interface AeonState {
staticIdChanged: Observable<StatPropIdUpdateData>
/** Set ID of static property with given original ID to a new id. */
setStaticId: (originalId: string, newId: string) => void
/** List of all `StaticProperty` after variable's ID is changed.
* Since var ID change can affect any property, we "refresh" all data at once. */
allStaticUpdated: Observable<StaticProperty[]>
}
}

Expand Down Expand Up @@ -967,6 +970,7 @@ export const aeonState: AeonState = {
staticContentChanged: new Observable<StaticProperty>(['sketch', 'properties', 'static', 'set_content']),
staticRemoved: new Observable<StaticProperty>(['sketch', 'properties', 'static', 'remove']),
staticIdChanged: new Observable<StatPropIdUpdateData>(['sketch', 'properties', 'static', 'set_id']),
allStaticUpdated: new Observable<StaticProperty[]>(['sketch', 'properties', 'static', 'set_var_id_everywhere']),

addDefaultDynamic (variant: DynamicPropertyType): void {
aeonEvents.emitAction({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,10 @@ export default class PropertiesEditor extends LitElement {
this.addEventListener('dynamic-property-edited', (e) => { void this.editDynProperty(e) })
this.addEventListener('static-property-edited', (e) => { void this.editStatProperty(e) })

// refresh-event listeners
// refresh-event listeners (or listeners to events that update the whole list of props)
aeonState.sketch.properties.staticPropsRefreshed.addEventListener(this.#onStaticRefreshed.bind(this))
aeonState.sketch.properties.dynamicPropsRefreshed.addEventListener(this.#onDynamicRefreshed.bind(this))
aeonState.sketch.properties.allStaticUpdated.addEventListener(this.#onStaticRefreshed.bind(this))

// note that the refresh events are automatically triggered or handled (after app refresh) directly
// from the root component (due to some dependency issues between different components)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ export default class abstractStaticProperty extends AbstractProperty {
<div class="uk-flex uk-flex-row uk-flex-bottom uk-width-auto">
<div class="uk-flex uk-flex-column">
<label class="uk-form-label" for="id-field">ID</label>
<input id="id-field" class="uk-input" .value="${this.property.id}"
@input="${(e: InputEvent) => this.idUpdated((e.target as HTMLInputElement).value)}"/>
<input id="id-field" class="uk-input static-name-field" .value="${this.property.id}" readonly/>
</div>
<div class="uk-flex uk-flex-column name-section">
<label class="uk-form-label" for="name-field">NAME</label>
Expand Down

0 comments on commit 6c1d04f

Please sign in to comment.