General Actions to handlers Refacto
This commit is contained in:
+54
-28
@@ -1,17 +1,35 @@
|
||||
import { MySQLClient } from '@p42/p42modules'
|
||||
import { SimRepository } from '../../SimMaestro/simRepository.js'
|
||||
import { SimRepository } from '../../Maestro/simRepository.js'
|
||||
|
||||
function agentHashKey(template, agentId) {
|
||||
return(template.replace(/\[UID\]/g, agentId))
|
||||
}
|
||||
|
||||
function parseJsonField(raw) {
|
||||
function hashFieldValue(raw) {
|
||||
if(raw == null) return(null)
|
||||
if(typeof(raw) === 'object') return(raw)
|
||||
if(typeof(raw) !== 'string') return(raw)
|
||||
try { return(JSON.parse(raw)) }
|
||||
catch { return(null) }
|
||||
catch { return(raw) }
|
||||
}
|
||||
|
||||
function valuesEqual(a, b) {
|
||||
if(a === b) return(true)
|
||||
if(a == null || b == null) return(false)
|
||||
if(typeof(a) !== 'object' && typeof(b) !== 'object') return(a == b)
|
||||
if(typeof(a) !== typeof(b)) return(false)
|
||||
if(typeof(a) !== 'object') return(false)
|
||||
if(Array.isArray(a) !== Array.isArray(b)) return(false)
|
||||
const keysA = Object.keys(a)
|
||||
const keysB = Object.keys(b)
|
||||
if(keysA.length !== keysB.length) return(false)
|
||||
for(const key of keysA) {
|
||||
if(!valuesEqual(a[key], b[key])) return(false)
|
||||
}
|
||||
return(true)
|
||||
}
|
||||
|
||||
const RESERVED_HASH_FIELDS = new Set(['position', 'vector', 'speed', 'segment'])
|
||||
|
||||
function vectorsEqual(a, b) {
|
||||
for(const axis of ['x', 'y', 'z']) {
|
||||
if(a[axis] !== b[axis]) return(false)
|
||||
@@ -23,13 +41,12 @@ async function findSimulationFixture(ctx) {
|
||||
const { guiDatabase, simDatabase } = ctx.databases
|
||||
const rows = await MySQLClient.poolExecute(ctx.db, `
|
||||
SELECT u.usr_uuid AS user_uuid,
|
||||
BIN_TO_UUID(s.sim_uuid) AS simulation_uuid,
|
||||
BIN_TO_UUID(s.sim_root_kf_uuid) AS keyframe_id
|
||||
BIN_TO_UUID(s.sim_uuid) AS simulation_uuid
|
||||
FROM \`${guiDatabase}\`.users u
|
||||
INNER JOIN \`${guiDatabase}\`.simowners o ON o.own_usr_id = u.usr_id
|
||||
INNER JOIN \`${simDatabase}\`.simulations s ON o.own_sim_uuid = s.sim_uuid
|
||||
INNER JOIN \`${simDatabase}\`.edited_kf_store ekfs ON ekfs.ekfs_ekf_uuid = s.sim_root_kf_uuid
|
||||
GROUP BY u.usr_uuid, s.sim_uuid, s.sim_root_kf_uuid
|
||||
GROUP BY u.usr_uuid, s.sim_uuid
|
||||
HAVING COUNT(ekfs.ekfs_agent_id) > 0
|
||||
LIMIT 1
|
||||
`)
|
||||
@@ -37,14 +54,13 @@ async function findSimulationFixture(ctx) {
|
||||
if(!rows.length) {
|
||||
throw(new Error(
|
||||
'No simulation fixture found in MySQL (need user-owned sim with root keyframe agents). '
|
||||
+ 'Pass --userUuid, --simulationUuid, --keyframeId explicitly, or use --guiDatabase / --simDatabase for a test DB.'
|
||||
+ 'Pass --userUuid, --simulationUuid explicitly, or use --guiDatabase / --simDatabase for a test DB.'
|
||||
))
|
||||
}
|
||||
|
||||
return({
|
||||
userUuid: rows[0].user_uuid,
|
||||
simulationUuid: rows[0].simulation_uuid,
|
||||
keyframeId: rows[0].keyframe_id,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -71,15 +87,11 @@ function waitForLifecycleEvent(ctx, eventType, timeoutMs) {
|
||||
export function configureYargs(yargsBuilder) {
|
||||
return(yargsBuilder.options({
|
||||
userUuid: {
|
||||
describe: 'User UUID to send STARTSIMULATION as (auto-discovered if omitted)',
|
||||
describe: 'User UUID (dashed or 0x-prefixed hex; auto-discovered if omitted)',
|
||||
type: 'string',
|
||||
},
|
||||
simulationUuid: {
|
||||
describe: 'Simulation UUID (auto-discovered if omitted)',
|
||||
type: 'string',
|
||||
},
|
||||
keyframeId: {
|
||||
describe: 'Root keyframe UUID (auto-discovered if omitted)',
|
||||
describe: 'Simulation UUID (dashed or 0x-prefixed hex; auto-discovered if omitted)',
|
||||
type: 'string',
|
||||
},
|
||||
timeout: {
|
||||
@@ -91,32 +103,32 @@ export function configureYargs(yargsBuilder) {
|
||||
}
|
||||
|
||||
export async function run(ctx) {
|
||||
const { log, argv, config, systemCnx, arenaCnx } = ctx
|
||||
const { log, argv, config, systemCnx, arenaCnx, normalizeUuid } = ctx
|
||||
const arenaStorage = config.gps?.arenaStorage ?? {
|
||||
agentHashKey: 'arena:agents:[UID]',
|
||||
agentsIndexKey: 'arena:agents',
|
||||
}
|
||||
|
||||
log('action', 'Resolving simulation fixture from MySQL...')
|
||||
let userUuid = argv.userUuid
|
||||
let simulationUuid = argv.simulationUuid
|
||||
let keyframeId = argv.keyframeId
|
||||
let userUuid = argv.userUuid ? normalizeUuid(argv.userUuid) : undefined
|
||||
let simulationUuid = argv.simulationUuid ? normalizeUuid(argv.simulationUuid) : undefined
|
||||
|
||||
if(!userUuid || !simulationUuid || !keyframeId) {
|
||||
if(!userUuid || !simulationUuid) {
|
||||
const fixture = await findSimulationFixture(ctx)
|
||||
userUuid = userUuid ?? fixture.userUuid
|
||||
simulationUuid = simulationUuid ?? fixture.simulationUuid
|
||||
keyframeId = keyframeId ?? fixture.keyframeId
|
||||
}
|
||||
|
||||
log('action', `User: ${userUuid}`)
|
||||
log('action', `Simulation: ${simulationUuid}`)
|
||||
log('action', `Keyframe: ${keyframeId}`)
|
||||
|
||||
const simRepo = new SimRepository(ctx.db, ctx.databases, false)
|
||||
const access = await simRepo.validateSimulationAccess(userUuid, simulationUuid, keyframeId)
|
||||
const access = await simRepo.validateSimulationAccess(userUuid, simulationUuid)
|
||||
if(!access.ok) throw(new Error(`Simulation access check failed: ${access.err}`))
|
||||
|
||||
const keyframeId = access.sim.sim_root_kf_uuid
|
||||
log('action', `Root keyframe: ${keyframeId}`)
|
||||
|
||||
const agentsResult = await simRepo.loadKeyframeAgents(keyframeId)
|
||||
if(!agentsResult.ok) throw(new Error(`Failed to load keyframe agents: ${agentsResult.err}`))
|
||||
if(!agentsResult.agents.length) throw(new Error('Keyframe has no agents with valid GPS values'))
|
||||
@@ -137,7 +149,6 @@ export async function run(ctx) {
|
||||
roles: ['*'],
|
||||
payload: {
|
||||
simulationUuid,
|
||||
keyframeId,
|
||||
infraId: null,
|
||||
},
|
||||
})
|
||||
@@ -174,8 +185,8 @@ export async function run(ctx) {
|
||||
const hash = await arenaCnx.redisHgetall(key)
|
||||
const expected = expectedById.get(agentId)
|
||||
|
||||
const position = parseJsonField(hash.position)
|
||||
const vector = parseJsonField(hash.vector)
|
||||
const position = hashFieldValue(hash.position)
|
||||
const vector = hashFieldValue(hash.vector)
|
||||
|
||||
if(!position || !vector) {
|
||||
throw(new Error(`Agent ${agentId}: missing position or vector in arena hash ${key}`))
|
||||
@@ -189,8 +200,23 @@ export async function run(ctx) {
|
||||
throw(new Error(`Agent ${agentId}: vector mismatch (MySQL vs arena store)`))
|
||||
}
|
||||
|
||||
log('success', `Agent ${agentId}: position and vector match MySQL`)
|
||||
const storeExpected = expected.store ?? {}
|
||||
for(const [field, expVal] of Object.entries(storeExpected)) {
|
||||
const actualVal = hashFieldValue(hash[field])
|
||||
if(!valuesEqual(actualVal, expVal)) {
|
||||
throw(new Error(`Agent ${agentId}: store field "${field}" mismatch (MySQL vs arena store)`))
|
||||
}
|
||||
}
|
||||
|
||||
for(const field of Object.keys(hash)) {
|
||||
if(RESERVED_HASH_FIELDS.has(field)) continue
|
||||
if(!(field in storeExpected)) {
|
||||
throw(new Error(`Agent ${agentId}: unexpected store field "${field}" in arena hash ${key}`))
|
||||
}
|
||||
}
|
||||
|
||||
log('success', `Agent ${agentId}: position, vector, and store values match MySQL`)
|
||||
}
|
||||
|
||||
log('success', 'Arena store seeded correctly from MySQL (Maestro will stall waiting for readyToStart without GPS)')
|
||||
log('success', 'Arena store seeded correctly from MySQL (Maestro will wait for agent + primordial daemon readyToStart until timeout)')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user