autodoc done
This commit is contained in:
parent
94b67115aa
commit
2e65e88b6c
7 changed files with 286 additions and 4 deletions
|
@ -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
BIN
dev/builder_colorcode.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 178 KiB |
12
dev/compute_graph.svg
Executable file
12
dev/compute_graph.svg
Executable file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 63 KiB |
|
@ -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
30
js/d3_export.js
vendored
Normal 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
176
js/render_compute_graph.js
Normal 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");
|
||||
|
||||
})();
|
Loading…
Reference in a new issue