diff options
| author | Paul Oliver <contact@pauloliver.dev> | 2026-03-21 01:36:24 +0100 |
|---|---|---|
| committer | Paul Oliver <contact@pauloliver.dev> | 2026-04-03 18:50:51 +0200 |
| commit | bda5ad2ec9fa333c8200451496d6c251abbeee19 (patch) | |
| tree | 52e15344f239a023d298ee9635e8c7c0c4fdf614 /data/vue/App.vue | |
| parent | 9f7e70904e6c0fa650323ac5e50ebf6003da333c (diff) | |
Adds data server (WIP)
Diffstat (limited to 'data/vue/App.vue')
| -rw-r--r-- | data/vue/App.vue | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/data/vue/App.vue b/data/vue/App.vue new file mode 100644 index 0000000..af8f1c0 --- /dev/null +++ b/data/vue/App.vue @@ -0,0 +1,206 @@ +<template> + <h1> + Salis data server ยป + <span class="opts_name">{{ opts.name }}</span> + </h1> + <form @change="on_form_change"> + <span class="nobr">Entries (max): <input v-model.number="entries" type="text" /></span>  + <span class="nobr">X-axis: <select v-model="x_axis"><option v-for="axis in x_axes">{{ axis }}</option></select></span>  + <span class="nobr">X-low: <input v-model="x_low" type="text" /></span>  + <span class="nobr">X-high: <input v-model="x_high" type="text" /></span> + </form> + <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 v-for="(section_plots, section) in plots" :name="section" grid> + <Plot v-for="(_, name) in section_plots" :section="section" :name="name" /> + </Section> + </div> +</template> + +<script setup> +import { onMounted, provide, 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 opts = ref({}) +const plots = ref({}) +const tables = ref({}) +const loaded = ref(false) + +const entries = ref(2000) +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([]) + +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(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 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 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, + 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, 5000) +} + +onMounted(async () => { + 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: 4px; +} + +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; +} + +.opts_name { + color: #b58900; + font-weight: normal; +} + +.nobr { + line-height: 32px; + white-space: nowrap; +} +</style> |
