import {Delete, OpenInNew} from "@mui/icons-material"; import { Button, Chip, Container, Divider, FormControl, Grid, InputLabel, MenuItem, Paper, Select, Slider, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography } from "@mui/material"; import {DataGrid} from "@mui/x-data-grid"; import * as Sentry from "@sentry/nextjs"; import dynamic from "next/dynamic"; import Image from "next/image"; import {useSnackbar} from "notistack"; import {useState} from "react"; import {useDispatch} from "react-redux"; import {BiasParams, CarSetupParams} from "../../consts/params"; import {AllPossibleSetups, FeedbackColorForMUI} from "../../consts/setup"; import {tracks} from "../../consts/tracks"; import {validateSetupArray} from "../../consts/validator"; import {updateSlot} from "../../libs/reducers/configReducer"; import {arrayFloatEqual, biasToSetup, eps, nearestSetup, randomSetup, setupToBias} from "../../libs/setup"; import {ClearFeedbackDialog} from "./ClearFeedbackDialog"; const trackMap = {}; tracks.map(x => trackMap[x.id] = x); export function Calculator({ slot }) { const { enqueueSnackbar } = useSnackbar(); const dispatch = useDispatch(); const [carSetupList, setCarSetupList] = useState([]); const [possibleSetups, setPossibleSetups] = useState(AllPossibleSetups); const [openClearFeedback, setOpenClearFeedback] = useState(false); const update = (payload) => dispatch(updateSlot({ id: slot.id, payload })); let { track, carSetup, prevCarSetup, feedback, previousRuns, } = slot; if ( slot.id && !( validateSetupArray(carSetup) && validateSetupArray(prevCarSetup) && feedback && track && previousRuns ) ) { update({ carSetup: [0.5, 0.5, 0.5, 0.5, 0.5], prevCarSetup: [0.5, 0.5, 0.5, 0.5, 0.5], feedback: [[], [], [], [], []], track: "XX", previousRuns: [], }); return null; } const biasParam = setupToBias(carSetup); const prevBiasParam = setupToBias(prevCarSetup); const isValidSetup = CarSetupParams.map(p => { try { const val = carSetup ? carSetup[p.index] : 0.5; if (val < -1e-6 || val >= 1+1e-6) { return false; } const roundValue = val * (p.max - p.min) / p.step; return Math.abs(Math.round(roundValue) - roundValue) <= 1e-6; } catch { Sentry.captureMessage("invalid car setup found: " + JSON.stringify(carSetup)); update({ carSetup: [0.5, 0.5, 0.5, 0.5, 0.5], prevCarSetup: [0.5, 0.5, 0.5, 0.5, 0.5], feedback: [[], [], [], [], []], track: "XX", previousRuns: [], }); } }); const currentTrack = trackMap[track]; const setCarSetup = (carSetup) => { update({ carSetup }); } const findNearest = () => { update({ prevCarSetup: carSetup, }); const {setup, possibleSetups, lowestRuleBreak, possibleSetupList} = nearestSetup(biasParam, feedback); if (setup) { if (lowestRuleBreak > 0) { enqueueSnackbar( 'Unable to find a valid setup matching all feedbacks. This is the closest we could get.', { variant: "warning" } ); setPossibleSetups(0); } else { setPossibleSetups(possibleSetups); } setCarSetup(setup); setCarSetupList(possibleSetupList); } else { enqueueSnackbar( 'Unable to find a valid setup matching all feedbacks. Try deleting some feedbacks.', { variant: "error" } ); } } const clearFeedbacks = () => { update({ feedback: [[], [], [], [], []] }); setPossibleSetups(AllPossibleSetups); } const createFeedback = (row, biasValue, v) => { const matchedRuns = previousRuns.filter(x => ( x.track === track && arrayFloatEqual(x.carSetup, carSetup) )) // setLastCarSetup(carSetup) update({ previousRuns: matchedRuns.length ? ( previousRuns.map(x => x.id === matchedRuns[0].id ? { ...x, ["feedback_" + row.index]: { value: biasValue, timestamp: +new Date(), feedback: v }, } : x) ) : ([{ track, carSetup: JSON.parse(JSON.stringify(carSetup)), ["feedback_" + row.index]: { value: biasValue, timestamp: +new Date(), feedback: v }, id: +new Date(), }, ...previousRuns]), feedback: feedback.map((x, idx) => idx === row.index ? [ ...x.filter(x => x.value !== biasValue), { value: biasValue, timestamp: +new Date(), feedback: v } ]: x), }); // if (v === "optimal" && Object.keys(preset).length) { // axios.post(`/api/report`, { // track, // value: biasValue, // feedback: v, // index: row.index, // }); // } } const loadPreset = () => setCarSetup(currentTrack.setup); return ( { clearFeedbacks(); loadPreset(); }} isOpen={openClearFeedback} setIsOpen={setOpenClearFeedback} /> Track Select: Setup Values Compare { CarSetupParams.map(row => { let carSetupDiff = carSetup[row.index] - prevCarSetup[row.index]; if (Math.abs(carSetupDiff) < eps) { carSetupDiff = 0; } return ( {row.name} 1 || carSetup[row.index] < 0) ? "error" : (isValidSetup[row.index] ? "primary" : "warning") } step={row.step / (row.max - row.min)} max={Math.max(1, carSetup[row.index])} min={Math.min(0, carSetup[row.index])} valueLabelFormat={v => row.render(v * (row.max - row.min) + row.min)} valueLabelDisplay="on" value={carSetup[row.index]} onChange={(e, value) => { const setup = carSetup.map((x, idx) => idx === row.index ? ( Math.round(value * 560) / 560 ): x); setCarSetup(setup) }} /> 0 ? "#ff6383" : carSetupDiff < 0 ? "#76ff03" : "white" }}>{carSetupDiff > 0 ? "▲" : carSetupDiff < 0 ? "▼" : ""} { row.render(carSetup[row.index] * (row.max - row.min) + row.min) } Prev: { row.render(prevCarSetup[row.index] * (row.max - row.min) + row.min) } ) }) }
{possibleSetups} Possibilities
{/* */} Feedback Bias Value { BiasParams.map(row => { let feedbacks = JSON.parse(JSON.stringify(feedback[row.index])); const biasValue = biasParam[row.index]; const k = row.name + ":" + slot.slotNaming; let currentFeedback = ""; for(const fb of feedbacks) { if (fb.value === biasValue) { currentFeedback = fb.feedback; } } return [( {row.name} v.toFixed(6)} valueLabelDisplay="on" value={biasParam[row.index]} onChange={(e, value) => { const bias = biasParam.map((x, idx) => idx === row.index ? value : x); setCarSetup(biasToSetup(bias)) }} /> { const val = e.target.value; const nVal = Number(val); if (0 <= nVal && nVal <= 1) { const b = biasParam.map((x, idx) => idx === row.index ? nVal : x); setCarSetup(biasToSetup(b)) } } } /> ),( { feedbacks.sort( (x, y) => x.value - y.value ).map((f, _idx) => ( { const bias = biasParam.map((x, idx) => idx === row.index ? f.value : x); setCarSetup(biasToSetup(bias)) }} onDelete={() => { update({ feedback: feedback.map((x, idx) => idx === row.index ? x.filter(x => x.value !== f.value) : x ) }) }} /> )) } )]; }).flat() }
{ const biasParams = setupToBias(x.arr); return {...x, biasParams, id: id + 1} }) ]} columns={[ { field: 'id', headerName: 'Setup #', renderCell : ({ row, value }) => }, { field: 'diff', headerName: '%', valueGetter: ({ value }) => value.toFixed(1) + "%", }, ...CarSetupParams.map(param => { const idx = param.index; return { field: 'arr_' + idx, headerName: param.name, valueGetter: ({ row }) => row.arr ? row.arr[idx] : 0, renderCell: ({ row }) => { const value = row.arr ? row.arr[idx] : 0; const carSetupDiff = value - (prevCarSetup ? prevCarSetup[idx] : 0); return 0 ? "#ff6383" : carSetupDiff < 0 ? "#76ff03" : "white" }} > {carSetupDiff > 0 ? "▲" : carSetupDiff < 0 ? "▼" : ""} { param.render(value * (param.max - param.min) + param.min) } }, } }), { field: 'arr', headerName: '⇒', width: 8, renderCell: () => "⇒", }, ...BiasParams.map(param => { const idx = param.index; return { field: 'biasArr_' + idx, headerName: param.name, valueGetter: ({ row }) => row.biasParams ? row.biasParams[idx] : 0, renderCell: ({ row }) => { const value = row.biasParams ? row.biasParams[idx] : 0; const carSetupDiff = value - (prevBiasParam ? prevBiasParam[idx] : 0); return 0 ? "#ff6383" : carSetupDiff < 0 ? "#76ff03" : "white" }} > {value.toFixed(4)} {carSetupDiff > 0 ? "▲" : carSetupDiff < 0 ? "▼" : ""} }, } }) ]} density="compact" rowsPerPageOptions={[10, 20, 40, 100, 200]} />
Previous Runs FWA RWA ARD TC TO Oversteer Braking Cornering Traction Straights { previousRuns.map(x => { return ( {trackMap[x.track]?.name} { [0, 1, 2, 3, 4].map(idx => ( { CarSetupParams[idx].render( x.carSetup[idx] * ( CarSetupParams[idx].max - CarSetupParams[idx].min ) + CarSetupParams[idx].min ) } )) } { [0, 1, 2, 3, 4].map(idx => ( { x["feedback_" + idx] && ( ) } )) } ) }) }
) } export default dynamic(() => Promise.resolve(Calculator), { ssr: false, });