Skip to content

Commit a4e70f5

Browse files
Merge dev
* 'dev' of github.com:Autodesk/synthesis: fix: prevent custom configs from showing as defaults feat: clean up match mode config parsing
2 parents c107d83 + dd961a3 commit a4e70f5

File tree

5 files changed

+61
-116
lines changed

5 files changed

+61
-116
lines changed

fission/src/systems/match_mode/DefaultMatchModeConfigs.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,22 @@ class DefaultMatchModeConfigs {
6767
}
6868
}
6969

70+
static fallbackValues = (): MatchModeConfig => {
71+
return {
72+
id: "default",
73+
name: "Default",
74+
isDefault: true,
75+
autonomousTime: 15,
76+
teleopTime: 135,
77+
endgameTime: 20,
78+
ignoreRotation: true,
79+
maxHeight: Infinity,
80+
heightLimitPenalty: 2,
81+
sideMaxExtension: Infinity,
82+
sideExtensionPenalty: 2,
83+
}
84+
}
85+
7086
/** @returns {MatchModeConfig[]} New copies of the default match mode configs without reference to any others. */
7187
public static get defaultMatchModeConfigCopies(): MatchModeConfig[] {
7288
return [

fission/src/systems/match_mode/MatchMode.ts

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,13 @@ import beep from "@/assets/sound-files/beep.wav"
22
import MatchEnd from "@/assets/sound-files/MatchEnd.wav"
33
import MatchResume from "@/assets/sound-files/MatchResume.wav"
44
import MatchStart from "@/assets/sound-files/MatchStart.wav"
5-
import { globalOpenModal } from "@/ui/components/GlobalUIControls"
6-
import MatchResultsModal from "@/ui/modals/MatchResultsModal"
5+
import { globalOpenModal } from "@/components/GlobalUIControls.ts"
6+
import MatchResultsModal from "@/modals/MatchResultsModal.tsx"
7+
import DefaultMatchModeConfigs from "@/systems/match_mode/DefaultMatchModeConfigs.ts"
78
import type { MatchModeConfig } from "@/ui/panels/configuring/MatchModeConfigPanel"
89
import SimulationSystem from "../simulation/SimulationSystem"
910
import { SoundPlayer } from "../sound/SoundPlayer"
10-
import {
11-
DEFAULT_AUTONOMOUS_TIME,
12-
DEFAULT_ENDGAME_TIME,
13-
DEFAULT_HEIGHT_LIMIT_PENALTY,
14-
DEFAULT_IGNORE_ROTATION,
15-
DEFAULT_MAX_HEIGHT,
16-
DEFAULT_SIDE_EXTENSION_PENALTY,
17-
DEFAULT_SIDE_MAX_EXTENSION,
18-
DEFAULT_TELEOP_TIME,
19-
MatchModeType,
20-
} from "./MatchModeTypes"
11+
import { MatchModeType } from "./MatchModeTypes"
2112
import RobotDimensionTracker from "./RobotDimensionTracker"
2213

2314
class MatchMode {
@@ -35,19 +26,7 @@ class MatchMode {
3526
private _intervalId: number | null = null
3627

3728
// Match Mode Config
38-
private _matchModeConfig: MatchModeConfig = {
39-
id: "default",
40-
name: "Default",
41-
isDefault: true,
42-
autonomousTime: DEFAULT_AUTONOMOUS_TIME,
43-
teleopTime: DEFAULT_TELEOP_TIME,
44-
endgameTime: DEFAULT_ENDGAME_TIME,
45-
ignoreRotation: DEFAULT_IGNORE_ROTATION,
46-
maxHeight: DEFAULT_MAX_HEIGHT,
47-
heightLimitPenalty: DEFAULT_HEIGHT_LIMIT_PENALTY,
48-
sideExtensionPenalty: DEFAULT_SIDE_EXTENSION_PENALTY,
49-
sideMaxExtension: DEFAULT_SIDE_MAX_EXTENSION,
50-
}
29+
private _matchModeConfig: MatchModeConfig = DefaultMatchModeConfigs.fallbackValues()
5130

5231
private constructor() {}
5332

fission/src/systems/match_mode/MatchModeTypes.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,3 @@ export enum MatchModeType {
55
ENDGAME = "Endgame",
66
MATCH_ENDED = "Match Ended",
77
}
8-
9-
// Default match mode timing values
10-
export const DEFAULT_AUTONOMOUS_TIME = 15
11-
export const DEFAULT_TELEOP_TIME = 135
12-
export const DEFAULT_ENDGAME_TIME = 20
13-
export const DEFAULT_IGNORE_ROTATION = true
14-
export const DEFAULT_MAX_HEIGHT = Infinity
15-
export const DEFAULT_HEIGHT_LIMIT_PENALTY = 2
16-
export const DEFAULT_SIDE_MAX_EXTENSION = Infinity
17-
export const DEFAULT_SIDE_EXTENSION_PENALTY = 2

fission/src/test/RobotDimensionsTracker.test.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { MiraType } from "@/mirabuf/MirabufLoader"
33
import MirabufSceneObject from "@/mirabuf/MirabufSceneObject"
44
import RobotDimensionTracker from "@/systems/match_mode/RobotDimensionTracker"
55
import SimulationSystem from "@/systems/simulation/SimulationSystem"
6-
import World from "@/systems/World.ts"
6+
import World from "@/systems/World"
77

88
interface MockDimensions {
99
width: number
@@ -48,14 +48,6 @@ vi.mock("@/systems/match_mode/MatchMode", () => ({
4848
TELEOP: 2,
4949
MATCH_ENDED: 3,
5050
},
51-
DEFAULT_AUTONOMOUS_TIME: 15,
52-
DEFAULT_TELEOP_TIME: 135,
53-
DEFAULT_ENDGAME_TIME: 20,
54-
DEFAULT_IGNORE_ROTATION: true,
55-
DEFAULT_MAX_HEIGHT: Infinity,
56-
DEFAULT_HEIGHT_LIMIT_PENALTY: 2,
57-
DEFAULT_SIDE_MAX_EXTENSION: 1.5,
58-
DEFAULT_SIDE_EXTENSION_PENALTY: 2,
5951
}))
6052

6153
vi.mock("@/systems/simulation/SimulationSystem", () => ({

fission/src/ui/panels/configuring/MatchModeConfigPanel.tsx

Lines changed: 39 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,12 @@ import type React from "react"
44
import { type ChangeEvent, useEffect, useMemo, useRef, useState } from "react"
55
import DefaultMatchModeConfigs from "@/systems/match_mode/DefaultMatchModeConfigs"
66
import MatchMode from "@/systems/match_mode/MatchMode"
7-
import {
8-
DEFAULT_AUTONOMOUS_TIME,
9-
DEFAULT_ENDGAME_TIME,
10-
DEFAULT_HEIGHT_LIMIT_PENALTY,
11-
DEFAULT_IGNORE_ROTATION,
12-
DEFAULT_MAX_HEIGHT,
13-
DEFAULT_SIDE_EXTENSION_PENALTY,
14-
DEFAULT_SIDE_MAX_EXTENSION,
15-
DEFAULT_TELEOP_TIME,
16-
} from "@/systems/match_mode/MatchModeTypes"
7+
178
import { globalAddToast } from "@/ui/components/GlobalUIControls"
189
import Label from "@/ui/components/Label"
1910
import type { PanelImplProps } from "@/ui/components/Panel"
2011
import { NegativeButton, PositiveButton, SynthesisIcons } from "@/ui/components/StyledComponents"
2112
import { CloseType, useUIContext } from "@/ui/helpers/UIProviderHelpers"
22-
import { convertFeetToMeters } from "@/util/UnitConversions"
2313

2414
/**
2515
* Configuration for match mode rules and timing.
@@ -55,7 +45,6 @@ export interface MatchModeConfig {
5545

5646
/**
5747
* Maximum allowed robot height in meters (stored internally).
58-
* User input is in feet but converted to meters during config processing.
5948
* Set to Infinity for no height limit. (default: Infinity)
6049
*/
6150
maxHeight: number
@@ -80,6 +69,19 @@ export interface MatchModeConfig {
8069
sideExtensionPenalty: number
8170
}
8271

72+
const props: Readonly<{ id: keyof MatchModeConfig; expectedType: string; required: boolean }>[] = [
73+
{ id: "id", expectedType: "string", required: true },
74+
{ id: "name", expectedType: "string", required: true },
75+
{ id: "autonomousTime", expectedType: "number", required: false },
76+
{ id: "teleopTime", expectedType: "number", required: false },
77+
{ id: "endgameTime", expectedType: "number", required: false },
78+
{ id: "ignoreRotation", expectedType: "boolean", required: false },
79+
{ id: "maxHeight", expectedType: "number", required: false },
80+
{ id: "heightLimitPenalty", expectedType: "number", required: false },
81+
{ id: "sideMaxExtension", expectedType: "number", required: false },
82+
{ id: "sideExtensionPenalty", expectedType: "number", required: false },
83+
]
84+
8385
function matchConfigSelected(config: MatchModeConfig) {
8486
if (MatchMode.getInstance().isMatchEnabled()) {
8587
globalAddToast(
@@ -196,87 +198,53 @@ const MatchModeConfigPanel: React.FC<PanelImplProps<void, void>> = ({ panel }) =
196198
}
197199

198200
const validateAndNormalizeMatchModeConfig = (config: unknown): MatchModeConfig | null => {
199-
let valid = true
200-
201201
// Type guard to check if config is an object
202202
if (typeof config !== "object" || config === null) {
203203
console.error("Match mode config validation failed: config must be an object")
204204
globalAddToast("error", "Invalid Match Mode Config", "Configuration must be an object")
205205
return null
206206
}
207-
208207
const configObj = config as Record<string, unknown>
209208

210-
const props: { id: string; expectedType: string; required: boolean }[] = [
211-
{ id: "id", expectedType: "string", required: true },
212-
{ id: "name", expectedType: "string", required: true },
213-
{ id: "autonomousTime", expectedType: "number", required: false },
214-
{ id: "teleopTime", expectedType: "number", required: false },
215-
{ id: "endgameTime", expectedType: "number", required: false },
216-
{ id: "ignoreRotation", expectedType: "boolean", required: false },
217-
{ id: "maxHeight", expectedType: "number", required: false },
218-
{ id: "heightLimitPenalty", expectedType: "number", required: false },
219-
{ id: "sideMaxExtension", expectedType: "number", required: false },
220-
{ id: "sideExtensionPenalty", expectedType: "number", required: false },
221-
]
222-
223209
const typeError = (id: string, expectedType?: string) => {
224210
const errorMessage = expectedType ? `must be a ${expectedType}` : "is required"
225211
console.error(`Match mode config validation failed: the '${id}' field ${errorMessage}`)
226212
globalAddToast("error", "Invalid Match Mode Config", `The '${id}' field ${errorMessage}`)
227213
}
228214

229-
for (const prop of props) {
230-
if (configObj[prop.id] == undefined) {
231-
if (prop.required) {
232-
typeError(prop.id)
233-
valid = false
234-
}
235-
} else if (typeof configObj[prop.id] != prop.expectedType) {
236-
if (prop.required) {
237-
typeError(prop.id, prop.expectedType)
238-
valid = false
239-
} else {
240-
globalAddToast(
241-
"warning",
242-
"Invalid Match Mode Config",
243-
`The '${prop.id}' field must be a ${prop.expectedType}, ignoring ${prop.id} field`
244-
)
215+
function checkValidity(configObj: Record<string, unknown>): configObj is Partial<MatchModeConfig> {
216+
for (const prop of props) {
217+
if (configObj[prop.id] == undefined) {
218+
if (prop.required) {
219+
typeError(prop.id)
220+
return false
221+
}
222+
} else if (typeof configObj[prop.id] != prop.expectedType) {
223+
if (prop.required) {
224+
typeError(prop.id, prop.expectedType)
225+
return false
226+
} else {
227+
globalAddToast(
228+
"warning",
229+
"Invalid Match Mode Config",
230+
`The '${prop.id}' field must be a ${prop.expectedType}, ignoring ${prop.id} field`
231+
)
232+
}
245233
}
246234
}
235+
return true
247236
}
248237

249-
if (!valid) {
238+
if (!checkValidity(configObj)) {
250239
return null
251240
}
252241

253-
// If validation passes, normalize the config with defaults for missing fields
254-
const normalizedConfig: MatchModeConfig = {
255-
id: configObj.id as string,
256-
name: configObj.name as string,
257-
isDefault: false, // User-uploaded configs are not default configs
258-
autonomousTime:
259-
typeof configObj.autonomousTime === "number" ? configObj.autonomousTime : DEFAULT_AUTONOMOUS_TIME,
260-
teleopTime: typeof configObj.teleopTime === "number" ? configObj.teleopTime : DEFAULT_TELEOP_TIME,
261-
endgameTime: typeof configObj.endgameTime === "number" ? configObj.endgameTime : DEFAULT_ENDGAME_TIME,
262-
ignoreRotation:
263-
typeof configObj.ignoreRotation === "boolean" ? configObj.ignoreRotation : DEFAULT_IGNORE_ROTATION,
264-
maxHeight:
265-
typeof configObj.maxHeight === "number" ? convertFeetToMeters(configObj.maxHeight) : DEFAULT_MAX_HEIGHT,
266-
heightLimitPenalty:
267-
typeof configObj.heightLimitPenalty === "number"
268-
? configObj.heightLimitPenalty
269-
: DEFAULT_HEIGHT_LIMIT_PENALTY,
270-
sideMaxExtension:
271-
typeof configObj.sideMaxExtension === "number"
272-
? convertFeetToMeters(configObj.sideMaxExtension)
273-
: DEFAULT_SIDE_MAX_EXTENSION,
274-
sideExtensionPenalty:
275-
typeof configObj.sideExtensionPenalty === "number"
276-
? configObj.sideExtensionPenalty
277-
: DEFAULT_SIDE_EXTENSION_PENALTY,
242+
// If validation passes, use the default values in any missing fields
243+
const normalizedConfig = {
244+
...DefaultMatchModeConfigs.fallbackValues(),
245+
...configObj,
278246
}
279-
247+
normalizedConfig.isDefault = false
280248
return normalizedConfig
281249
}
282250

0 commit comments

Comments
 (0)