src/modules/candidates/candidateMergeSplitValidator.ts
import ProgressBar, { ProgressBarSymbol } from '../../console/progressBar.js';
import ArrayPoly from '../../polyfill/arrayPoly.js';
import DAT from '../../types/dats/dat.js';
import Game from '../../types/dats/game.js';
import Machine from '../../types/dats/mame/machine.js';
import Parent from '../../types/dats/parent.js';
import Options, { MergeMode } from '../../types/options.js';
import ReleaseCandidate from '../../types/releaseCandidate.js';
import Module from '../module.js';
/**
* Validate un-merged, split, and merged ROM sets for playability after all generation and filtering
* has happened.
*/
export default class CandidateMergeSplitValidator extends Module {
private readonly options: Options;
constructor(options: Options, progressBar: ProgressBar) {
super(progressBar, CandidateMergeSplitValidator.name);
this.options = options;
}
/**
* Validate the {@link ReleaseCandidate}s.
*/
validate(dat: DAT, parentsToCandidates: Map<Parent, ReleaseCandidate[]>): string[] {
if (parentsToCandidates.size === 0) {
this.progressBar.logTrace(
`${dat.getNameShort()}: no parents to validate merged & split ROM sets for`,
);
return [];
}
this.progressBar.logTrace(`${dat.getNameShort()}: validating merged & split ROM sets`);
this.progressBar.setSymbol(ProgressBarSymbol.CANDIDATE_VALIDATING);
this.progressBar.reset(parentsToCandidates.size);
const datGamesIndexed = dat.getGames().reduce((map, game) => {
map.set(game.getName(), game);
return map;
}, new Map<string, Game>());
const releaseCandidatesIndexed = [...parentsToCandidates.values()]
.flat()
.filter((releaseCandidate) => releaseCandidate.getRomsWithFiles().length)
.reduce((map, releaseCandidate) => {
map.set(releaseCandidate.getGame().getName(), releaseCandidate);
return map;
}, new Map<string, ReleaseCandidate>());
// For every Game that has ReleaseCandidate(s) with files
const missingGames = [...parentsToCandidates.values()]
.flat()
.filter((releaseCandidate) => releaseCandidate.getRomsWithFiles().length)
.map((releaseCandidate) => releaseCandidate.getGame())
.reduce(ArrayPoly.reduceUnique(), [])
.flatMap((game) => {
let missingDependencies: string[] = [];
// Validate dependent parent was found
if (
this.options.getMergeRoms() === MergeMode.SPLIT &&
game.isClone() &&
!releaseCandidatesIndexed.has(game.getParent())
) {
missingDependencies = [game.getParent(), ...missingDependencies];
}
// Validate dependent devices were found
if (this.options.getMergeRoms() !== MergeMode.FULLNONMERGED && game instanceof Machine) {
const missingDeviceGames = game
.getDeviceRefs()
.map((deviceRef) => datGamesIndexed.get(deviceRef.getName()))
.filter((deviceGame) => deviceGame !== undefined)
// Dependent device has ROM files
.filter((deviceGame) => deviceGame.getRoms().length)
.map((deviceGame) => {
const deviceReleaseCandidate = releaseCandidatesIndexed.get(deviceGame.getName());
if (deviceReleaseCandidate) {
// The device game has candidates, validation passed
return undefined;
}
return deviceGame.getName();
})
.filter((deviceGameName) => deviceGameName !== undefined)
.sort();
missingDependencies = [...missingDependencies, ...missingDeviceGames];
}
if (missingDependencies.length > 0) {
this.progressBar.logWarn(
`${dat.getNameShort()}: ${game.getName()}: missing dependent ROM set${missingDependencies.length !== 1 ? 's' : ''}: ${missingDependencies.join(', ')}`,
);
}
return missingDependencies;
});
this.progressBar.logTrace(`${dat.getNameShort()}: done validating merged & split ROM sets`);
return missingGames;
}
}