From 02acc6ede1ba9b7cb9662cdc52d849cb1a7973d1 Mon Sep 17 00:00:00 2001 From: Uriel Date: Thu, 20 Jul 2023 05:19:43 -0300 Subject: [PATCH] Improvements on the Autobone GUI (#776) Co-authored-by: Butterscotch! --- gui/package.json | 29 +- gui/public/i18n/en/translation.ftl | 21 +- gui/src/App.tsx | 3 +- gui/src/components/commons/A.tsx | 14 + gui/src/components/commons/NumberSelector.tsx | 6 +- .../body-proportions/AutomaticProportions.tsx | 2 + .../body-proportions/BodyProportions.tsx | 2 +- .../body-proportions/ProportionsChoose.tsx | 47 +- .../autobone-steps/AutoboneErrorModal.tsx | 64 ++ .../autobone-steps/Recording.tsx | 77 +- .../autobone-steps/VerifyResults.tsx | 17 +- .../autobone-steps/СheckHeight.tsx | 176 +++++ .../settings/pages/GeneralSettings.tsx | 2 +- gui/src/hooks/app.ts | 7 + gui/src/hooks/autobone.ts | 59 +- package-lock.json | 714 ++++++++++++++---- package.json | 4 +- .../java/dev/slimevr/autobone/AutoBone.kt | 278 +++---- .../dev/slimevr/autobone/AutoBoneHandler.kt | 91 +-- .../dev/slimevr/autobone/AutoBoneListener.kt | 3 +- .../slimevr/autobone/AutoBoneProcessType.kt | 2 +- .../java/dev/slimevr/autobone/AutoBoneStep.kt | 7 +- .../autobone/errors/BodyProportionError.kt | 5 +- .../java/dev/slimevr/config/AutoBoneConfig.kt | 4 +- .../config/CurrentVRConfigConverter.java | 10 + .../protocol/datafeed/DataFeedHandler.java | 4 +- .../dev/slimevr/protocol/rpc/RPCHandler.java | 146 +--- .../rpc/autobone/RPCAutoBoneHandler.kt | 181 +++++ .../rpc/settings/RPCSettingsBuilder.java | 122 +++ .../rpc/settings/RPCSettingsHandler.java | 14 + .../dev/slimevr/tracking/trackers/Tracker.kt | 35 +- solarxr-protocol | 2 +- 32 files changed, 1523 insertions(+), 625 deletions(-) create mode 100644 gui/src/components/commons/A.tsx create mode 100644 gui/src/components/onboarding/pages/body-proportions/autobone-steps/AutoboneErrorModal.tsx create mode 100644 gui/src/components/onboarding/pages/body-proportions/autobone-steps/СheckHeight.tsx create mode 100644 server/core/src/main/java/dev/slimevr/protocol/rpc/autobone/RPCAutoBoneHandler.kt diff --git a/gui/package.json b/gui/package.json index 8b8b20766..6e61ff09d 100644 --- a/gui/package.json +++ b/gui/package.json @@ -29,10 +29,11 @@ "react-modal": "3.15.1", "react-responsive": "^9.0.2", "react-router-dom": "^6.2.2", - "semver": "^7.5.0", + "semver": "^7.5.3", "solarxr-protocol": "file:../solarxr-protocol", "three": "^0.148.0", - "typescript": "^4.6.3" + "ts-pattern": "^5.0.1", + "typescript": "^5.1.6" }, "scripts": { "start": "vite --force", @@ -64,30 +65,30 @@ ] }, "devDependencies": { - "@tailwindcss/forms": "^0.5.0", + "@tailwindcss/forms": "^0.5.3", "@tauri-apps/cli": "^1.4.0", "@types/file-saver": "^2.0.5", "@types/react": "18.0.25", "@types/react-dom": "^18.0.5", "@types/react-modal": "3.13.1", "@types/three": "^0.148.0", - "@typescript-eslint/eslint-plugin": "^5.59.6", - "@typescript-eslint/parser": "^5.59.0", + "@typescript-eslint/eslint-plugin": "^5.60.1", + "@typescript-eslint/parser": "^5.60.1", "autoprefixer": "^10.4.4", "cross-env": "^7.0.3", - "eslint": "^8.18.0", + "eslint": "^8.44.0", "eslint-config-airbnb": "^19.0.4", - "eslint-import-resolver-typescript": "^3.1.1", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jsx-a11y": "^6.6.0", - "eslint-plugin-react": "^7.30.1", + "eslint-import-resolver-typescript": "^3.5.5", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", - "postcss": "^8.4.12", - "prettier": "^2.7.1", + "postcss": "^8.4.24", + "prettier": "^2.8.8", "pretty-quick": "^3.1.3", "rollup-plugin-visualizer": "^5.9.2", "tailwind-gradient-mask-image": "^1.0.0", - "tailwindcss": "^3.3.1", - "vite": "^4.0.3" + "tailwindcss": "^3.3.2", + "vite": "^4.3.9" } } diff --git a/gui/public/i18n/en/translation.ftl b/gui/public/i18n/en/translation.ftl index 16e87eb18..b6caa1cd9 100644 --- a/gui/public/i18n/en/translation.ftl +++ b/gui/public/i18n/en/translation.ftl @@ -730,7 +730,10 @@ onboarding-choose_proportions-description = Body proportions are used to know th onboarding-choose_proportions-auto_proportions = Automatic proportions # Italized text onboarding-choose_proportions-auto_proportions-subtitle = Recommended -onboarding-choose_proportions-auto_proportions-description = This will guess your proportions by recording a sample of your movements and passing it through an algorithm +onboarding-choose_proportions-auto_proportions-descriptionv2 = + This will guess your proportions by recording a sample of your movements and passing it through an algorithm. + + It requires having your HMD connected to SlimeVR! onboarding-choose_proportions-manual_proportions = Manual proportions # Italized text onboarding-choose_proportions-manual_proportions-subtitle = For small touches @@ -764,6 +767,18 @@ onboarding-automatic_proportions-requirements-description = Your trackers and headset are working properly within the SlimeVR server. Your headset is reporting positional data to the SlimeVR server (this generally means having SteamVR running and connected to SlimeVR using SlimeVR's SteamVR driver). onboarding-automatic_proportions-requirements-next = I have read the requirements +onboarding-automatic_proportions-check_height-title = Check your height +onboarding-automatic_proportions-check_height-description = We use your height as a basis of our measurements by using the HMD's height as an approximation of your actual height, but it's better to check if they are right yourself! +# All the text is in bold! +onboarding-automatic_proportions-check_height-calculation_warning = Please press the button while standing upright to calculate your height. You have 3 seconds after you press the button! +onboarding-automatic_proportions-check_height-fetch_height = I'm standing! +# Context is that the height is unknown +onboarding-automatic_proportions-check_height-unknown = Unknown +# Shows an element below it +onboarding-automatic_proportions-check_height-height = Your height is +# Shows an element below it +onboarding-automatic_proportions-check_height-hmd_height = And HMD height is +onboarding-automatic_proportions-check_height-next_step = They are fine onboarding-automatic_proportions-start_recording-title = Get ready to move onboarding-automatic_proportions-start_recording-description = We're now going to record some specific poses and moves. These will be prompted in the next screen. Be ready to start when the button is pressed! onboarding-automatic_proportions-start_recording-next = Start Recording @@ -792,6 +807,10 @@ onboarding-automatic_proportions-verify_results-redo = Redo recording onboarding-automatic_proportions-verify_results-confirm = They're correct onboarding-automatic_proportions-done-title = Body measured and saved. onboarding-automatic_proportions-done-description = Your body proportions' calibration is complete! +onboarding-automatic_proportions-error_modal = + Warning: An error was found while estimating proportions! + Please check the docs or join our Discord for help ^_^ +onboarding-automatic_proportions-error_modal-confirm = Understood! ## Home home-no_trackers = No trackers detected or assigned diff --git a/gui/src/App.tsx b/gui/src/App.tsx index 71f02bae1..213d70f51 100644 --- a/gui/src/App.tsx +++ b/gui/src/App.tsx @@ -55,7 +55,8 @@ import { error, log } from './utils/logging'; export const GH_REPO = 'SlimeVR/SlimeVR-Server'; export const VersionContext = createContext(''); -export const DOCS_SITE = 'https://docs.slimevr.dev/'; +export const DOCS_SITE = 'https://docs.slimevr.dev'; +export const SLIMEVR_DISCORD = 'https://discord.gg/slimevr'; function Layout() { const { loading } = useConfig(); diff --git a/gui/src/components/commons/A.tsx b/gui/src/components/commons/A.tsx new file mode 100644 index 000000000..311887831 --- /dev/null +++ b/gui/src/components/commons/A.tsx @@ -0,0 +1,14 @@ +import { open } from '@tauri-apps/api/shell'; +import { ReactNode } from 'react'; + +export function A({ href, children }: { href: string; children?: ReactNode }) { + return ( + open(href).catch(() => window.open(href, '_blank'))} + className="underline" + > + {children} + + ); +} diff --git a/gui/src/components/commons/NumberSelector.tsx b/gui/src/components/commons/NumberSelector.tsx index 4d93e6018..06d39869e 100644 --- a/gui/src/components/commons/NumberSelector.tsx +++ b/gui/src/components/commons/NumberSelector.tsx @@ -10,6 +10,7 @@ export function NumberSelector({ min, max, step, + disabled = false, }: { label: string; valueLabelFormat?: (value: number) => string; @@ -18,6 +19,7 @@ export function NumberSelector({ min: number; max: number; step: number | ((value: number, add: boolean) => number); + disabled?: boolean; }) { const stepFn = typeof step === 'function' @@ -38,7 +40,7 @@ export function NumberSelector({ variant="tertiary" rounded onClick={() => onChange(stepFn(value, false))} - disabled={stepFn(value, false) < min} + disabled={stepFn(value, false) < min || disabled} > - @@ -51,7 +53,7 @@ export function NumberSelector({ variant="tertiary" rounded onClick={() => onChange(stepFn(value, true))} - disabled={stepFn(value, true) > max} + disabled={stepFn(value, true) > max || disabled} > + diff --git a/gui/src/components/onboarding/pages/body-proportions/AutomaticProportions.tsx b/gui/src/components/onboarding/pages/body-proportions/AutomaticProportions.tsx index 62b31ad33..84f1c11e9 100644 --- a/gui/src/components/onboarding/pages/body-proportions/AutomaticProportions.tsx +++ b/gui/src/components/onboarding/pages/body-proportions/AutomaticProportions.tsx @@ -16,6 +16,7 @@ import { Recording } from './autobone-steps/Recording'; import { StartRecording } from './autobone-steps/StartRecording'; import { VerifyResultsStep } from './autobone-steps/VerifyResults'; import { useCountdown } from '../../../../hooks/countdown'; +import { CheckHeight } from './autobone-steps/СheckHeight'; export function AutomaticProportionsPage() { const { l10n } = useLocalization(); @@ -53,6 +54,7 @@ export function AutomaticProportionsPage() { steps={[ { type: 'numbered', component: PutTrackersOnStep }, { type: 'numbered', component: RequirementsStep }, + { type: 'numbered', component: CheckHeight }, { type: 'numbered', component: StartRecording }, { type: 'fullsize', component: Recording }, { type: 'numbered', component: VerifyResultsStep }, diff --git a/gui/src/components/onboarding/pages/body-proportions/BodyProportions.tsx b/gui/src/components/onboarding/pages/body-proportions/BodyProportions.tsx index 6a52015f9..3a6d099aa 100644 --- a/gui/src/components/onboarding/pages/body-proportions/BodyProportions.tsx +++ b/gui/src/components/onboarding/pages/body-proportions/BodyProportions.tsx @@ -60,7 +60,7 @@ export function BodyProportions({ unit: 'centimeter', maximumFractionDigits: 1, }); - const percentageFormat = Intl.NumberFormat(currentLocales, { + const percentageFormat = new Intl.NumberFormat(currentLocales, { style: 'percent', maximumFractionDigits: 1, }); diff --git a/gui/src/components/onboarding/pages/body-proportions/ProportionsChoose.tsx b/gui/src/components/onboarding/pages/body-proportions/ProportionsChoose.tsx index 9e030058b..0b24616a9 100644 --- a/gui/src/components/onboarding/pages/body-proportions/ProportionsChoose.tsx +++ b/gui/src/components/onboarding/pages/body-proportions/ProportionsChoose.tsx @@ -1,6 +1,6 @@ import { useOnboarding } from '../../../../hooks/onboarding'; -import { useLocalization } from '@fluent/react'; -import { useState } from 'react'; +import { Localized, useLocalization } from '@fluent/react'; +import { useMemo, useState } from 'react'; import classNames from 'classnames'; import { Typography } from '../../../commons/Typography'; import { Button } from '../../../commons/Button'; @@ -14,14 +14,39 @@ import saveAs from 'file-saver'; import { save } from '@tauri-apps/api/dialog'; import { writeTextFile } from '@tauri-apps/api/fs'; import { useIsTauri } from '../../../../hooks/breakpoint'; +import { useAppContext } from '../../../../hooks/app'; import { error } from '../../../../utils/logging'; +export const MIN_HEIGHT = 0.4; +export const MAX_HEIGHT = 4; +export const DEFAULT_HEIGHT = 1.5; + export function ProportionsChoose() { const isTauri = useIsTauri(); const { l10n } = useLocalization(); const { applyProgress, state } = useOnboarding(); const { useRPCPacket, sendRPCPacket } = useWebsocketAPI(); const [animated, setAnimated] = useState(false); + const { computedTrackers } = useAppContext(); + + const hmdTracker = useMemo( + () => + computedTrackers.find( + (tracker) => + tracker.tracker.trackerId?.trackerNum === 1 && + tracker.tracker.trackerId.deviceId?.id === undefined + ), + [computedTrackers] + ); + + const beneathFloor = useMemo( + () => + !( + hmdTracker?.tracker.position && + hmdTracker.tracker.position.y >= MIN_HEIGHT + ), + [hmdTracker?.tracker.position?.y] + ); useRPCPacket( RpcMessage.SkeletonConfigResponse, @@ -145,15 +170,23 @@ export function ProportionsChoose() {
- - {l10n.getString( - 'onboarding-choose_proportions-auto_proportions-description' - )} - + }} + > + + Description for autobone + +
+ + + + + ); +} diff --git a/gui/src/components/onboarding/pages/body-proportions/autobone-steps/Recording.tsx b/gui/src/components/onboarding/pages/body-proportions/autobone-steps/Recording.tsx index 83911f205..c9c56cc02 100644 --- a/gui/src/components/onboarding/pages/body-proportions/autobone-steps/Recording.tsx +++ b/gui/src/components/onboarding/pages/body-proportions/autobone-steps/Recording.tsx @@ -1,22 +1,49 @@ -import { useEffect } from 'react'; -import { useAutobone } from '../../../../../hooks/autobone'; +import { ReactNode, useEffect, useState } from 'react'; +import { ProcessStatus, useAutobone } from '../../../../../hooks/autobone'; import { ProgressBar } from '../../../../commons/ProgressBar'; import { TipBox } from '../../../../commons/TipBox'; import { Typography } from '../../../../commons/Typography'; import { useLocalization } from '@fluent/react'; +import { P, match } from 'ts-pattern'; +import { AutoboneErrorModal } from './AutoboneErrorModal'; -export function Recording({ nextStep }: { nextStep: () => void }) { +export function Recording({ + nextStep, + resetSteps, +}: { + nextStep: () => void; + resetSteps: () => void; +}) { const { l10n } = useLocalization(); - const { progress, hasCalibration, hasRecording } = useAutobone(); + const { progress, hasCalibration, hasRecording, eta } = useAutobone(); + const [modalOpen, setModalOpen] = useState(false); useEffect(() => { - if (progress === 1 && hasCalibration) { + if ( + hasRecording === ProcessStatus.REJECTED || + hasCalibration === ProcessStatus.REJECTED + ) { + setModalOpen(true); + } + if (progress !== 1) return; + + if ( + hasRecording === ProcessStatus.FULFILLED && + hasCalibration === ProcessStatus.FULFILLED + ) { nextStep(); } - }, [progress, hasCalibration]); + }, [progress, hasCalibration, hasRecording]); return (
+ { + setModalOpen(false); + resetSteps(); + }} + >
@@ -51,19 +78,39 @@ export function Recording({ nextStep }: { nextStep: () => void }) { {l10n.getString('tips-do_not_move_heels')}
- + () + .with( + P.union( + [ProcessStatus.REJECTED, P._], + [P._, ProcessStatus.REJECTED] + ), + () => 'bg-status-critical' + ) + .with( + [ProcessStatus.FULFILLED, ProcessStatus.FULFILLED], + () => 'bg-status-success' + ) + .otherwise(() => undefined)} + > - {!hasCalibration && hasRecording - ? l10n.getString( + {match([hasCalibration, hasRecording]) + .returnType() + .with([ProcessStatus.PENDING, ProcessStatus.FULFILLED], () => + l10n.getString( 'onboarding-automatic_proportions-recording-processing' ) - : l10n.getString( + ) + .with([ProcessStatus.PENDING, ProcessStatus.PENDING], () => + l10n.getString( 'onboarding-automatic_proportions-recording-timer', - { - // TODO: The progress should be communicated by the server in SolarXR - time: Math.round(20 * (1 - progress)), - } - )} + { time: Math.round(eta) } + ) + ) + .otherwise(() => '')}
diff --git a/gui/src/components/onboarding/pages/body-proportions/autobone-steps/VerifyResults.tsx b/gui/src/components/onboarding/pages/body-proportions/autobone-steps/VerifyResults.tsx index 910eda6bb..58c99d5a3 100644 --- a/gui/src/components/onboarding/pages/body-proportions/autobone-steps/VerifyResults.tsx +++ b/gui/src/components/onboarding/pages/body-proportions/autobone-steps/VerifyResults.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames'; -import { useAutobone } from '../../../../../hooks/autobone'; +import { ProcessStatus, useAutobone } from '../../../../../hooks/autobone'; import { Button } from '../../../../commons/Button'; import { Typography } from '../../../../commons/Typography'; import { useLocalization } from '@fluent/react'; @@ -69,13 +69,14 @@ export function VerifyResultsStep({ {(value * 100).toFixed(2)} CM
))} - {!hasCalibration && hasRecording && ( - - {l10n.getString( - 'onboarding-automatic-proportions-verify-results-processing' - )} - - )} + {hasCalibration === ProcessStatus.PENDING && + hasRecording === ProcessStatus.FULFILLED && ( + + {l10n.getString( + 'onboarding-automatic-proportions-verify-results-processing' + )} + + )} diff --git a/gui/src/components/onboarding/pages/body-proportions/autobone-steps/СheckHeight.tsx b/gui/src/components/onboarding/pages/body-proportions/autobone-steps/СheckHeight.tsx new file mode 100644 index 000000000..06e32cc7f --- /dev/null +++ b/gui/src/components/onboarding/pages/body-proportions/autobone-steps/СheckHeight.tsx @@ -0,0 +1,176 @@ +import { + AutoBoneSettingsT, + ChangeSettingsRequestT, + HeightRequestT, + HeightResponseT, + RpcMessage, +} from 'solarxr-protocol'; +import { useWebsocketAPI } from '../../../../../hooks/websocket-api'; +import { Button } from '../../../../commons/Button'; +import { Typography } from '../../../../commons/Typography'; +import { Localized, useLocalization } from '@fluent/react'; +import { useForm } from 'react-hook-form'; +import { useMemo, useState } from 'react'; +import { NumberSelector } from '../../../../commons/NumberSelector'; +import { DEFAULT_HEIGHT, MIN_HEIGHT } from '../ProportionsChoose'; +import { useLocaleConfig } from '../../../../../i18n/config'; +import { useCountdown } from '../../../../../hooks/countdown'; + +interface HeightForm { + height: number; + hmdHeight: number; +} + +export function CheckHeight({ + nextStep, + prevStep, + variant, +}: { + nextStep: () => void; + prevStep: () => void; + variant: 'onboarding' | 'alone'; +}) { + const { l10n } = useLocalization(); + const { control, handleSubmit, setValue } = useForm(); + const [fetchedHeight, setFetchedHeight] = useState(false); + const { sendRPCPacket, useRPCPacket } = useWebsocketAPI(); + const { timer, isCounting, startCountdown } = useCountdown({ + duration: 3, + onCountdownEnd: () => { + setFetchedHeight(true); + sendRPCPacket(RpcMessage.HeightRequest, new HeightRequestT()); + }, + }); + const { currentLocales } = useLocaleConfig(); + + const mFormat = useMemo( + () => + new Intl.NumberFormat(currentLocales, { + style: 'unit', + unit: 'meter', + maximumFractionDigits: 2, + }), + [currentLocales] + ); + + const sFormat = useMemo( + () => new Intl.RelativeTimeFormat(currentLocales, { style: 'short' }), + [currentLocales] + ); + + useRPCPacket( + RpcMessage.HeightResponse, + ({ hmdHeight, estimatedFullHeight }: HeightResponseT) => { + setValue('height', estimatedFullHeight || DEFAULT_HEIGHT); + setValue('hmdHeight', hmdHeight); + } + ); + + const onSubmit = (values: HeightForm) => { + const changeSettings = new ChangeSettingsRequestT(); + const autobone = new AutoBoneSettingsT(); + autobone.targetFullHeight = values.height; + autobone.targetHmdHeight = values.hmdHeight; + changeSettings.autoBoneSettings = autobone; + + sendRPCPacket(RpcMessage.ChangeSettingsRequest, changeSettings); + nextStep(); + }; + + return ( + <> +
+
+ + {l10n.getString( + 'onboarding-automatic_proportions-check_height-title' + )} + +
+ + {l10n.getString( + 'onboarding-automatic_proportions-check_height-description' + )} + + }} + > + + Press the button to get your height! + + + + +
+
+ + isNaN(value) + ? l10n.getString( + 'onboarding-automatic_proportions-check_height-unknown' + ) + : mFormat.format(value) + } + min={MIN_HEIGHT} + max={4} + step={0.01} + /> + + isNaN(value) + ? l10n.getString( + 'onboarding-automatic_proportions-check_height-unknown' + ) + : mFormat.format(value) + } + min={MIN_HEIGHT} + max={4} + step={0.01} + disabled={true} + /> + +
+ +
+ + +
+
+ + ); +} diff --git a/gui/src/components/settings/pages/GeneralSettings.tsx b/gui/src/components/settings/pages/GeneralSettings.tsx index f9cb17720..5c3b06814 100644 --- a/gui/src/components/settings/pages/GeneralSettings.tsx +++ b/gui/src/components/settings/pages/GeneralSettings.tsx @@ -125,7 +125,7 @@ export function GeneralSettings() { const { currentLocales } = useLocaleConfig(); // const pageRef = useRef(null); - const percentageFormat = Intl.NumberFormat(currentLocales, { + const percentageFormat = new Intl.NumberFormat(currentLocales, { style: 'percent', maximumFractionDigits: 0, }); diff --git a/gui/src/hooks/app.ts b/gui/src/hooks/app.ts index 5a27864ea..591f628d1 100644 --- a/gui/src/hooks/app.ts +++ b/gui/src/hooks/app.ts @@ -42,6 +42,7 @@ export interface AppContext { trackers: FlatDeviceTracker[]; dispatch: Dispatch; bones: BoneT[]; + computedTrackers: FlatDeviceTracker[]; } export function reducer(state: AppState, action: AppStateAction) { @@ -89,6 +90,11 @@ export function useProvideAppContext(): AppContext { [state] ); + const computedTrackers: FlatDeviceTracker[] = useMemo( + () => (state.datafeed?.syntheticTrackers || []).map((tracker) => ({ tracker })), + [state] + ); + const bones = useMemo(() => state.datafeed?.bones || [], [state]); useDataFeedPacket(DataFeedMessage.DataFeedUpdate, (packet: DataFeedUpdateT) => { @@ -114,6 +120,7 @@ export function useProvideAppContext(): AppContext { trackers, dispatch, bones, + computedTrackers, }; } diff --git a/gui/src/hooks/autobone.ts b/gui/src/hooks/autobone.ts index a65d7f2db..9657c54d2 100644 --- a/gui/src/hooks/autobone.ts +++ b/gui/src/hooks/autobone.ts @@ -1,23 +1,30 @@ import { createContext, useContext, useMemo, useState } from 'react'; import { + AutoBoneApplyRequestT, AutoBoneEpochResponseT, AutoBoneProcessRequestT, AutoBoneProcessStatusResponseT, AutoBoneProcessType, RpcMessage, SkeletonBone, - SkeletonConfigRequestT, SkeletonPartT, } from 'solarxr-protocol'; import { useWebsocketAPI } from './websocket-api'; import { useLocalization } from '@fluent/react'; import { log } from '../utils/logging'; +export enum ProcessStatus { + PENDING, + FULFILLED, + REJECTED, +} + export interface AutoboneContext { - hasRecording: boolean; - hasCalibration: boolean; + hasRecording: ProcessStatus; + hasCalibration: ProcessStatus; progress: number; bodyParts: { bone: SkeletonBone; label: string; value: number }[] | null; + eta: number; startRecording: () => void; startProcessing: () => void; applyProcessing: () => void; @@ -26,9 +33,10 @@ export interface AutoboneContext { export function useProvideAutobone(): AutoboneContext { const { l10n } = useLocalization(); const { useRPCPacket, sendRPCPacket } = useWebsocketAPI(); - const [hasRecording, setHasRecording] = useState(false); - const [hasCalibration, setHasCalibration] = useState(false); + const [hasRecording, setHasRecording] = useState(ProcessStatus.PENDING); + const [hasCalibration, setHasCalibration] = useState(ProcessStatus.PENDING); const [progress, setProgress] = useState(0); + const [eta, setEta] = useState(-1); const [skeletonParts, setSkeletonParts] = useState(null); const bodyParts = useMemo(() => { @@ -48,6 +56,7 @@ export function useProvideAutobone(): AutoboneContext { // } setProgress(0); + setEta(-1); const processRequest = new AutoBoneProcessRequestT(); processRequest.processType = processType; @@ -56,19 +65,19 @@ export function useProvideAutobone(): AutoboneContext { }; const startRecording = () => { - setHasCalibration(false); - setHasRecording(false); + setHasCalibration(ProcessStatus.PENDING); + setHasRecording(ProcessStatus.PENDING); setSkeletonParts(null); startProcess(AutoBoneProcessType.RECORD); }; const startProcessing = () => { - setHasCalibration(false); + setHasCalibration(ProcessStatus.PENDING); startProcess(AutoBoneProcessType.PROCESS); }; const applyProcessing = () => { - startProcess(AutoBoneProcessType.APPLY); + sendRPCPacket(RpcMessage.AutoBoneApplyRequest, new AutoBoneApplyRequestT()); }; useRPCPacket( @@ -79,35 +88,36 @@ export function useProvideAutobone(): AutoboneContext { } if (data.processType) { - if (data.message) { - log(AutoBoneProcessType[data.processType], ': ', data.message); - } - if (data.total > 0 && data.current >= 0) { setProgress(data.current / data.total); } + setEta(data.eta); + if (data.completed) { - log('Process ', AutoBoneProcessType[data.processType], ' has completed'); + log(`Process ${AutoBoneProcessType[data.processType]} has completed`); switch (data.processType) { case AutoBoneProcessType.RECORD: - setHasRecording(data.success); + setHasRecording( + data.success ? ProcessStatus.FULFILLED : ProcessStatus.REJECTED + ); startProcessing(); break; case AutoBoneProcessType.PROCESS: - setHasCalibration(data.success); - - break; - - case AutoBoneProcessType.APPLY: - // Update skeleton config when applied - sendRPCPacket( - RpcMessage.SkeletonConfigRequest, - new SkeletonConfigRequestT() + setHasCalibration( + data.success ? ProcessStatus.FULFILLED : ProcessStatus.REJECTED ); break; + + // case AutoBoneProcessType.APPLY: + // // Update skeleton config when applied + // sendRPCPacket( + // RpcMessage.SkeletonConfigRequest, + // new SkeletonConfigRequestT() + // ); + // break; } } } @@ -135,6 +145,7 @@ export function useProvideAutobone(): AutoboneContext { hasCalibration, hasRecording, progress, + eta, bodyParts, startProcessing, startRecording, diff --git a/package-lock.json b/package-lock.json index 393969d8b..a87d0bd8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,7 @@ "gui" ], "devDependencies": { - "@typescript-eslint/parser": "^5.59.6", - "husky": "^8.0.2", - "typescript": "^5.0.4" + "husky": "^8.0.3" } }, "gui": { @@ -47,49 +45,46 @@ "react-modal": "3.15.1", "react-responsive": "^9.0.2", "react-router-dom": "^6.2.2", - "semver": "^7.5.0", + "semver": "^7.5.3", "solarxr-protocol": "file:../solarxr-protocol", "three": "^0.148.0", - "typescript": "^4.6.3" + "ts-pattern": "^5.0.1", + "typescript": "^5.1.6" }, "devDependencies": { - "@tailwindcss/forms": "^0.5.0", + "@tailwindcss/forms": "^0.5.3", "@tauri-apps/cli": "^1.4.0", "@types/file-saver": "^2.0.5", "@types/react": "18.0.25", "@types/react-dom": "^18.0.5", "@types/react-modal": "3.13.1", "@types/three": "^0.148.0", - "@typescript-eslint/eslint-plugin": "^5.59.6", - "@typescript-eslint/parser": "^5.59.0", + "@typescript-eslint/eslint-plugin": "^5.60.1", + "@typescript-eslint/parser": "^5.60.1", "autoprefixer": "^10.4.4", "cross-env": "^7.0.3", - "eslint": "^8.18.0", + "eslint": "^8.44.0", "eslint-config-airbnb": "^19.0.4", - "eslint-import-resolver-typescript": "^3.1.1", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jsx-a11y": "^6.6.0", - "eslint-plugin-react": "^7.30.1", + "eslint-import-resolver-typescript": "^3.5.5", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", - "postcss": "^8.4.12", - "prettier": "^2.7.1", + "postcss": "^8.4.24", + "prettier": "^2.8.8", "pretty-quick": "^3.1.3", "rollup-plugin-visualizer": "^5.9.2", "tailwind-gradient-mask-image": "^1.0.0", - "tailwindcss": "^3.3.1", - "vite": "^4.0.3" + "tailwindcss": "^3.3.2", + "vite": "^4.3.9" } }, - "gui/node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", "engines": { - "node": ">=4.2.0" + "node": ">=0.10.0" } }, "node_modules/@alloc/quick-lru": { @@ -2709,13 +2704,13 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", - "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", + "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.2", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -2745,9 +2740,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz", - "integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==", + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", + "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -3414,14 +3409,14 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.11.tgz", - "integrity": "sha512-XxuOfTkCUiOSyBWIvHlUraLw/JT/6Io1365RO6ZuI88STKMavJZPNMU0lFcUTeQXEhHiv64CbxYxBNoDVSmghg==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.60.1.tgz", + "integrity": "sha512-KSWsVvsJsLJv3c4e73y/Bzt7OpqMCADUO846bHcuWYSYM19bldbAeDv7dYyV0jwkbMfJ2XdlzwjhXtuD7OY6bw==", "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/type-utils": "5.59.11", - "@typescript-eslint/utils": "5.59.11", + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/type-utils": "5.60.1", + "@typescript-eslint/utils": "5.60.1", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -3446,6 +3441,101 @@ } } }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.1.tgz", + "integrity": "sha512-Dn/LnN7fEoRD+KspEOV0xDMynEmR3iSHdgNsarlXNLGGtcUok8L4N71dxUgt3YvlO8si7E+BJ5Fe3wb5yUw7DQ==", + "dependencies": { + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.1.tgz", + "integrity": "sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz", + "integrity": "sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw==", + "dependencies": { + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.60.1.tgz", + "integrity": "sha512-tiJ7FFdFQOWssFa3gqb94Ilexyw0JVxj6vBzaSpfN/8IhoKkDuSAenUKvsSHw2A/TMpJb26izIszTXaqygkvpQ==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/typescript-estree": "5.60.1", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz", + "integrity": "sha512-xEYIxKcultP6E/RMKqube11pGjXH1DCo60mQoWhVYyKfLkwbIVVjYxmOenNMxILx0TjCujPTjjnTIVzm09TXIw==", + "dependencies": { + "@typescript-eslint/types": "5.60.1", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/experimental-utils": { "version": "5.59.11", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.59.11.tgz", @@ -3465,13 +3555,13 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.11.tgz", - "integrity": "sha512-s9ZF3M+Nym6CAZEkJJeO2TFHHDsKAM3ecNkLuH4i4s8/RCPnF5JRip2GyviYkeEAcwGMJxkqG9h2dAsnA1nZpA==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.60.1.tgz", + "integrity": "sha512-pHWlc3alg2oSMGwsU/Is8hbm3XFbcrb6P5wIxcQW9NsYBfnrubl/GhVVD/Jm/t8HXhA2WncoIRfBtnCgRGV96Q==", "dependencies": { - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/typescript-estree": "5.59.11", + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/typescript-estree": "5.60.1", "debug": "^4.3.4" }, "engines": { @@ -3490,6 +3580,76 @@ } } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.1.tgz", + "integrity": "sha512-Dn/LnN7fEoRD+KspEOV0xDMynEmR3iSHdgNsarlXNLGGtcUok8L4N71dxUgt3YvlO8si7E+BJ5Fe3wb5yUw7DQ==", + "dependencies": { + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.1.tgz", + "integrity": "sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz", + "integrity": "sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw==", + "dependencies": { + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz", + "integrity": "sha512-xEYIxKcultP6E/RMKqube11pGjXH1DCo60mQoWhVYyKfLkwbIVVjYxmOenNMxILx0TjCujPTjjnTIVzm09TXIw==", + "dependencies": { + "@typescript-eslint/types": "5.60.1", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/scope-manager": { "version": "5.59.11", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.11.tgz", @@ -3507,12 +3667,12 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.11.tgz", - "integrity": "sha512-LZqVY8hMiVRF2a7/swmkStMYSoXMFlzL6sXV6U/2gL5cwnLWQgLEG8tjWPpaE4rMIdZ6VKWwcffPlo1jPfk43g==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.60.1.tgz", + "integrity": "sha512-vN6UztYqIu05nu7JqwQGzQKUJctzs3/Hg7E2Yx8rz9J+4LgtIDFWjjl1gm3pycH0P3mHAcEUBd23LVgfrsTR8A==", "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.11", - "@typescript-eslint/utils": "5.59.11", + "@typescript-eslint/typescript-estree": "5.60.1", + "@typescript-eslint/utils": "5.60.1", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -3532,6 +3692,101 @@ } } }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.1.tgz", + "integrity": "sha512-Dn/LnN7fEoRD+KspEOV0xDMynEmR3iSHdgNsarlXNLGGtcUok8L4N71dxUgt3YvlO8si7E+BJ5Fe3wb5yUw7DQ==", + "dependencies": { + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.1.tgz", + "integrity": "sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz", + "integrity": "sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw==", + "dependencies": { + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.60.1.tgz", + "integrity": "sha512-tiJ7FFdFQOWssFa3gqb94Ilexyw0JVxj6vBzaSpfN/8IhoKkDuSAenUKvsSHw2A/TMpJb26izIszTXaqygkvpQ==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/typescript-estree": "5.60.1", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz", + "integrity": "sha512-xEYIxKcultP6E/RMKqube11pGjXH1DCo60mQoWhVYyKfLkwbIVVjYxmOenNMxILx0TjCujPTjjnTIVzm09TXIw==", + "dependencies": { + "@typescript-eslint/types": "5.60.1", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/types": { "version": "5.59.11", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.11.tgz", @@ -3788,9 +4043,9 @@ "peer": true }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", "bin": { "acorn": "bin/acorn" }, @@ -5132,14 +5387,14 @@ } }, "node_modules/eslint": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz", - "integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==", + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz", + "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.42.0", + "@eslint/eslintrc": "^2.1.0", + "@eslint/js": "8.44.0", "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -5151,7 +5406,7 @@ "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.0", "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", + "espree": "^9.6.0", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -5171,7 +5426,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" @@ -5718,11 +5973,11 @@ } }, "node_modules/espree": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", - "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz", + "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==", "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" }, @@ -7606,16 +7861,16 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -9427,9 +9682,9 @@ } }, "node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -10014,6 +10269,11 @@ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, + "node_modules/ts-pattern": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-5.0.1.tgz", + "integrity": "sha512-ZyNm28Lsg34Co5DS3e9DVyjlX2Y+2exkI4jqTKyU+9/OL6Y2fKOOuL8i+7no71o74C6mVS+UFoP3ekM3iCT1HQ==" + }, "node_modules/tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", @@ -10096,9 +10356,9 @@ } }, "node_modules/typescript": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", - "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10421,14 +10681,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -10595,6 +10847,11 @@ } }, "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==" + }, "@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -12195,13 +12452,13 @@ "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==" }, "@eslint/eslintrc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", - "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", + "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.2", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -12221,9 +12478,9 @@ } }, "@eslint/js": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz", - "integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==" + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", + "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==" }, "@fluent/bundle": { "version": "0.17.1", @@ -12678,20 +12935,74 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.11.tgz", - "integrity": "sha512-XxuOfTkCUiOSyBWIvHlUraLw/JT/6Io1365RO6ZuI88STKMavJZPNMU0lFcUTeQXEhHiv64CbxYxBNoDVSmghg==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.60.1.tgz", + "integrity": "sha512-KSWsVvsJsLJv3c4e73y/Bzt7OpqMCADUO846bHcuWYSYM19bldbAeDv7dYyV0jwkbMfJ2XdlzwjhXtuD7OY6bw==", "requires": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/type-utils": "5.59.11", - "@typescript-eslint/utils": "5.59.11", + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/type-utils": "5.60.1", + "@typescript-eslint/utils": "5.60.1", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", "semver": "^7.3.7", "tsutils": "^3.21.0" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.1.tgz", + "integrity": "sha512-Dn/LnN7fEoRD+KspEOV0xDMynEmR3iSHdgNsarlXNLGGtcUok8L4N71dxUgt3YvlO8si7E+BJ5Fe3wb5yUw7DQ==", + "requires": { + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1" + } + }, + "@typescript-eslint/types": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.1.tgz", + "integrity": "sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg==" + }, + "@typescript-eslint/typescript-estree": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz", + "integrity": "sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw==", + "requires": { + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.60.1.tgz", + "integrity": "sha512-tiJ7FFdFQOWssFa3gqb94Ilexyw0JVxj6vBzaSpfN/8IhoKkDuSAenUKvsSHw2A/TMpJb26izIszTXaqygkvpQ==", + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/typescript-estree": "5.60.1", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz", + "integrity": "sha512-xEYIxKcultP6E/RMKqube11pGjXH1DCo60mQoWhVYyKfLkwbIVVjYxmOenNMxILx0TjCujPTjjnTIVzm09TXIw==", + "requires": { + "@typescript-eslint/types": "5.60.1", + "eslint-visitor-keys": "^3.3.0" + } + } } }, "@typescript-eslint/experimental-utils": { @@ -12703,14 +13014,53 @@ } }, "@typescript-eslint/parser": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.11.tgz", - "integrity": "sha512-s9ZF3M+Nym6CAZEkJJeO2TFHHDsKAM3ecNkLuH4i4s8/RCPnF5JRip2GyviYkeEAcwGMJxkqG9h2dAsnA1nZpA==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.60.1.tgz", + "integrity": "sha512-pHWlc3alg2oSMGwsU/Is8hbm3XFbcrb6P5wIxcQW9NsYBfnrubl/GhVVD/Jm/t8HXhA2WncoIRfBtnCgRGV96Q==", "requires": { - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/typescript-estree": "5.59.11", + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/typescript-estree": "5.60.1", "debug": "^4.3.4" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.1.tgz", + "integrity": "sha512-Dn/LnN7fEoRD+KspEOV0xDMynEmR3iSHdgNsarlXNLGGtcUok8L4N71dxUgt3YvlO8si7E+BJ5Fe3wb5yUw7DQ==", + "requires": { + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1" + } + }, + "@typescript-eslint/types": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.1.tgz", + "integrity": "sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg==" + }, + "@typescript-eslint/typescript-estree": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz", + "integrity": "sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw==", + "requires": { + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz", + "integrity": "sha512-xEYIxKcultP6E/RMKqube11pGjXH1DCo60mQoWhVYyKfLkwbIVVjYxmOenNMxILx0TjCujPTjjnTIVzm09TXIw==", + "requires": { + "@typescript-eslint/types": "5.60.1", + "eslint-visitor-keys": "^3.3.0" + } + } } }, "@typescript-eslint/scope-manager": { @@ -12723,14 +13073,68 @@ } }, "@typescript-eslint/type-utils": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.11.tgz", - "integrity": "sha512-LZqVY8hMiVRF2a7/swmkStMYSoXMFlzL6sXV6U/2gL5cwnLWQgLEG8tjWPpaE4rMIdZ6VKWwcffPlo1jPfk43g==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.60.1.tgz", + "integrity": "sha512-vN6UztYqIu05nu7JqwQGzQKUJctzs3/Hg7E2Yx8rz9J+4LgtIDFWjjl1gm3pycH0P3mHAcEUBd23LVgfrsTR8A==", "requires": { - "@typescript-eslint/typescript-estree": "5.59.11", - "@typescript-eslint/utils": "5.59.11", + "@typescript-eslint/typescript-estree": "5.60.1", + "@typescript-eslint/utils": "5.60.1", "debug": "^4.3.4", "tsutils": "^3.21.0" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.1.tgz", + "integrity": "sha512-Dn/LnN7fEoRD+KspEOV0xDMynEmR3iSHdgNsarlXNLGGtcUok8L4N71dxUgt3YvlO8si7E+BJ5Fe3wb5yUw7DQ==", + "requires": { + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1" + } + }, + "@typescript-eslint/types": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.1.tgz", + "integrity": "sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg==" + }, + "@typescript-eslint/typescript-estree": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz", + "integrity": "sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw==", + "requires": { + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.60.1.tgz", + "integrity": "sha512-tiJ7FFdFQOWssFa3gqb94Ilexyw0JVxj6vBzaSpfN/8IhoKkDuSAenUKvsSHw2A/TMpJb26izIszTXaqygkvpQ==", + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/typescript-estree": "5.60.1", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz", + "integrity": "sha512-xEYIxKcultP6E/RMKqube11pGjXH1DCo60mQoWhVYyKfLkwbIVVjYxmOenNMxILx0TjCujPTjjnTIVzm09TXIw==", + "requires": { + "@typescript-eslint/types": "5.60.1", + "eslint-visitor-keys": "^3.3.0" + } + } } }, "@typescript-eslint/types": { @@ -12947,9 +13351,9 @@ "peer": true }, "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==" + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==" }, "acorn-import-assertions": { "version": "1.9.0", @@ -13904,14 +14308,14 @@ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" }, "eslint": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz", - "integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==", + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz", + "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==", "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.42.0", + "@eslint/eslintrc": "^2.1.0", + "@eslint/js": "8.44.0", "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -13923,7 +14327,7 @@ "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.0", "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", + "espree": "^9.6.0", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -13943,7 +14347,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" @@ -14318,11 +14722,11 @@ "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==" }, "espree": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", - "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz", + "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==", "requires": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } @@ -15646,16 +16050,16 @@ } }, "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" } }, "p-limit": { @@ -16787,9 +17191,9 @@ } }, "semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "requires": { "lru-cache": "^6.0.0" }, @@ -16875,7 +17279,7 @@ "@fontsource/poppins": "^4.5.8", "@formatjs/intl-localematcher": "^0.2.32", "@react-three/fiber": "^8.10.0", - "@tailwindcss/forms": "^0.5.0", + "@tailwindcss/forms": "^0.5.3", "@tauri-apps/api": "^1.4.0", "@tauri-apps/cli": "^1.4.0", "@types/file-saver": "^2.0.5", @@ -16883,31 +17287,31 @@ "@types/react-dom": "^18.0.5", "@types/react-modal": "3.13.1", "@types/three": "^0.148.0", - "@typescript-eslint/eslint-plugin": "^5.59.6", - "@typescript-eslint/parser": "^5.59.0", + "@typescript-eslint/eslint-plugin": "^5.60.1", + "@typescript-eslint/parser": "^5.60.1", "@vitejs/plugin-react": "^3.0.0", "autoprefixer": "^10.4.4", "browserslist": "^4.18.1", "classnames": "^2.3.1", "cross-env": "^7.0.3", - "eslint": "^8.18.0", + "eslint": "^8.44.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-react-app": "^7.0.0", - "eslint-import-resolver-typescript": "^3.1.1", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jsx-a11y": "^6.6.0", - "eslint-plugin-react": "^7.30.1", + "eslint-import-resolver-typescript": "^3.5.5", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "file-saver": "^2.0.5", "flatbuffers": "^22.10.26", "identity-obj-proxy": "^3.0.0", "intl-pluralrules": "^1.3.1", "ip-num": "^1.4.1", - "postcss": "^8.4.12", + "postcss": "^8.4.24", "postcss-flexbugs-fixes": "^5.0.2", "postcss-normalize": "^10.0.1", "postcss-preset-env": "^7.0.1", - "prettier": "^2.7.1", + "prettier": "^2.8.8", "pretty-quick": "^3.1.3", "prompts": "^2.4.2", "react": "^18.0.0", @@ -16917,21 +17321,15 @@ "react-modal": "3.15.1", "react-responsive": "^9.0.2", "react-router-dom": "^6.2.2", + "semver": "^7.5.3", "rollup-plugin-visualizer": "^5.9.2", - "semver": "^7.5.0", "solarxr-protocol": "file:../solarxr-protocol", "tailwind-gradient-mask-image": "^1.0.0", - "tailwindcss": "^3.3.1", + "tailwindcss": "^3.3.2", "three": "^0.148.0", - "typescript": "^4.6.3", - "vite": "^4.0.3" - }, - "dependencies": { - "typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" - } + "ts-pattern": "^5.0.1", + "typescript": "^5.1.6", + "vite": "^4.3.9" } }, "solarxr-protocol": { @@ -17294,6 +17692,11 @@ } } }, + "ts-pattern": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-5.0.1.tgz", + "integrity": "sha512-ZyNm28Lsg34Co5DS3e9DVyjlX2Y+2exkI4jqTKyU+9/OL6Y2fKOOuL8i+7no71o74C6mVS+UFoP3ekM3iCT1HQ==" + }, "tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", @@ -17359,9 +17762,9 @@ } }, "typescript": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", - "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==" + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==" }, "unbox-primitive": { "version": "1.0.2", @@ -17556,11 +17959,6 @@ "is-typed-array": "^1.1.10" } }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" - }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index 1bdc3f687..2190ff38f 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,6 @@ "prepare": "husky install && npm run update-solarxr" }, "devDependencies": { - "@typescript-eslint/parser": "^5.59.6", - "husky": "^8.0.2", - "typescript": "^5.0.4" + "husky": "^8.0.3" } } diff --git a/server/core/src/main/java/dev/slimevr/autobone/AutoBone.kt b/server/core/src/main/java/dev/slimevr/autobone/AutoBone.kt index 39c5baff1..b7128c222 100644 --- a/server/core/src/main/java/dev/slimevr/autobone/AutoBone.kt +++ b/server/core/src/main/java/dev/slimevr/autobone/AutoBone.kt @@ -7,7 +7,6 @@ import dev.slimevr.autobone.errors.* import dev.slimevr.config.AutoBoneConfig import dev.slimevr.poseframeformat.PoseFrameIO import dev.slimevr.poseframeformat.PoseFrames -import dev.slimevr.tracking.processor.BoneType import dev.slimevr.tracking.processor.HumanPoseManager import dev.slimevr.tracking.processor.config.SkeletonConfigManager import dev.slimevr.tracking.processor.config.SkeletonConfigOffsets @@ -20,45 +19,43 @@ import io.github.axisangles.ktmath.Vector3 import org.apache.commons.lang3.tuple.Pair import java.io.File import java.util.* -import java.util.function.BiConsumer import java.util.function.Consumer import java.util.function.Function class AutoBone(server: VRServer) { - // This is filled by reloadConfigValues() - val offsets = EnumMap( - BoneType::class.java + // This is filled by loadConfigValues() + val offsets = EnumMap( + SkeletonConfigOffsets::class.java ) val adjustOffsets = FastList( arrayOf( - BoneType.HEAD, - BoneType.NECK, - BoneType.UPPER_CHEST, - BoneType.CHEST, - BoneType.WAIST, - BoneType.HIP, // This now works when using body proportion error! It's not the + SkeletonConfigOffsets.HEAD, + SkeletonConfigOffsets.NECK, + SkeletonConfigOffsets.UPPER_CHEST, + SkeletonConfigOffsets.CHEST, + SkeletonConfigOffsets.WAIST, + SkeletonConfigOffsets.HIP, // This now works when using body proportion error! It's not the // best still, but it is somewhat functional - BoneType.LEFT_HIP, - BoneType.LEFT_UPPER_LEG, - BoneType.LEFT_LOWER_LEG + SkeletonConfigOffsets.HIPS_WIDTH, + SkeletonConfigOffsets.UPPER_LEG, + SkeletonConfigOffsets.LOWER_LEG ) ) - val heightOffsets = FastList( - arrayOf( - BoneType.NECK, - BoneType.UPPER_CHEST, - BoneType.CHEST, - BoneType.WAIST, - BoneType.HIP, - BoneType.LEFT_UPPER_LEG, - BoneType.RIGHT_UPPER_LEG, - BoneType.LEFT_LOWER_LEG, - BoneType.RIGHT_LOWER_LEG - ) + val heightOffsetDefaults = EnumMap( + SkeletonConfigOffsets::class.java ) - val legacyConfigs = EnumMap( - SkeletonConfigOffsets::class.java + // This is filled by loadConfigValues() + val heightOffsets = FastList( + arrayOf( + SkeletonConfigOffsets.NECK, + SkeletonConfigOffsets.UPPER_CHEST, + SkeletonConfigOffsets.CHEST, + SkeletonConfigOffsets.WAIST, + SkeletonConfigOffsets.HIP, + SkeletonConfigOffsets.UPPER_LEG, + SkeletonConfigOffsets.LOWER_LEG + ) ) private val server: VRServer @@ -83,34 +80,6 @@ class AutoBone(server: VRServer) { loadConfigValues() } - fun computeBoneOffset( - bone: BoneType, - getOffset: Function, - ): Float { - return when (bone) { - BoneType.HEAD -> getOffset.apply(SkeletonConfigOffsets.HEAD) - BoneType.NECK -> getOffset.apply(SkeletonConfigOffsets.NECK) - BoneType.UPPER_CHEST -> getOffset.apply(SkeletonConfigOffsets.UPPER_CHEST) - BoneType.CHEST -> getOffset.apply(SkeletonConfigOffsets.CHEST) - BoneType.WAIST -> getOffset.apply(SkeletonConfigOffsets.WAIST) - BoneType.HIP -> getOffset.apply(SkeletonConfigOffsets.HIP) - BoneType.LEFT_HIP, BoneType.RIGHT_HIP -> ( - getOffset.apply(SkeletonConfigOffsets.HIPS_WIDTH) / - 2f - ) - - BoneType.LEFT_UPPER_LEG, BoneType.RIGHT_UPPER_LEG -> - getOffset - .apply(SkeletonConfigOffsets.UPPER_LEG) - - BoneType.LEFT_LOWER_LEG, BoneType.RIGHT_LOWER_LEG -> - getOffset - .apply(SkeletonConfigOffsets.LOWER_LEG) - - else -> -1f - } - } - private fun loadConfigValues() { // Remove all previous values offsets.clear() @@ -127,143 +96,83 @@ class AutoBone(server: VRServer) { } } for (bone in adjustOffsets) { - val offset = computeBoneOffset(bone, getOffset) + val offset = getOffset.apply(bone) if (offset > 0f) { offsets[bone] = offset } } + for (bone in heightOffsets) { + val offset = getOffset.apply(bone) + if (offset > 0f) { + heightOffsetDefaults[bone] = offset + } + } } fun getBoneDirection( skeleton: HumanPoseManager, - node: BoneType, + configOffset: SkeletonConfigOffsets, rightSide: Boolean, ): Vector3 { - var node = when (node) { - BoneType.LEFT_HIP, BoneType.RIGHT_HIP -> if (rightSide) BoneType.RIGHT_HIP else BoneType.LEFT_HIP - BoneType.LEFT_UPPER_LEG, BoneType.RIGHT_UPPER_LEG -> - if (rightSide) BoneType.RIGHT_UPPER_LEG else BoneType.LEFT_UPPER_LEG - BoneType.LEFT_LOWER_LEG, BoneType.RIGHT_LOWER_LEG -> - if (rightSide) BoneType.RIGHT_LOWER_LEG else BoneType.LEFT_LOWER_LEG - else -> node + // IMPORTANT: This assumption for acquiring BoneType only works if + // SkeletonConfigOffsets is set up to only affect one BoneType, make sure no + // changes to SkeletonConfigOffsets goes against this assumption, please! + val boneType = when (configOffset) { + SkeletonConfigOffsets.HIPS_WIDTH, SkeletonConfigOffsets.SHOULDERS_WIDTH, + SkeletonConfigOffsets.SHOULDERS_DISTANCE, SkeletonConfigOffsets.UPPER_ARM, + SkeletonConfigOffsets.LOWER_ARM, SkeletonConfigOffsets.UPPER_LEG, + SkeletonConfigOffsets.LOWER_LEG, SkeletonConfigOffsets.FOOT_LENGTH, + -> + if (rightSide) configOffset.affectedOffsets[1] else configOffset.affectedOffsets[0] + else -> configOffset.affectedOffsets[0] } - val relevantTransform = skeleton.getTailNodeOfBone(node) - return (relevantTransform.worldTransform.translation - relevantTransform.parent!!.worldTransform.translation).unit() + val relevantTransform = skeleton.getTailNodeOfBone(boneType) + return ( + relevantTransform.worldTransform.translation - + relevantTransform.parent!!.worldTransform.translation + ).unit() } fun getDotProductDiff( skeleton1: HumanPoseManager, skeleton2: HumanPoseManager, - node: BoneType, + configOffset: SkeletonConfigOffsets, rightSide: Boolean, offset: Vector3, ): Float { val normalizedOffset = offset.unit() - val dot1 = normalizedOffset.dot(getBoneDirection(skeleton1, node, rightSide)) - val dot2 = normalizedOffset.dot(getBoneDirection(skeleton2, node, rightSide)) + val dot1 = normalizedOffset.dot(getBoneDirection(skeleton1, configOffset, rightSide)) + val dot2 = normalizedOffset.dot(getBoneDirection(skeleton2, configOffset, rightSide)) return dot2 - dot1 } - fun applyConfig( - configConsumer: BiConsumer, - offsets: Map = this.offsets, - ): Boolean { - return try { - val headOffset = offsets[BoneType.HEAD] - if (headOffset != null) { - configConsumer.accept(SkeletonConfigOffsets.HEAD, headOffset) - } - val neckOffset = offsets[BoneType.NECK] - if (neckOffset != null) { - configConsumer.accept(SkeletonConfigOffsets.NECK, neckOffset) - } - val upperChestOffset = offsets[BoneType.UPPER_CHEST] - val chestOffset = offsets[BoneType.CHEST] - val waistOffset = offsets[BoneType.WAIST] - val hipOffset = offsets[BoneType.HIP] - if (upperChestOffset != null) { - configConsumer - .accept(SkeletonConfigOffsets.UPPER_CHEST, upperChestOffset) - } - if (chestOffset != null) { - configConsumer - .accept(SkeletonConfigOffsets.CHEST, chestOffset) - } - if (waistOffset != null) { - configConsumer.accept(SkeletonConfigOffsets.WAIST, waistOffset) - } - if (hipOffset != null) { - configConsumer.accept(SkeletonConfigOffsets.HIP, hipOffset) - } - var hipWidthOffset = offsets[BoneType.LEFT_HIP] - if (hipWidthOffset == null) { - hipWidthOffset = offsets[BoneType.RIGHT_HIP] - } - if (hipWidthOffset != null) { - configConsumer - .accept(SkeletonConfigOffsets.HIPS_WIDTH, hipWidthOffset * 2f) - } - var upperLegOffset = offsets[BoneType.LEFT_UPPER_LEG] - if (upperLegOffset == null) { - upperLegOffset = offsets[BoneType.RIGHT_UPPER_LEG] - } - var lowerLegOffset = offsets[BoneType.LEFT_LOWER_LEG] - if (lowerLegOffset == null) { - lowerLegOffset = offsets[BoneType.RIGHT_LOWER_LEG] - } - if (upperLegOffset != null) { - configConsumer - .accept(SkeletonConfigOffsets.UPPER_LEG, upperLegOffset) - } - if (lowerLegOffset != null) { - configConsumer.accept(SkeletonConfigOffsets.LOWER_LEG, lowerLegOffset) - } - true - } catch (e: Exception) { - false - } - } - - fun applyConfig( - skeletonConfig: MutableMap, - offsets: Map = this.offsets, - ): Boolean { - return applyConfig({ key: SkeletonConfigOffsets, value: Float -> skeletonConfig[key] = value }, offsets) - } - fun applyConfig( humanPoseManager: HumanPoseManager, - offsets: Map = this.offsets, - ): Boolean { - return applyConfig({ key: SkeletonConfigOffsets?, newLength: Float? -> - humanPoseManager.setOffset( - key, - newLength - ) - }, offsets) + offsets: Map = this.offsets, + ) { + for ((offset, value) in offsets) { + humanPoseManager.setOffset(offset, value) + } } @JvmOverloads fun applyAndSaveConfig(humanPoseManager: HumanPoseManager? = this.server.humanPoseManager): Boolean { if (humanPoseManager == null) return false - if (!applyConfig(humanPoseManager)) return false + applyConfig(humanPoseManager) humanPoseManager.saveConfig() server.configManager.saveConfig() LogManager.info("[AutoBone] Configured skeleton bone lengths") return true } - fun getConfig(config: BoneType): Float? { - return offsets[config] - } - fun sumSelectConfigs( selection: List, - configs: Function, + configs: Map, + configsAlt: Map? = null, ): Float { var sum = 0f for (config in selection) { - val length = configs.apply(config) + val length = configs[config] ?: configsAlt?.get(config) if (length != null) { sum += length } @@ -271,27 +180,17 @@ class AutoBone(server: VRServer) { return sum } - fun sumSelectConfigs( - selection: List, - configs: Map, - ): Float { - return sumSelectConfigs(selection) { key: T -> configs[key] } + fun calcHeight(): Float { + return sumSelectConfigs(heightOffsets, offsets, heightOffsetDefaults) } - fun sumSelectConfigs( - selection: List, - humanPoseManager: HumanPoseManager, - ): Float { - return sumSelectConfigs(selection) { key: SkeletonConfigOffsets? -> humanPoseManager.getOffset(key) } - } - - fun getLengthSum(configs: Map): Float { + fun getLengthSum(configs: Map): Float { return getLengthSum(configs, null) } fun getLengthSum( - configs: Map, - configsAlt: Map?, + configs: Map, + configsAlt: Map?, ): Float { var length = 0f if (configsAlt != null) { @@ -327,7 +226,7 @@ class AutoBone(server: VRServer) { // Otherwise if there is no skeleton available, attempt to get the // max HMD height from the recording val hmdHeight = frames.maxHmdHeight - if (hmdHeight <= 0.50f) { + if (hmdHeight <= 0.4f) { LogManager .warning( "[AutoBone] Max headset height detected (Value seems too low, did you not stand up straight while measuring?): $hmdHeight" @@ -346,7 +245,7 @@ class AutoBone(server: VRServer) { fun processFrames( frames: PoseFrames, config: AutoBoneConfig = globalConfig, - epochCallback: Consumer? = null, + epochCallback: Consumer? = null, ): AutoBoneResults { // Load current values for adjustable configs loadConfigValues() @@ -370,7 +269,6 @@ class AutoBone(server: VRServer) { targetHmdHeight = targetHmdHeight, targetFullHeight = targetFullHeight, frames = frames, - intermediateOffsets = EnumMap(offsets), epochCallback = epochCallback, serverConfig = server.configManager ) @@ -387,7 +285,7 @@ class AutoBone(server: VRServer) { internalEpoch(trainingStep) } - val finalHeight = sumSelectConfigs(heightOffsets, offsets) + val finalHeight = calcHeight() LogManager .info( "[AutoBone] Target height: ${trainingStep.targetHmdHeight}, New height: $finalHeight" @@ -397,7 +295,7 @@ class AutoBone(server: VRServer) { finalHeight, trainingStep.targetHmdHeight, trainingStep.errorStats, - legacyConfigs + offsets ) } @@ -475,20 +373,16 @@ class AutoBone(server: VRServer) { ) } - // Convert current adjusted config to the legacy config format for the epoch - // callback, then call it - applyConfig(legacyConfigs) - trainingStep.epochCallback?.accept(Epoch(epoch + 1, config.numEpochs, errorStats, legacyConfigs)) + trainingStep.epochCallback?.accept(Epoch(epoch + 1, config.numEpochs, errorStats, offsets)) } private fun internalIter(trainingStep: AutoBoneStep) { // Pull frequently used variables out of trainingStep to reduce call length val skeleton1 = trainingStep.skeleton1 val skeleton2 = trainingStep.skeleton2 - val intermediateOffsets = trainingStep.intermediateOffsets val totalLength = getLengthSum(offsets) - val curHeight = sumSelectConfigs(heightOffsets, offsets) + val curHeight = calcHeight() trainingStep.currentHmdHeight = curHeight val errorDeriv = getErrorDeriv(trainingStep) @@ -528,13 +422,10 @@ class AutoBone(server: VRServer) { .getComputedTracker(TrackerRole.RIGHT_FOOT).position - skeleton1.getComputedTracker(TrackerRole.RIGHT_FOOT).position - // Load the current offsets into the working offsets holder - intermediateOffsets.putAll(offsets) - for (entry in offsets.entries) { // Skip adjustment if the epoch is before starting (for - // logging only) - if (trainingStep.curEpoch < 0) { + // logging only) or if there are no BoneTypes for this value + if (trainingStep.curEpoch < 0 || entry.key.affectedOffsets.isEmpty()) { break } val originalLength = entry.value @@ -571,9 +462,8 @@ class AutoBone(server: VRServer) { } // Apply new offset length - intermediateOffsets[entry.key] = newLength - applyConfig(skeleton1, intermediateOffsets) - applyConfig(skeleton2, intermediateOffsets) + skeleton1.setOffset(entry.key, newLength) + skeleton2.setOffset(entry.key, newLength) // Update the skeleton poses for the new offset length skeleton1.update() @@ -587,9 +477,8 @@ class AutoBone(server: VRServer) { // Reset the length to minimize bias in other variables, // it's applied later - intermediateOffsets[entry.key] = originalLength - applyConfig(skeleton1, intermediateOffsets) - applyConfig(skeleton2, intermediateOffsets) + skeleton1.setOffset(entry.key, originalLength) + skeleton2.setOffset(entry.key, originalLength) } if (trainingStep.config.scaleEachStep) { @@ -599,12 +488,15 @@ class AutoBone(server: VRServer) { } private fun scaleToTargetHeight(trainingStep: AutoBoneStep) { - val stepHeight = sumSelectConfigs(heightOffsets, offsets) + // Recalculate the height and update it in the AutoBoneStep + val stepHeight = calcHeight() + trainingStep.currentHmdHeight = stepHeight + if (stepHeight > 0f) { val stepHeightDiff = trainingStep.targetHmdHeight - stepHeight for (entry in offsets.entries) { // Only height variables - if (entry.key == BoneType.NECK || + if (entry.key == SkeletonConfigOffsets.NECK || !heightOffsets.contains(entry.key) ) { continue @@ -671,12 +563,12 @@ class AutoBone(server: VRServer) { val lengthsString: String get() { val configInfo = StringBuilder() - offsets.forEach { (key: BoneType, value: Float) -> + offsets.forEach { (key: SkeletonConfigOffsets, value: Float) -> if (configInfo.isNotEmpty()) { configInfo.append(", ") } configInfo - .append(key.toString()) + .append(key.configKey) .append(": ") .append(StringUtils.prettyNumber(value * 100f, 2)) } diff --git a/server/core/src/main/java/dev/slimevr/autobone/AutoBoneHandler.kt b/server/core/src/main/java/dev/slimevr/autobone/AutoBoneHandler.kt index 670c2329f..0b21c91a9 100644 --- a/server/core/src/main/java/dev/slimevr/autobone/AutoBoneHandler.kt +++ b/server/core/src/main/java/dev/slimevr/autobone/AutoBoneHandler.kt @@ -3,7 +3,6 @@ package dev.slimevr.autobone import dev.slimevr.VRServer import dev.slimevr.autobone.AutoBone.AutoBoneResults import dev.slimevr.autobone.AutoBone.Companion.loadDir -import dev.slimevr.autobone.AutoBone.Epoch import dev.slimevr.autobone.errors.AutoBoneException import dev.slimevr.poseframeformat.PoseFrames import dev.slimevr.poseframeformat.PoseRecorder @@ -16,7 +15,6 @@ import io.eiren.util.logging.LogManager import org.apache.commons.lang3.tuple.Pair import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.locks.ReentrantLock -import java.util.function.Consumer import kotlin.concurrent.thread import kotlin.concurrent.withLock @@ -38,44 +36,29 @@ class AutoBoneHandler(private val server: VRServer) { } fun removeListener(listener: AutoBoneListener) { - listeners.removeIf { l: AutoBoneListener -> listener === l } + listeners.removeIf { listener == it } } private fun announceProcessStatus( processType: AutoBoneProcessType, - message: String?, - current: Long, - total: Long, - completed: Boolean, - success: Boolean, - ) { - listeners - .forEach( - Consumer { listener: AutoBoneListener -> - listener - .onAutoBoneProcessStatus( - processType, - message, - current, - total, - completed, - success - ) - } - ) - } - - private fun announceProcessStatus( - processType: AutoBoneProcessType, - message: String, + message: String? = null, + current: Long = -1L, + total: Long = -1L, + eta: Float = -1f, completed: Boolean = false, success: Boolean = true, ) { - announceProcessStatus(processType, message, 0, 0, completed, success) - } - - private fun announceProcessStatus(processType: AutoBoneProcessType, current: Long, total: Long) { - announceProcessStatus(processType, null, current, total, completed = false, success = true) + listeners.forEach { + it.onAutoBoneProcessStatus( + processType, + message, + current, + total, + eta, + completed, + success + ) + } } val lengthsString: String @@ -84,17 +67,16 @@ class AutoBoneHandler(private val server: VRServer) { @Throws(AutoBoneException::class) private fun processFrames(frames: PoseFrames): AutoBoneResults { return autoBone - .processFrames( - frames - ) { epoch: Epoch? -> listeners.forEach(Consumer { listener: AutoBoneListener -> listener.onAutoBoneEpoch(epoch!!) }) } + .processFrames(frames) { epoch -> + listeners.forEach { listener -> listener.onAutoBoneEpoch(epoch) } + } } - fun startProcessByType(processType: AutoBoneProcessType): Boolean { + fun startProcessByType(processType: AutoBoneProcessType?): Boolean { when (processType) { AutoBoneProcessType.RECORD -> startRecording() AutoBoneProcessType.SAVE -> saveRecording() AutoBoneProcessType.PROCESS -> processRecording() - AutoBoneProcessType.APPLY -> applyValues() else -> { return false } @@ -117,9 +99,12 @@ class AutoBoneHandler(private val server: VRServer) { if (poseRecorder.isReadyToRecord) { announceProcessStatus(AutoBoneProcessType.RECORD, "Recording...") - // 1000 samples at 20 ms per sample is 20 seconds + // ex. 1000 samples at 20 ms per sample is 20 seconds val sampleCount = autoBone.globalConfig.sampleCount val sampleRate = autoBone.globalConfig.sampleRateMs + // Calculate total time in seconds + val totalTime: Float = (sampleCount * sampleRate) / 1000f + val framesFuture = poseRecorder .startFrameRecording( sampleCount, @@ -127,9 +112,9 @@ class AutoBoneHandler(private val server: VRServer) { ) { progress: RecordingProgress -> announceProcessStatus( AutoBoneProcessType.RECORD, - progress.frame.toLong(), - progress.totalFrames - .toLong() + current = progress.frame.toLong(), + total = progress.totalFrames.toLong(), + eta = totalTime - (progress.frame * totalTime / progress.totalFrames) ) } val frames = framesFuture.get() @@ -145,7 +130,7 @@ class AutoBoneHandler(private val server: VRServer) { ) autoBone.saveRecording(frames) } - listeners.forEach(Consumer { listener: AutoBoneListener -> listener.onAutoBoneRecordingEnd(frames) }) + listeners.forEach { listener: AutoBoneListener -> listener.onAutoBoneRecordingEnd(frames) } announceProcessStatus( AutoBoneProcessType.RECORD, "Done recording!", @@ -175,6 +160,18 @@ class AutoBoneHandler(private val server: VRServer) { } } + fun stopRecording() { + if (poseRecorder.isRecording) { + poseRecorder.stopFrameRecording() + } + } + + fun cancelRecording() { + if (poseRecorder.isRecording) { + poseRecorder.cancelFrameRecording() + } + } + fun saveRecording() { saveRecordingLock.withLock { // Prevent running multiple times @@ -373,7 +370,7 @@ class AutoBoneHandler(private val server: VRServer) { ")" ) // #endregion - listeners.forEach(Consumer { listener: AutoBoneListener -> listener.onAutoBoneEnd(autoBone.legacyConfigs) }) + listeners.forEach { listener: AutoBoneListener -> listener.onAutoBoneEnd(autoBone.offsets) } announceProcessStatus( AutoBoneProcessType.PROCESS, "Done processing!", @@ -395,11 +392,5 @@ class AutoBoneHandler(private val server: VRServer) { fun applyValues() { autoBone.applyAndSaveConfig() - announceProcessStatus( - AutoBoneProcessType.APPLY, - "Adjusted values applied!", - completed = true, - success = true - ) } } diff --git a/server/core/src/main/java/dev/slimevr/autobone/AutoBoneListener.kt b/server/core/src/main/java/dev/slimevr/autobone/AutoBoneListener.kt index 0a2d49db0..5101167db 100644 --- a/server/core/src/main/java/dev/slimevr/autobone/AutoBoneListener.kt +++ b/server/core/src/main/java/dev/slimevr/autobone/AutoBoneListener.kt @@ -3,7 +3,7 @@ package dev.slimevr.autobone import dev.slimevr.autobone.AutoBone.Epoch import dev.slimevr.poseframeformat.PoseFrames import dev.slimevr.tracking.processor.config.SkeletonConfigOffsets -import java.util.EnumMap +import java.util.* interface AutoBoneListener { fun onAutoBoneProcessStatus( @@ -11,6 +11,7 @@ interface AutoBoneListener { message: String?, current: Long, total: Long, + eta: Float, completed: Boolean, success: Boolean, ) diff --git a/server/core/src/main/java/dev/slimevr/autobone/AutoBoneProcessType.kt b/server/core/src/main/java/dev/slimevr/autobone/AutoBoneProcessType.kt index 7736a2429..b661abc43 100644 --- a/server/core/src/main/java/dev/slimevr/autobone/AutoBoneProcessType.kt +++ b/server/core/src/main/java/dev/slimevr/autobone/AutoBoneProcessType.kt @@ -1,7 +1,7 @@ package dev.slimevr.autobone enum class AutoBoneProcessType(val id: Int) { - NONE(0), RECORD(1), SAVE(2), PROCESS(3), APPLY(4); + NONE(0), RECORD(1), SAVE(2), PROCESS(3); companion object { fun getById(id: Int): AutoBoneProcessType? = byId[id] diff --git a/server/core/src/main/java/dev/slimevr/autobone/AutoBoneStep.kt b/server/core/src/main/java/dev/slimevr/autobone/AutoBoneStep.kt index 19b4a3c72..1ab792d9a 100644 --- a/server/core/src/main/java/dev/slimevr/autobone/AutoBoneStep.kt +++ b/server/core/src/main/java/dev/slimevr/autobone/AutoBoneStep.kt @@ -4,9 +4,7 @@ import dev.slimevr.config.AutoBoneConfig import dev.slimevr.config.ConfigManager import dev.slimevr.poseframeformat.PoseFrames import dev.slimevr.poseframeformat.player.TrackerFramesPlayer -import dev.slimevr.tracking.processor.BoneType import dev.slimevr.tracking.processor.HumanPoseManager -import java.util.* import java.util.function.Consumer class AutoBoneStep( @@ -14,8 +12,7 @@ class AutoBoneStep( val targetHmdHeight: Float, val targetFullHeight: Float, val frames: PoseFrames, - val intermediateOffsets: EnumMap, - val epochCallback: Consumer?, + val epochCallback: Consumer?, serverConfig: ConfigManager, var curEpoch: Int = 0, var curAdjustRate: Float = 0f, @@ -24,6 +21,8 @@ class AutoBoneStep( var currentHmdHeight: Float = 0f, ) { + val eyeHeightToHeightRatio: Float = targetHmdHeight / targetFullHeight + val maxFrameCount = frames.maxFrameCount val framePlayer1 = TrackerFramesPlayer(frames) diff --git a/server/core/src/main/java/dev/slimevr/autobone/errors/BodyProportionError.kt b/server/core/src/main/java/dev/slimevr/autobone/errors/BodyProportionError.kt index 923393cc7..678f36303 100644 --- a/server/core/src/main/java/dev/slimevr/autobone/errors/BodyProportionError.kt +++ b/server/core/src/main/java/dev/slimevr/autobone/errors/BodyProportionError.kt @@ -13,12 +13,11 @@ class BodyProportionError : IAutoBoneError { override fun getStepError(trainingStep: AutoBoneStep): Float { return getBodyProportionError( trainingStep.skeleton1, - trainingStep.currentHmdHeight + trainingStep.currentHmdHeight / trainingStep.eyeHeightToHeightRatio ) } - fun getBodyProportionError(humanPoseManager: HumanPoseManager, height: Float): Float { - val fullHeight = height / eyeHeightToHeightRatio + fun getBodyProportionError(humanPoseManager: HumanPoseManager, fullHeight: Float): Float { var sum = 0f for (limiter in proportionLimits) { sum += FastMath.abs(limiter.getProportionError(humanPoseManager, fullHeight)) diff --git a/server/core/src/main/java/dev/slimevr/config/AutoBoneConfig.kt b/server/core/src/main/java/dev/slimevr/config/AutoBoneConfig.kt index 731826fa9..d931f568e 100644 --- a/server/core/src/main/java/dev/slimevr/config/AutoBoneConfig.kt +++ b/server/core/src/main/java/dev/slimevr/config/AutoBoneConfig.kt @@ -20,8 +20,8 @@ class AutoBoneConfig { var targetFullHeight = -1f var randomizeFrameOrder = true var scaleEachStep = true - var sampleCount = 1000 - var sampleRateMs: Long = 20 + var sampleCount = 1500 + var sampleRateMs = 20L var saveRecordings = false var useSkeletonHeight = false var randSeed = 4L diff --git a/server/core/src/main/java/dev/slimevr/config/CurrentVRConfigConverter.java b/server/core/src/main/java/dev/slimevr/config/CurrentVRConfigConverter.java index f402619ce..69cd277f7 100644 --- a/server/core/src/main/java/dev/slimevr/config/CurrentVRConfigConverter.java +++ b/server/core/src/main/java/dev/slimevr/config/CurrentVRConfigConverter.java @@ -225,6 +225,16 @@ public class CurrentVRConfigConverter implements VersionedModelConverter { } } if (version < 9) { + // Change default AutoBone recording length from 20 to 30 + // seconds + ObjectNode autoBoneNode = (ObjectNode) modelData.get("autoBone"); + if (autoBoneNode != null) { + JsonNode sampleCountNode = autoBoneNode.get("sampleCount"); + if (sampleCountNode != null && sampleCountNode.intValue() == 1000) { + autoBoneNode.set("sampleCount", new IntNode(1500)); + } + } + // split chest into 2 offsets ObjectNode skeletonNode = (ObjectNode) modelData.get("skeleton"); if (skeletonNode != null) { diff --git a/server/core/src/main/java/dev/slimevr/protocol/datafeed/DataFeedHandler.java b/server/core/src/main/java/dev/slimevr/protocol/datafeed/DataFeedHandler.java index 7eddca9fa..61ed3abbe 100644 --- a/server/core/src/main/java/dev/slimevr/protocol/datafeed/DataFeedHandler.java +++ b/server/core/src/main/java/dev/slimevr/protocol/datafeed/DataFeedHandler.java @@ -4,6 +4,7 @@ import com.google.flatbuffers.FlatBufferBuilder; import dev.slimevr.protocol.GenericConnection; import dev.slimevr.protocol.ProtocolAPI; import dev.slimevr.protocol.ProtocolHandler; +import dev.slimevr.tracking.trackers.Tracker; import io.eiren.util.logging.LogManager; import solarxr_protocol.MessageBundle; import solarxr_protocol.data_feed.*; @@ -77,6 +78,7 @@ public class DataFeedHandler extends ProtocolHandler { this.api.server.deviceManager .getDevices() ); + // Synthetic tracker is computed tracker apparently int trackersOffset = DataFeedBuilder .createSyntheticTrackersData( fbb, @@ -84,7 +86,7 @@ public class DataFeedHandler extends ProtocolHandler { this.api.server .getAllTrackers() .stream() - .filter(tracker -> tracker.getDevice() == null) + .filter(Tracker::isComputed) .collect(Collectors.toList()) ); diff --git a/server/core/src/main/java/dev/slimevr/protocol/rpc/RPCHandler.java b/server/core/src/main/java/dev/slimevr/protocol/rpc/RPCHandler.java index 030427eb9..986582211 100644 --- a/server/core/src/main/java/dev/slimevr/protocol/rpc/RPCHandler.java +++ b/server/core/src/main/java/dev/slimevr/protocol/rpc/RPCHandler.java @@ -1,14 +1,12 @@ package dev.slimevr.protocol.rpc; import com.google.flatbuffers.FlatBufferBuilder; -import dev.slimevr.autobone.AutoBone.Epoch; -import dev.slimevr.autobone.AutoBoneListener; -import dev.slimevr.autobone.AutoBoneProcessType; +import dev.slimevr.autobone.errors.BodyProportionError; import dev.slimevr.config.OverlayConfig; -import dev.slimevr.poseframeformat.PoseFrames; import dev.slimevr.protocol.GenericConnection; import dev.slimevr.protocol.ProtocolAPI; import dev.slimevr.protocol.ProtocolHandler; +import dev.slimevr.protocol.rpc.autobone.RPCAutoBoneHandler; import dev.slimevr.protocol.rpc.reset.RPCResetHandler; import dev.slimevr.protocol.rpc.serial.RPCProvisioningHandler; import dev.slimevr.protocol.rpc.serial.RPCSerialHandler; @@ -26,13 +24,10 @@ import solarxr_protocol.MessageBundle; import solarxr_protocol.datatypes.TransactionId; import solarxr_protocol.rpc.*; -import java.util.EnumMap; -import java.util.Map.Entry; import java.util.function.BiConsumer; -public class RPCHandler extends ProtocolHandler - implements AutoBoneListener { +public class RPCHandler extends ProtocolHandler { private static final String resetSourceName = "WebSocketAPI"; @@ -50,6 +45,7 @@ public class RPCHandler extends ProtocolHandler new RPCSettingsHandler(this, api); new RPCTapSetupHandler(this, api); new RPCStatusHandler(this, api); + new RPCAutoBoneHandler(this, api); registerPacketListener(RpcMessage.ResetRequest, this::onResetRequest); registerPacketListener( @@ -72,8 +68,6 @@ public class RPCHandler extends ProtocolHandler this::onChangeSkeletonConfigRequest ); - registerPacketListener(RpcMessage.AutoBoneProcessRequest, this::onAutoBoneProcessRequest); - registerPacketListener( RpcMessage.OverlayDisplayModeChangeRequest, this::onOverlayDisplayModeChangeRequest @@ -93,7 +87,7 @@ public class RPCHandler extends ProtocolHandler registerPacketListener(RpcMessage.SetPauseTrackingRequest, this::onSetPauseTrackingRequest); - this.api.server.autoBoneHandler.addListener(this); + registerPacketListener(RpcMessage.HeightRequest, this::onHeightRequest); } private void onServerInfosRequest( @@ -360,122 +354,6 @@ public class RPCHandler extends ProtocolHandler return RpcMessage.names.length; } - public void onAutoBoneProcessRequest(GenericConnection conn, RpcMessageHeader messageHeader) { - AutoBoneProcessRequest req = (AutoBoneProcessRequest) messageHeader - .message(new AutoBoneProcessRequest()); - if (req == null || conn.getContext().useAutoBone()) - return; - - conn.getContext().setUseAutoBone(true); - this.api.server.autoBoneHandler - .startProcessByType(AutoBoneProcessType.Companion.getById(req.processType())); - } - - @Override - public void onAutoBoneProcessStatus( - AutoBoneProcessType processType, - String message, - long current, - long total, - boolean completed, - boolean success - ) { - this.api - .getAPIServers() - .forEach( - (server) -> server - .getAPIConnections() - .filter(conn -> conn.getContext().useAutoBone()) - .forEach((conn) -> { - FlatBufferBuilder fbb = new FlatBufferBuilder(32); - - Integer messageOffset = message != null ? fbb.createString(message) : null; - - AutoBoneProcessStatusResponse.startAutoBoneProcessStatusResponse(fbb); - AutoBoneProcessStatusResponse.addProcessType(fbb, processType.getId()); - if (messageOffset != null) - AutoBoneProcessStatusResponse.addMessage(fbb, messageOffset); - if (total > 0 && current >= 0) { - AutoBoneProcessStatusResponse.addCurrent(fbb, current); - AutoBoneProcessStatusResponse.addTotal(fbb, total); - } - AutoBoneProcessStatusResponse.addCompleted(fbb, completed); - AutoBoneProcessStatusResponse.addSuccess(fbb, success); - int update = AutoBoneProcessStatusResponse - .endAutoBoneProcessStatusResponse(fbb); - int outbound = this - .createRPCMessage( - fbb, - RpcMessage.AutoBoneProcessStatusResponse, - update - ); - fbb.finish(outbound); - - conn.send(fbb.dataBuffer()); - if (completed) { - conn.getContext().setUseAutoBone(false); - } - }) - ); - } - - @Override - public void onAutoBoneRecordingEnd(PoseFrames recording) { - // Do nothing, this is broadcasted by "onAutoBoneProcessStatus" uwu - } - - @Override - public void onAutoBoneEpoch(Epoch epoch) { - this.api - .getAPIServers() - .forEach( - (server) -> server - .getAPIConnections() - .filter(conn -> conn.getContext().useAutoBone()) - .forEach((conn) -> { - FlatBufferBuilder fbb = new FlatBufferBuilder(32); - - int[] skeletonPartOffsets = new int[epoch.getConfigValues().size()]; - int i = 0; - for ( - Entry skeletonConfig : epoch - .getConfigValues() - .entrySet() - ) { - skeletonPartOffsets[i++] = SkeletonPart - .createSkeletonPart( - fbb, - skeletonConfig.getKey().id, - skeletonConfig.getValue() - ); - } - - int skeletonPartsOffset = AutoBoneEpochResponse - .createAdjustedSkeletonPartsVector(fbb, skeletonPartOffsets); - - int update = AutoBoneEpochResponse - .createAutoBoneEpochResponse( - fbb, - epoch.getEpoch(), - epoch.getTotalEpochs(), - epoch.getEpochError().getMean(), - skeletonPartsOffset - ); - int outbound = this - .createRPCMessage(fbb, RpcMessage.AutoBoneEpochResponse, update); - fbb.finish(outbound); - - conn.send(fbb.dataBuffer()); - }) - ); - } - - @Override - public void onAutoBoneEnd(EnumMap configValues) { - // Do nothing, the last epoch from "onAutoBoneEpoch" should be all - // that's needed - } - public void onStatusSystemRequest(GenericConnection conn, RpcMessageHeader messageHeader) { StatusSystemRequest req = (StatusSystemRequest) messageHeader .message(new StatusSystemRequest()); @@ -503,4 +381,18 @@ public class RPCHandler extends ProtocolHandler this.api.server.humanPoseManager.setPauseTracking(req.pauseTracking()); } + + public void onHeightRequest(GenericConnection conn, RpcMessageHeader messageHeader) { + FlatBufferBuilder fbb = new FlatBufferBuilder(32); + + float hmdHeight = this.api.server.humanPoseManager.getHmdHeight(); + int response = HeightResponse + .createHeightResponse( + fbb, + hmdHeight, + hmdHeight / BodyProportionError.eyeHeightToHeightRatio + ); + fbb.finish(createRPCMessage(fbb, RpcMessage.HeightResponse, response)); + conn.send(fbb.dataBuffer()); + } } diff --git a/server/core/src/main/java/dev/slimevr/protocol/rpc/autobone/RPCAutoBoneHandler.kt b/server/core/src/main/java/dev/slimevr/protocol/rpc/autobone/RPCAutoBoneHandler.kt new file mode 100644 index 000000000..7baad46b8 --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/protocol/rpc/autobone/RPCAutoBoneHandler.kt @@ -0,0 +1,181 @@ +package dev.slimevr.protocol.rpc.autobone + +import com.google.flatbuffers.FlatBufferBuilder +import dev.slimevr.autobone.AutoBone.Epoch +import dev.slimevr.autobone.AutoBoneListener +import dev.slimevr.autobone.AutoBoneProcessType +import dev.slimevr.autobone.AutoBoneProcessType.Companion.getById +import dev.slimevr.poseframeformat.PoseFrames +import dev.slimevr.protocol.GenericConnection +import dev.slimevr.protocol.ProtocolAPI +import dev.slimevr.protocol.rpc.RPCBuilder +import dev.slimevr.protocol.rpc.RPCHandler +import dev.slimevr.tracking.processor.config.SkeletonConfigOffsets +import solarxr_protocol.rpc.AutoBoneEpochResponse +import solarxr_protocol.rpc.AutoBoneProcessRequest +import solarxr_protocol.rpc.AutoBoneProcessStatusResponse +import solarxr_protocol.rpc.RpcMessage +import solarxr_protocol.rpc.RpcMessageHeader +import solarxr_protocol.rpc.SkeletonPart +import java.util.* + +class RPCAutoBoneHandler( + private val rpcHandler: RPCHandler, + val api: ProtocolAPI, +) : AutoBoneListener { + init { + rpcHandler.registerPacketListener( + RpcMessage.AutoBoneProcessRequest, + ::onAutoBoneProcessRequest + ) + rpcHandler.registerPacketListener( + RpcMessage.AutoBoneApplyRequest, + ::onAutoBoneApplyRequest + ) + rpcHandler.registerPacketListener( + RpcMessage.AutoBoneStopRecordingRequest, + ::onAutoBoneStopRecordingRequest + ) + rpcHandler.registerPacketListener( + RpcMessage.AutoBoneCancelRecordingRequest, + ::onAutoBoneCancelRecordingRequest + ) + + this.api.server.autoBoneHandler.addListener(this) + } + + fun onAutoBoneProcessRequest( + conn: GenericConnection, + messageHeader: RpcMessageHeader, + ) { + val req = messageHeader + .message(AutoBoneProcessRequest()) as AutoBoneProcessRequest + if (conn.context.useAutoBone()) return + conn.context.setUseAutoBone(true) + api.server + .autoBoneHandler + .startProcessByType(getById(req.processType())) + } + + override fun onAutoBoneProcessStatus( + processType: AutoBoneProcessType, + message: String?, + current: Long, + total: Long, + eta: Float, + completed: Boolean, + success: Boolean, + ) { + forAllListeners { conn -> + if (!conn.context.useAutoBone()) { + return@forAllListeners + } + + val fbb = FlatBufferBuilder(32) + + AutoBoneProcessStatusResponse.startAutoBoneProcessStatusResponse(fbb) + AutoBoneProcessStatusResponse.addProcessType( + fbb, + processType.id + ) + + AutoBoneProcessStatusResponse.addCurrent(fbb, current) + AutoBoneProcessStatusResponse.addTotal(fbb, total) + AutoBoneProcessStatusResponse.addEta(fbb, eta) + AutoBoneProcessStatusResponse.addCompleted(fbb, completed) + AutoBoneProcessStatusResponse.addSuccess(fbb, success) + + val update = AutoBoneProcessStatusResponse + .endAutoBoneProcessStatusResponse(fbb) + val outbound: Int = rpcHandler.createRPCMessage( + fbb, + RpcMessage.AutoBoneProcessStatusResponse, + update + ) + fbb.finish(outbound) + conn.send(fbb.dataBuffer()) + if (completed) { + conn.context.setUseAutoBone(false) + } + } + } + + override fun onAutoBoneRecordingEnd(recording: PoseFrames) { + // Do nothing, this is broadcasted by "onAutoBoneProcessStatus" uwu + } + + override fun onAutoBoneEpoch(epoch: Epoch) { + forAllListeners { conn -> + if (!conn.context.useAutoBone()) { + return@forAllListeners + } + + val fbb = FlatBufferBuilder(32) + + val skeletonPartsOffset = AutoBoneEpochResponse + .createAdjustedSkeletonPartsVector( + fbb, + epoch.configValues.map { (key, value) -> + SkeletonPart.createSkeletonPart(fbb, key.id, value) + }.toIntArray() + ) + val update = AutoBoneEpochResponse + .createAutoBoneEpochResponse( + fbb, + epoch.epoch.toLong(), + epoch.totalEpochs.toLong(), + epoch.epochError.mean, + skeletonPartsOffset + ) + val outbound: Int = rpcHandler.createRPCMessage( + fbb, + RpcMessage.AutoBoneEpochResponse, + update + ) + fbb.finish(outbound) + conn.send(fbb.dataBuffer()) + } + } + + override fun onAutoBoneEnd(configValues: EnumMap) { + // Do nothing, the last epoch from "onAutoBoneEpoch" should be all + // that's needed + } + + private fun onAutoBoneApplyRequest( + conn: GenericConnection, + messageHeader: RpcMessageHeader, + ) { + api.server.autoBoneHandler.applyValues() + + // Send the new body proportions, this is to update the listener's state + val fbb = FlatBufferBuilder(300) + val outbound = rpcHandler.createRPCMessage( + fbb, + RpcMessage.SkeletonConfigResponse, + RPCBuilder.createSkeletonConfig(fbb, api.server.humanPoseManager) + ) + fbb.finish(outbound) + conn.send(fbb.dataBuffer()) + } + + private fun onAutoBoneStopRecordingRequest( + conn: GenericConnection, + messageHeader: RpcMessageHeader, + ) { + api.server.autoBoneHandler.stopRecording() + } + + private fun onAutoBoneCancelRecordingRequest( + conn: GenericConnection, + messageHeader: RpcMessageHeader, + ) { + api.server.autoBoneHandler.cancelRecording() + } + + private fun forAllListeners(action: (GenericConnection) -> Unit) { + api.apiServers.forEach { + it.apiConnections.forEach(action) + } + } +} diff --git a/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsBuilder.java b/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsBuilder.java index b634b701e..8726e3067 100644 --- a/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsBuilder.java +++ b/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsBuilder.java @@ -204,4 +204,126 @@ public class RPCSettingsBuilder { ); return ModelSettings.createModelSettings(fbb, togglesOffset, ratiosOffset, legTweaksOffset); } + + public static int createAutoBoneSettings(FlatBufferBuilder fbb, AutoBoneConfig autoBoneConfig) { + return AutoBoneSettings + .createAutoBoneSettings( + fbb, + autoBoneConfig.getCursorIncrement(), + autoBoneConfig.getMinDataDistance(), + autoBoneConfig.getMaxDataDistance(), + autoBoneConfig.getNumEpochs(), + autoBoneConfig.getPrintEveryNumEpochs(), + autoBoneConfig.getInitialAdjustRate(), + autoBoneConfig.getAdjustRateDecay(), + autoBoneConfig.getSlideErrorFactor(), + autoBoneConfig.getOffsetSlideErrorFactor(), + autoBoneConfig.getFootHeightOffsetErrorFactor(), + autoBoneConfig.getBodyProportionErrorFactor(), + autoBoneConfig.getHeightErrorFactor(), + autoBoneConfig.getPositionErrorFactor(), + autoBoneConfig.getPositionOffsetErrorFactor(), + autoBoneConfig.getCalcInitError(), + autoBoneConfig.getTargetHmdHeight(), + autoBoneConfig.getTargetFullHeight(), + autoBoneConfig.getRandomizeFrameOrder(), + autoBoneConfig.getScaleEachStep(), + autoBoneConfig.getSampleCount(), + autoBoneConfig.getSampleRateMs(), + autoBoneConfig.getSaveRecordings(), + autoBoneConfig.getUseSkeletonHeight(), + autoBoneConfig.getRandSeed() + ); + } + + + /** + * Writes values from AutoBoneSettings to an AutoBoneConfig. + * + * @param autoBoneSettings The settings to read from. + * @param autoBoneConfig The config to write to. + * @return The autoBoneConfig parameter. + */ + public static AutoBoneConfig readAutoBoneSettings( + AutoBoneSettings autoBoneSettings, + AutoBoneConfig autoBoneConfig + ) { + if (autoBoneSettings.hasCursorIncrement()) { + autoBoneConfig.setCursorIncrement(autoBoneSettings.cursorIncrement()); + } + if (autoBoneSettings.hasMinDataDistance()) { + autoBoneConfig.setMinDataDistance(autoBoneSettings.minDataDistance()); + } + if (autoBoneSettings.hasMaxDataDistance()) { + autoBoneConfig.setMaxDataDistance(autoBoneSettings.maxDataDistance()); + } + if (autoBoneSettings.hasNumEpochs()) { + autoBoneConfig.setNumEpochs(autoBoneSettings.numEpochs()); + } + if (autoBoneSettings.hasPrintEveryNumEpochs()) { + autoBoneConfig.setPrintEveryNumEpochs(autoBoneSettings.printEveryNumEpochs()); + } + if (autoBoneSettings.hasInitialAdjustRate()) { + autoBoneConfig.setInitialAdjustRate(autoBoneSettings.initialAdjustRate()); + } + if (autoBoneSettings.hasAdjustRateDecay()) { + autoBoneConfig.setAdjustRateDecay(autoBoneSettings.adjustRateDecay()); + } + if (autoBoneSettings.hasSlideErrorFactor()) { + autoBoneConfig.setSlideErrorFactor(autoBoneSettings.slideErrorFactor()); + } + if (autoBoneSettings.hasOffsetSlideErrorFactor()) { + autoBoneConfig.setOffsetSlideErrorFactor(autoBoneSettings.offsetSlideErrorFactor()); + } + if (autoBoneSettings.hasFootHeightOffsetErrorFactor()) { + autoBoneConfig + .setFootHeightOffsetErrorFactor(autoBoneSettings.footHeightOffsetErrorFactor()); + } + if (autoBoneSettings.hasBodyProportionErrorFactor()) { + autoBoneConfig + .setBodyProportionErrorFactor(autoBoneSettings.bodyProportionErrorFactor()); + } + if (autoBoneSettings.hasHeightErrorFactor()) { + autoBoneConfig.setHeightErrorFactor(autoBoneSettings.heightErrorFactor()); + } + if (autoBoneSettings.hasPositionErrorFactor()) { + autoBoneConfig.setPositionErrorFactor(autoBoneSettings.positionErrorFactor()); + } + if (autoBoneSettings.hasPositionOffsetErrorFactor()) { + autoBoneConfig + .setPositionOffsetErrorFactor(autoBoneSettings.positionOffsetErrorFactor()); + } + if (autoBoneSettings.hasCalcInitError()) { + autoBoneConfig.setCalcInitError(autoBoneSettings.calcInitError()); + } + if (autoBoneSettings.hasTargetHmdHeight()) { + autoBoneConfig.setTargetHmdHeight(autoBoneSettings.targetHmdHeight()); + } + if (autoBoneSettings.hasTargetFullHeight()) { + autoBoneConfig.setTargetFullHeight(autoBoneSettings.targetFullHeight()); + } + if (autoBoneSettings.hasRandomizeFrameOrder()) { + autoBoneConfig.setRandomizeFrameOrder(autoBoneSettings.randomizeFrameOrder()); + } + if (autoBoneSettings.hasScaleEachStep()) { + autoBoneConfig.setScaleEachStep(autoBoneSettings.scaleEachStep()); + } + if (autoBoneSettings.hasSampleCount()) { + autoBoneConfig.setSampleCount(autoBoneSettings.sampleCount()); + } + if (autoBoneSettings.hasSampleRateMs()) { + autoBoneConfig.setSampleRateMs(autoBoneSettings.sampleRateMs()); + } + if (autoBoneSettings.hasSaveRecordings()) { + autoBoneConfig.setSaveRecordings(autoBoneSettings.saveRecordings()); + } + if (autoBoneSettings.hasUseSkeletonHeight()) { + autoBoneConfig.setUseSkeletonHeight(autoBoneSettings.useSkeletonHeight()); + } + if (autoBoneSettings.hasRandSeed()) { + autoBoneConfig.setRandSeed(autoBoneSettings.randSeed()); + } + + return autoBoneConfig; + } } diff --git a/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsHandler.java b/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsHandler.java index f01cfa754..7946fc774 100644 --- a/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsHandler.java +++ b/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsHandler.java @@ -80,6 +80,11 @@ public class RPCSettingsHandler { .createTapDetectionSettings( fbb, this.api.server.configManager.getVrConfig().getTapDetection() + ), + RPCSettingsBuilder + .createAutoBoneSettings( + fbb, + this.api.server.configManager.getVrConfig().getAutoBone() ) ); int outbound = rpcHandler.createRPCMessage(fbb, RpcMessage.SettingsResponse, settings); @@ -358,6 +363,15 @@ public class RPCSettingsHandler { } + var autoBoneSettings = req.autoBoneSettings(); + if (autoBoneSettings != null) { + AutoBoneConfig autoBoneConfig = this.api.server.configManager + .getVrConfig() + .getAutoBone(); + + RPCSettingsBuilder.readAutoBoneSettings(autoBoneSettings, autoBoneConfig); + } + this.api.server.configManager.saveConfig(); } diff --git a/server/core/src/main/java/dev/slimevr/tracking/trackers/Tracker.kt b/server/core/src/main/java/dev/slimevr/tracking/trackers/Tracker.kt index 27c7d5b66..ba56e16f4 100644 --- a/server/core/src/main/java/dev/slimevr/tracking/trackers/Tracker.kt +++ b/server/core/src/main/java/dev/slimevr/tracking/trackers/Tracker.kt @@ -23,9 +23,18 @@ const val TIMEOUT_MS = 2000L */ class Tracker @JvmOverloads constructor( val device: Device?, - val id: Int, // VRServer.nextLocalTrackerId - val name: String, // unique, for config - val displayName: String = "Tracker #$id", // default display GUI name + /** + * VRServer.nextLocalTrackerId + */ + val id: Int, + /** + * unique, for config + */ + val name: String, + /** + * default display GUI name + */ + val displayName: String = "Tracker #$id", trackerPosition: TrackerPosition?, /** * It's like the ID, but it should be local to the device if it has one @@ -34,11 +43,23 @@ class Tracker @JvmOverloads constructor( val hasPosition: Boolean = false, val hasRotation: Boolean = false, val hasAcceleration: Boolean = false, - val userEditable: Boolean = false, // User can change TrackerPosition, mounting... - val isInternal: Boolean = false, // Is used within SlimeVR (shareable trackers) - val isComputed: Boolean = false, // Has solved position + rotation (Vive trackers) + /** + * User can change TrackerPosition, mounting... + */ + val userEditable: Boolean = false, + /** + * Is used within SlimeVR (shareable trackers) + */ + val isInternal: Boolean = false, + /** + * Has solved position + rotation (Vive trackers) + */ + val isComputed: Boolean = false, val imuType: IMUType? = null, - val usesTimeout: Boolean = false, // Automatically set the status to DISCONNECTED + /** + * Automatically set the status to DISCONNECTED + */ + val usesTimeout: Boolean = false, val allowFiltering: Boolean = false, val needsReset: Boolean = false, val needsMounting: Boolean = false, diff --git a/solarxr-protocol b/solarxr-protocol index e0718504e..c22d4729e 160000 --- a/solarxr-protocol +++ b/solarxr-protocol @@ -1 +1 @@ -Subproject commit e0718504e702b025d50e5389802a0bc8d8cdc59d +Subproject commit c22d4729ec45a9d4bd357458cfa4cc5cc65968ab