diff options
Diffstat (limited to 'data/vue/App.vue')
| -rw-r--r-- | data/vue/App.vue | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/data/vue/App.vue b/data/vue/App.vue new file mode 100644 index 0000000..114857a --- /dev/null +++ b/data/vue/App.vue @@ -0,0 +1,242 @@ +<template> + <div ref="top_pad"></div> + <div class="top_bar" ref="top_bar"> + <h1> + Salis data server » + <span class="opts_name"> + {{ opts.name }} + {{ query_in_progress ? '⧖' : '✓' }} + </span> + </h1> + <form @change="on_form_change"> + <span class="nobr">Entries (max): <input class="input_small" v-model="entries" /></span><wbr /> + <span class="nobr">nth: <input class="input_small" v-model="nth" /></span><wbr /> + <span class="nobr">X-axis: <select class="input_small" v-model="x_axis"><option v-for="axis in x_axes">{{ axis }}</option></select></span><wbr /> + <span class="nobr">X-low: <input v-model="x_low" /></span><wbr /> + <span class="nobr">X-high: <input v-model="x_high" /></span> + </form> + </div> + <Section name="Options"> + <table> + <tr v-for="opt_fmt in opt_fmts"> + <td>{{ opt_fmt[0] }}:</td> + <td>{{ opt_fmt[2](opts[opt_fmt[1]]) }}</td> + </tr> + </table> + </Section> + <!-- Render plots after simulation options have been loaded --> + <div v-if="loaded"> + <Section :name="section" grid v-for="(section_plots, section) in plots"> + <Plot :name="name" :section="section" v-for="(_, name) in section_plots" /> + </Section> + </div> +</template> + +<script setup> +import { onMounted, provide, useTemplateRef, ref } from 'vue' + +import Plot from './Plot.vue' +import Section from './Section.vue' + +const root = window.location.href +const id = v => v +const hex = v => v !== undefined ? `0x${v.toString(16)}` : '' +const hex_pow = v => v !== undefined ? `0x${Math.pow(2, v).toString(16)}` : '' +const disabled = v => v ? 'disabled' : 'enabled' + +const opt_fmts = [ + ['Ancestor', 'anc', id], + ['Architecture', 'arch', id], + ['Auto-save interval', 'auto_save_pow', hex_pow], + ['Clones', 'clones', id], + ['Cores', 'cores', id], + ['Data push interval', 'data_push_pow', hex_pow], + ['Mutator flip bit', 'muta_flip', id], + ['Mutator range', 'muta_pow', hex_pow], + ['Memory vector size', 'mvec_pow', hex_pow], + ['Save file compression', 'no_compress', disabled], + ['Seed', 'seed', hex], +] + +const opts = ref({}) +const plots = ref({}) +const tables = ref({}) +const loaded = ref(false) + +const entries = ref(2000) +const nth = ref(BigInt(1)) +const x_axes = ref(['rowid']) +const x_axis = ref(x_axes.value[0]) +const x_low = ref(hex(BigInt(0))) +const x_high = ref(hex(BigInt(Math.pow(2, 64)))) + +const plot_x_low = ref(0n) +const plot_redraw = ref(false) +const query_in_progress = ref(false) +const data = ref([]) + +const top_pad = useTemplateRef('top_pad') +const top_bar = useTemplateRef('top_bar') + +provide('plots', plots) +provide('entries', entries) +provide('x_axis', x_axis) +provide('data', data) + +const sanitize = (input, min, max, def, fmt) => { + if (isNaN(Number(input.value)) || input.value === '' || input.value < min || input.value > max) { + input.value = fmt(def) + } +} + +const on_form_change = () => { + sanitize(entries, 1n, BigInt(Math.pow(2, 64)), 2000n, id) + sanitize(nth, 1n, BigInt(Math.pow(2, 64)), 1n, id) + sanitize(x_low, 0n, BigInt(Math.pow(2, 64)), 0n, hex) + sanitize(x_high, 1n, BigInt(Math.pow(2, 64)), BigInt(Math.pow(2, 64)), hex) + + plot_x_low.value = x_low.value + plot_redraw.value = true + + query(false) +} + +const pad_top_bar = () => { + top_pad.value.style.height = `${Math.round(top_bar.value.getBoundingClientRect().height)}px` +} + +const reviver = (_, val, { source }) => { + if (Number.isInteger(val) && !Number.isSafeInteger(val)) { + try { return BigInt(source) } catch {} + } + + return val +} + +const query_table = async table => { + const params = { + table: table, + entries: entries.value, + nth: nth.value, + x_axis: x_axis.value, + x_low: Number(plot_x_low.value), + x_high: Number(x_high.value), + } + + const search_params = new URLSearchParams(params) + const resp_table = await fetch(root + `data?${search_params}`, { method: 'GET' }) + const resp_json = JSON.parse(await resp_table.text(), reviver) + + // Keep track of the highest x-axis value fetched so far. + // Future queries will set this as the minimum, which prevents re-fetching already stored data. + if (resp_json.length) { + const x_last = BigInt(resp_json.slice(-1)[0][x_axis.value] + 1) + plot_x_low.value = plot_x_low.value > x_last ? plot_x_low.value : x_last + } + + return resp_json +} + +const query = async (reschedule = true) => { + if (query_in_progress.value) return + + query_in_progress.value = true + + const query_results = await Promise.all(tables.value.map(query_table)) + const query_values = Object.fromEntries(tables.value.map((key, i) => [key, query_results[i]])) + + data.value = { redraw: plot_redraw.value, values: query_values } + plot_redraw.value = false + query_in_progress.value = false + + if (reschedule) setTimeout(query, 10000) +} + +onMounted(async () => { + window.onresize = _ => pad_top_bar() + pad_top_bar() + + // Fetch initial data + const resp_opts = await fetch(root + 'opts', { method: 'GET' }) + const resp_plots = await fetch(root + 'plots', { method: 'GET' }) + const resp_tables = await fetch(root + 'tables', { method: 'GET' }) + + opts.value = JSON.parse(await resp_opts.text(), reviver) + plots.value = JSON.parse(await resp_plots.text(), reviver) + tables.value = JSON.parse(await resp_tables.text(), reviver) + loaded.value = true + + // All tables should include one cycle column for each core. + // This allows normalizing the plots against each core's cycle count + // (i.e. making `cycl_#` the plots' x-axis). + x_axes.value.push(...Array(opts.value.cores).keys().map(i => `cycl_${i}`)) + + query() +}) +</script> + +<style> +html { + background-color: #002b36; + color: #586e75; + font-family: sans-serif; +} + +h1 { + font-size: 20px; + font-weight: 600; +} + +input, select { + background-color: #586e75; + border: none; + color: #002b36; + font-family: monospace; + font-size: 14px; + margin: 0 4px; + padding: 2px; +} + +table { + border-collapse: collapse; + border-spacing: 0; + height: 100%; + width: 100%; +} + +tr:nth-child(odd) { + background-color: #073642; +} + +td { + font-family: monospace; + font-size: 14px; + margin: 0; + padding: 0; +} + +.top_bar { + background-color: #073642; + left: 0; + padding: 8px; + position: fixed; + top: 0; + width: 100%; + z-index: 1; +} + +.opts_name { + color: #b58900; + font-weight: normal; +} + +.nobr { + line-height: 32px; + margin-right: 16px; + white-space: nowrap; +} + +.input_small { + width: 80px; +} +</style> |
