diff --git a/js/atree.js b/js/atree.js index dcc060e..055bbbb 100644 --- a/js/atree.js +++ b/js/atree.js @@ -125,16 +125,16 @@ const default_abils = { /** * Update ability tree internal representation. (topologically sorted node list) * - * Signature: AbilityTreeUpdateNode(build: Build) => ATree (List of atree nodes in topological order) + * Signature: AbilityTreeUpdateNode(player-class: str) => ATree (List of atree nodes in topological order) */ const atree_node = new (class extends ComputeNode { constructor() { super('builder-atree-update'); } compute_func(input_map) { - if (input_map.size !== 1) { throw "AbilityTreeUpdateNode accepts exactly one input (build)"; } - const [build] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element + if (input_map.size !== 1) { throw "AbilityTreeUpdateNode accepts exactly one input (player-class)"; } + const [player_class] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element - const atree_raw = atrees[wep_to_class.get(build.weapon.statMap.get('type'))]; + const atree_raw = atrees[player_class]; if (!atree_raw) return null; let atree_map = new Map(); diff --git a/js/builder_graph.js b/js/builder_graph.js index 28140fe..cff072a 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -435,6 +435,17 @@ class BuildAssembleNode extends ComputeNode { } } +class PlayerClassNode extends ValueCheckComputeNode { + constructor(name) { super(name); } + + compute_func(input_map) { + if (input_map.size !== 1) { throw "PlayerClassNode accepts exactly one input (build)"; } + const [build] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element + + return wep_to_class.get(build.weapon.statMap.get('type')); + } +} + /** * Read an input field and parse into a list of powderings. * Every two characters makes one powder. If parsing fails, NULL is returned. @@ -1085,8 +1096,9 @@ function builder_graph_init() { // Phase 3/3: Set up atree stuff. + let class_node = new PlayerClassNode('builder-class').link_to(build_node); // These two are defined in `atree.js` - atree_node.link_to(build_node, 'build'); + atree_node.link_to(class_node, 'player-class'); atree_merge.link_to(build_node, 'build'); atree_graph_creator = new AbilityTreeEnsureNodesNode(build_node, stat_agg_node) .link_to(atree_collect_spells, 'spells'); diff --git a/js/computation_graph.js b/js/computation_graph.js index d0d5b7a..bf45564 100644 --- a/js/computation_graph.js +++ b/js/computation_graph.js @@ -14,7 +14,10 @@ class ComputeNode { this.name = name; this.update_task = null; this.fail_cb = false; // Set to true to force updates even if parent failed. - this.dirty = true; + this.dirty = 2; // 3 states: + // 2: dirty + // 1: possibly dirty + // 0: clean this.inputs_dirty = new Map(); this.inputs_dirty_count = 0; all_nodes.push(this); @@ -27,15 +30,17 @@ class ComputeNode { if (this.inputs_dirty_count != 0) { return; } - if (!this.dirty) { + if (this.dirty === 0) { return; } - let calc_inputs = new Map(); - for (const input of this.inputs) { - calc_inputs.set(this.input_translation.get(input.name), input.value); + if (this.dirty == 2) { + let calc_inputs = new Map(); + for (const input of this.inputs) { + calc_inputs.set(this.input_translation.get(input.name), input.value); + } + this.value = this.compute_func(calc_inputs); } - this.value = this.compute_func(calc_inputs); - this.dirty = false; + this.dirty = 0; for (const child of this.children) { child.mark_input_clean(this.name, this.value); } @@ -64,12 +69,12 @@ class ComputeNode { } } - mark_dirty() { - if (!this.dirty) { - this.dirty = true; + mark_dirty(dirty_state=2) { + if (this.dirty < dirty_state) { + this.dirty = dirty_state; for (const child of this.children) { child.mark_input_dirty(this.name); - child.mark_dirty(); + child.mark_dirty(dirty_state); } } return this; @@ -125,6 +130,50 @@ class ComputeNode { } } +class ValueCheckComputeNode extends ComputeNode { + constructor(name) { super(name); } + + /** + * Request update of this compute node. Pushes updates to children, + * but only if this node's value changed. + */ + update() { + if (this.inputs_dirty_count != 0) { + return; + } + if (this.dirty === 0) { + return; + } + + let calc_inputs = new Map(); + for (const input of this.inputs) { + calc_inputs.set(this.input_translation.get(input.name), input.value); + } + let val = this.compute_func(calc_inputs); + if (val !== this.value) { + this.mark_dirty(2); + } + else { + console.log("soft update"); + } + this.value = val; + + this.dirty = 0; + for (const child of this.children) { + child.mark_input_clean(this.name, this.value); + } + return this; + } + + /** + * Defaulting to "dusty" state. + */ + mark_dirty(dirty_state="unused") { + return super.mark_dirty(1); + } + +} + /** * Schedule a ComputeNode to be updated. *