You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Hydro/packages/hydrojudge/src/flow.ts

127 lines
5.0 KiB
TypeScript

import Queue from 'p-queue';
import { STATUS } from '@hydrooj/utils/lib/status';
import type { JudgeResultBody } from 'hydrooj';
import { getConfig } from './config';
import { FormatError } from './error';
import { Context, ContextSubTask } from './judge/interface';
import { NormalizedCase, NormalizedSubtask } from './utils';
interface Task {
compile: () => Promise<void>;
judgeCase: (c: NormalizedCase) => (
(ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => Promise<JudgeResultBody['case']>
)
}
const Score = {
sum: (a: number, b: number) => (a + b),
max: Math.max,
min: Math.min,
};
function judgeSubtask(subtask: NormalizedSubtask, sid: string, judgeCase: Task['judgeCase']) {
return async (ctx: Context) => {
subtask.type ||= 'min';
const ctxSubtask = {
subtask,
status: 0,
score: subtask.type === 'min'
? subtask.score
: 0,
};
const cases = [];
for (const cid in subtask.cases) {
const runner = judgeCase(subtask.cases[cid]);
cases.push(ctx.queue.add(async () => {
const res = (ctx.errored
|| (subtask.type === 'min' && ctxSubtask.score === 0)
|| (subtask.type === 'max' && ctxSubtask.score === subtask.score)
|| (subtask.if || []).filter((i) => ctx.failed[i]).length)
? {
id: subtask.cases[cid].id,
status: STATUS.STATUS_CANCELED,
subtaskId: subtask.id,
score: 0,
time: 0,
memory: 0,
message: '',
} : await runner(ctx, ctxSubtask, runner);
if (res?.status !== STATUS.STATUS_CANCELED) {
ctxSubtask.score = Score[ctxSubtask.subtask.type](ctxSubtask.score, res.score);
ctxSubtask.status = Math.max(ctxSubtask.status, res.status);
if (ctxSubtask.status > STATUS.STATUS_ACCEPTED) ctx.failed[sid] = true;
ctx.total_time += res.time;
ctx.total_memory = Math.max(ctx.total_memory, res.memory);
}
ctx.next({ ...res ? { case: res } : {}, addProgress: 100 / ctx.config.count });
}));
}
try {
await Promise.all(cases);
} catch (e) {
ctx.errored = true;
throw e;
}
ctx.total_status = Math.max(ctx.total_status, ctxSubtask.status);
return ctxSubtask.score;
};
}
export const runFlow = async (ctx: Context, task: Task) => {
if (!ctx.config.subtasks.length) throw new FormatError('Problem data not found.');
ctx.next({ status: STATUS.STATUS_COMPILING });
await task.compile();
ctx.next({ status: STATUS.STATUS_JUDGING, progress: 0 });
ctx.total_status = 0;
ctx.total_score = 0;
ctx.total_memory = 0;
ctx.total_time = 0;
ctx.rerun = getConfig('rerun') || 0;
ctx.queue = new Queue({ concurrency: getConfig('singleTaskParallelism') });
ctx.failed = {};
if (ctx.meta.hackRejudge) {
const subtask = ctx.config.subtasks.find((i) => i.cases.find((j) => j.input.endsWith(ctx.meta.hackRejudge)));
const ctxSubtask = {
subtask,
status: STATUS.STATUS_ACCEPTED,
score: subtask.type === 'min' ? subtask.score : 0,
};
const runner = task.judgeCase(subtask.cases.find((i) => i.input.endsWith(ctx.meta.hackRejudge)));
const res = await runner(ctx, ctxSubtask, runner);
if (res) ctx.next({ case: res });
if (res?.status !== STATUS.STATUS_ACCEPTED) {
const totalScore = Math.sum(ctx.config.subtasks.map((i) => i.score));
ctx.end({
status: STATUS.STATUS_HACKED,
score: totalScore - subtask.score,
});
} else {
ctx.next({ status: STATUS.STATUS_ACCEPTED });
ctx.end({ nop: true });
}
} else {
const scores = {};
await Promise.all(Object.entries(ctx.config.subtasks).map(async ([key, value]) => {
const sid = value.id?.toString() || key;
scores[sid] = await judgeSubtask(value, sid, task.judgeCase)(ctx);
}));
for (const [key, value] of Object.entries(ctx.config.subtasks)) {
let effective = true;
const sid = value.id?.toString() || key;
for (const required of value.if || []) {
if (ctx.failed[required.toString()]) effective = false;
}
if (effective) ctx.total_score += scores[sid];
else ctx.failed[sid] = true;
}
ctx.end({
status: ctx.total_status,
score: ctx.total_score,
time: Math.floor(ctx.total_time * 1000000) / 1000000,
memory: ctx.total_memory,
});
}
ctx.stat.done = new Date();
if (process.env.DEV) ctx.next({ message: JSON.stringify(ctx.stat) });
};