autodoc done

This commit is contained in:
hppeng 2022-06-25 05:23:58 -07:00
parent 94b67115aa
commit 2e65e88b6c
7 changed files with 286 additions and 4 deletions

View file

@ -1428,9 +1428,12 @@
<script type="text/javascript" src="../js/sq2items.js"></script>
<script type="text/javascript" src="../js/optimize.js"></script>
<div id="graph_body" style="max-width: 95%; height: 100vh">
<div id="graph_body" style="max-width: 100%; height: 100vh">
<button id="saveButton">JANKY Export SVG</button>
<a id="saveLink">savelink</a>
</div>
<script src="https://d3js.org/d3.v7.js"></script>
<script type="text/javascript" src="../js/d3_export.js"></script>
<script type="text/javascript" src="../js/render_compute_graph.js"></script>
</body>
</html>

BIN
dev/builder_colorcode.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

12
dev/compute_graph.svg Executable file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 63 KiB

View file

@ -892,9 +892,70 @@
Last updated: 30 May 2022
</p>
</div>
<div class="row section" title="Wynnbuilder Internals (compute graph)">
<p>
This section is about how Wynnbuilder's main builder page processes user input and calculates results.
Might be useful if you want to script wynnbuilder or extend it! Or for wynnbuilder developers (internal docs).
</p>
<div class="row section" title="Why?">
<p>
Modeling wynnbuilder's internal computations as a directed graph has a few advantages:
</p>
<ul class = "indent">
<li>Each compute "node" is small(er); easier to debug.</li>
<li>Information flow is specified explicitly (easier to debug).</li>
<li>Easy to build caching for arbitrary computations (only calculate what u need)</li>
<li>Stateless builder! Abstract the entire builder as a chain of function calls</li>
<li>Makes for pretty pictures</li>
</ul>
</div>
<div class="row section" title="TODO ComputeNode details">
TODO
</div>
<p>
An overview of wynnbuilder's internal structure can be seen <a href = "./compute_graph.svg" target = "_blank">here</a>. Arrows indicate flow of information.
Colors correspond roughly as follows:
</p>
<img src="./builder_colorcode.png"/>
<p>
The overall logic flow is as follows:
<ul class = "indent">
<li>Item and Powder inputs are parsed. Powders are applied to items.</li>
<li>Items and level information are combined to make a build.</li>
<li>Information from input fields for skill points and edit IDs is collected into an ID bonuses table.</li>
<li>Information about active powder specials, strength boosts, etc. are collected into their own ID tables.</li>
<li>All of the above tables are merged with the build's stats table to produce the "Final" ID bonus table.</li>
<li>Which spell variant (think: major id) to use for each of the 4 spells is computed based on the build.</li>
<li>Spell damage is calculated, using the merged stat table, spell info, and weapon info.</li>
</ul>
</p>
<p>
Outputs are computed as follows:
<ul class = "indent">
<li>Input box highlights are computed from the items produced by item input box nodes.</li>
<li>Item display is computed from item input boxes.</li>
<li>Build hash/URL is computed from the build, and skillpoint assignment.</li>
<li>Spell damage is displayed based on calculated spell damage results.</li>
<li>Build stats are displayed by builder-stats-display (this same node also displays a bunch of stuff at the bottom of the screen...)</li>
</ul>
</p>
<div class="row section" title="Gotchas">
<p>
The build sets default skillpoints and edited IDs automatically, whenever a build item/level is updated.
This is done using "soft links" by two nodes shown in red (builder-skillpoint-setter and builder-id-setter).
</p>
<p>
A soft link is where something goes and manually marks nodes dirty and calls their update methods.
This is useful for these cases because the skillpoints and editable ID fields usually take their value from
user input, but in some cases we want to programatically set them.
</p>
<p>
For example another soft link (not shown) is used to implement the reset button.
</p>
</div>
</div>
<!-- <div class="row section" title="Test Section">
</div> -->
</div>
<script type="text/javascript" src="../js/dev.js"></script>
<script type="text/javascript" src="../js/sq2icons.js"></script>

30
js/d3_export.js vendored Normal file
View file

@ -0,0 +1,30 @@
// http://bl.ocks.org/rokotyan/0556f8facbaf344507cdc45dc3622177
// Set-up the export button
function set_export_button(svg, button_id, output_id) {
d3.select('#'+button_id).on('click', function(){
//get svg source.
var serializer = new XMLSerializer();
var source = serializer.serializeToString(svg.node());
console.log(source);
source = source.replace(/^<g/, '<svg');
source = source.replace(/<\/g>$/, '</svg>');
//add name spaces.
if(!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)){
source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
}
if(!source.match(/^<svg[^>]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)){
source = source.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
}
//add xml declaration
source = '<?xml version="1.0" standalone="no"?>\r\n' + source;
//convert svg source to URI data scheme.
var url = "data:image/svg+xml;charset=utf-8,"+encodeURIComponent(source);
//set url value to a element's href attribute.
document.getElementById(output_id).href = url;
});
}

176
js/render_compute_graph.js Normal file
View file

@ -0,0 +1,176 @@
d3.select("#graph_body")
.append("div")
.attr("style", "width: 100%; height: 100%; min-height: 0px; flex-grow: 1")
.append("svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.classed("svg-content-responsive", true);
let graph = d3.select("svg");
let svg = graph.append('g');
let margin = {top: 20, right: 20, bottom: 35, left: 40};
function bbox() {
let ret = graph.node().parentNode.getBoundingClientRect();
return ret;
}
let _bbox = bbox();
const colors = ['aqua', 'yellow', 'fuchsia', 'white', 'teal', 'olive', 'purple', 'gray', 'blue', 'lime', 'red', 'silver', 'navy', 'green', 'maroon'];
const n_colors = colors.length;
const view = svg.append("rect")
.attr("class", "view")
.attr("x", 0)
.attr("y", 0);
function convert_data(nodes_raw) {
let edges = [];
let node_id = new Map();
nodes = [];
for (let i in nodes_raw) {
node_id.set(nodes_raw[i], i);
nodes.push({id: i, color: 0, data: nodes_raw[i]});
}
for (const node of nodes_raw) {
const to = node_id.get(node);
for (const input of node.inputs) {
const from = node_id.get(input);
let name = input.name;
let link_name = node.input_translation.get(name);
edges.push({
source: from,
target: to,
name: link_name
});
}
}
return {
nodes: nodes,
links: edges
}
}
function create_svg(data, redraw_func) {
// Initialize the links
var link = svg
.selectAll("line")
.data(data.links)
.enter()
.append("line")
.style("stroke", "#aaa")
// Initialize the nodes
let node = svg
.selectAll("g")
.data(data.nodes);
let node_enter = node.enter()
.append('g')
let circles = node_enter.append("circle")
.attr("r", 20)
.style("fill", ({id, color, data}) => colors[color])
node_enter.append('text')
.attr("dx", -20)
.attr("dy", -22)
.style('fill', 'white')
.text(({id, color, data}) => data.name);
// Let's list the force we wanna apply on the network
var simulation = d3.forceSimulation(data.nodes) // Force algorithm is applied to data.nodes
.force("link", d3.forceLink().strength(0.1) // This force provides links between nodes
.id(function(d) { return d.id; }) // This provide the id of a node
.links(data.links) // and this the list of links
)
.force("charge", d3.forceManyBody().strength(-400)) // This adds repulsion between nodes. Play with the -400 for the repulsion strength
//.force("center", d3.forceCenter(_bbox.width / 2, _bbox.height / 2).strength(0.1)) // This force attracts nodes to the center of the svg area
.on("tick", ticked);
// This function is run at each iteration of the force algorithm, updating the nodes position.
let scale_transform = {k: 1, x: 0, y: 0}
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node_enter.attr("transform", function (d) { return 'translate('+scale_transform.x+','+scale_transform.y+') scale('+scale_transform.k+') translate('+d.x+','+d.y+')' })
}
const drag = d3.drag()
.on("start", dragstart)
.on("drag", dragged);
node_enter.call(drag).on('click', click);
function click(event, d) {
if (event.ctrlKey) {
// Color cycle.
d.color = (d.color + 1) % n_colors;
d3.select(this).selectAll('circle').style("fill", ({id, color, data}) => colors[color])
}
else {
delete d.fx;
delete d.fy;
d3.select(this).classed("fixed", false);
simulation.alpha(0.5).restart();
}
}
function dragstart() {
d3.select(this).classed("fixed", true);
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
simulation.alpha(0.5).restart();
}
const zoom = d3.zoom()
.scaleExtent([0.01, 10])
.translateExtent([[-10000, -10000], [10000, 10000]])
.filter(filter)
.on("zoom", zoomed);
view.call(zoom);
function zoomed({ transform }) {
link.attr('transform', transform);
scale_transform = transform;
node_enter.attr("transform", function (d) { return 'translate('+scale_transform.x+','+scale_transform.y+') scale('+scale_transform.k+') translate('+d.x+','+d.y+')' })
redraw_func();
}
// prevent scrolling then apply the default filter
function filter(event) {
event.preventDefault();
return (!event.ctrlKey || event.type === 'wheel') && !event.button;
}
}
set_export_button(svg, 'saveButton', 'saveLink');
(async function() {
// JANKY
while (edit_id_output === undefined) {
await sleep(500);
}
function redraw() {
_bbox = bbox();
graph.attr("viewBox", [0, 0, _bbox.width, _bbox.height]);
view.attr("width", _bbox.width - 1)
.attr("height", _bbox.height - 1);
}
d3.select(window)
.on("resize", function() {
redraw();
});
redraw();
const data = convert_data(all_nodes);
create_svg(data, redraw);
console.log("render");
})();