wynnbuilder-idk/js/computation_graph.js

159 lines
4.6 KiB
JavaScript

let _ALL_NODES = new Map();
class ComputeNode {
/***
* Make a generic compute node.
* Adds the node to the global map of nodenames to nodes (for calling from html listeners).
*
* @param name : Name of the node (string). Must be unique. Must "fit in" a JS string (terminated by single quotes).
*/
constructor(name) {
if (_ALL_NODES.has(name)) {
throw 'Duplicate node name: ' + name;
}
_ALL_NODES.set(name, this)
this.inputs = [];
this.children = [];
this.value = 0;
this.name = name;
this.update_task = null;
this.update_time = Date.now();
this.fail_cb = false; // Set to true to force updates even if parent failed.
}
/***
* Request update of this compute node. Pushes updates to children.
*/
update(timestamp) {
if (timestamp <= this.update_time) {
return;
}
this.update_time = timestamp;
let value_map = Map();
for (const input of this.inputs) {
value_map.set(input.name, input.get_value());
}
this.value = this.compute_func(value_map);
for (const child of this.children) {
if (this.value || child.fail_cb) {
child.update();
}
}
}
/***
* Get value of this compute node. Can't trigger update cascades (push based update, not pull based.)
*/
get_value() {
return this.value
}
/***
* Abstract method for computing something. Return value is set into this.value
*/
compute_func(input_map) {
throw "no compute func specified";
}
link_to(parent_node) {
this.inputs.push(parent_node)
parent_node.children.push(this);
}
}
/***
* Schedule a ComputeNode to be updated.
*
* @param node_name : ComputeNode name to schedule an update for.
*/
function calcSchedule(node_name) {
node = _ALL_NODES.get(node_name);
if (node.update_task !== null) {
clearTimeout(node.update_task);
}
node.update_task = setTimeout(function() {
const timestamp = Date.now();
node.update(timestamp);
node.update_task = null;
}, 500);
}
/***
* Node for getting an item's stats from an item input field.
*/
class ItemStats extends ComputeNode {
/***
* Make an item stat pulling compute node.
*
* @param name: Name of this node.
* @param item_input_field: Input field (html element) to listen for item names from.
* @param none_item: Item object to use as the "none" for this field.
*/
constructor(name, item_input_field, none_item) {
super(name);
this.input_field.setAttribute("onInput", "calcSchedule('"+name+"');");
this.input_field = item_input_field;
this.none_item = none_item;
}
compute_func(input_map) {
// built on the assumption of no one will type in CI/CR letter by letter
let item_text = this.input_field.value;
if (!item_text) {
return this.none_item;
}
let item;
if (item_text.slice(0, 3) == "CI-") {
item = getCustomFromHash(item_text);
}
else if (item_text.slice(0, 3) == "CR-") {
item = getCraftFromHash(item_text);
}
else if (itemMap.has(item_text)) {
item = itemMap.get(item_text);
}
else if (tomeMap.has(item_text)) {
item = tomeMap.get(item_text);
}
if (!item || item.) {
return null;
}
return item;
}
}
/***
* Node for updating item input fields from parsed items.
*/
class ItemInputDisplay extends ComputeNode {
constructor(name, item_input_field, item_image) {
super(name);
this.input_field = item_input_field;
this.image = item_image;
this.fail_cb = true;
}
compute_func(input_map) {
if (input_map.size !== 1) {
throw "ItemInputDisplay accepts exactly one input (item)";
}
const [item] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element
this.input_field.classList.remove("text-light", "is-invalid", 'Normal', 'Unique', 'Rare', 'Legendary', 'Fabled', 'Mythic', 'Set', 'Crafted', 'Custom');
this.input_field.classList.add("text-light");
this.image.classList.remove('Normal-shadow', 'Unique-shadow', 'Rare-shadow', 'Legendary-shadow', 'Fabled-shadow', 'Mythic-shadow', 'Set-shadow', 'Crafted-shadow', 'Custom-shadow');
if (!item) {
this.input_field.classList.add("is-invalid");
return null;
}
}
}