diff --git a/clients/js/src/instructionPlans/instructionPlan.ts b/clients/js/src/instructionPlans/instructionPlan.ts index 2378e95..10f5130 100644 --- a/clients/js/src/instructionPlans/instructionPlan.ts +++ b/clients/js/src/instructionPlans/instructionPlan.ts @@ -36,8 +36,6 @@ export type IterableInstructionPlan< TInstruction extends IInstruction = IInstruction, > = Readonly<{ kind: 'iterable'; - /** Get all the instructions in one go or return `null` if not possible */ - getAll: () => TInstruction[] | null; /** Get an iterator for the instructions. */ getIterator: () => InstructionIterator; }>; @@ -102,7 +100,6 @@ export function getLinearIterableInstructionPlan({ }): IterableInstructionPlan { return { kind: 'iterable', - getAll: () => [getInstruction(0, totalBytes)], getIterator: () => { let offset = 0; return { @@ -135,7 +132,6 @@ export function getIterableInstructionPlanFromInstructions< >(instructions: TInstruction[]): IterableInstructionPlan { return { kind: 'iterable', - getAll: () => instructions, getIterator: () => { let instructionIndex = 0; return { diff --git a/clients/js/src/instructionPlans/transactionPlannerBase.ts b/clients/js/src/instructionPlans/transactionPlannerBase.ts index f45a321..0b6b03e 100644 --- a/clients/js/src/instructionPlans/transactionPlannerBase.ts +++ b/clients/js/src/instructionPlans/transactionPlannerBase.ts @@ -153,16 +153,26 @@ async function traverseSequential( context.parent && (context.parent.kind === 'parallel' || !instructionPlan.divisible); if (mustEntirelyFitInCandidate) { - const allInstructions = getAllInstructions(instructionPlan); - candidate = allInstructions - ? selectCandidate(context.parentCandidates, allInstructions) - : null; - if (candidate && allInstructions) { - await context.addInstructionsToSingleTransactionPlan( - candidate, - allInstructions + for (const parentCandidate of context.parentCandidates) { + const transactionPlan = await traverseWithSingleCandidate( + instructionPlan, + { + ...context, + candidate: { + kind: 'single', + message: { + ...parentCandidate.message, + instructions: [...parentCandidate.message.instructions], + } as CompilableTransactionMessage, + }, + } ); - return null; + if (transactionPlan) { + (parentCandidate as Mutable).message = + transactionPlan.message; + // TODO: Use hook. + return null; + } } } else { candidate = @@ -327,27 +337,6 @@ function getParallelCandidates( return getAllSingleTransactionPlans(latestPlan); } -function getAllInstructions( - instructionPlan: InstructionPlan -): IInstruction[] | null { - if (instructionPlan.kind === 'single') { - return [instructionPlan.instruction]; - } - if (instructionPlan.kind === 'iterable') { - return instructionPlan.getAll(); - } - return instructionPlan.plans.reduce( - (acc, plan) => { - if (acc === null) return null; - const instructions = getAllInstructions(plan); - if (instructions === null) return null; - acc.push(...instructions); - return acc; - }, - [] as IInstruction[] | null - ); -} - function selectCandidateForIterator( candidates: SingleTransactionPlan[], iterator: InstructionIterator @@ -395,3 +384,121 @@ function isValidTransactionPlan(transactionPlan: TransactionPlan): boolean { } return transactionPlan.plans.every(isValidTransactionPlan); } + +type TraverseWithSingleCandidateContext = { + abortSignal?: AbortSignal; + candidate: SingleTransactionPlan | null; + createSingleTransactionPlan: ( + instructions?: IInstruction[], + abortSignal?: AbortSignal + ) => Promise; + addInstructionsToSingleTransactionPlan: ( + plan: SingleTransactionPlan, + instructions: IInstruction[], + abortSignal?: AbortSignal + ) => Promise; +}; + +async function traverseWithSingleCandidate( + instructionPlan: InstructionPlan, + context: TraverseWithSingleCandidateContext +): Promise { + context.abortSignal?.throwIfAborted(); + switch (instructionPlan.kind) { + case 'sequential': + return await traverseSequentialWithSingleCandidate( + instructionPlan, + context + ); + case 'parallel': + return await traverseParallelWithSingleCandidate( + instructionPlan, + context + ); + case 'single': + return await traverseSingleWithSingleCandidate(instructionPlan, context); + case 'iterable': + return await traverseIterableWithSingleCandidate( + instructionPlan, + context + ); + default: + instructionPlan satisfies never; + throw new Error( + `Unknown instruction plan kind: ${(instructionPlan as { kind: string }).kind}` + ); + } +} + +async function traverseSequentialWithSingleCandidate( + instructionPlan: SequentialInstructionPlan, + context: TraverseWithSingleCandidateContext +): Promise { + if (context.candidate === null) { + return null; + } + for (const plan of instructionPlan.plans) { + const candidate = await traverseWithSingleCandidate(plan, { + ...context, + candidate: context.candidate, + }); + if (candidate === null) { + return null; + } + } + return context.candidate; +} + +async function traverseParallelWithSingleCandidate( + instructionPlan: ParallelInstructionPlan, + context: TraverseWithSingleCandidateContext +): Promise { + if (context.candidate === null) { + return null; + } + for (const plan of instructionPlan.plans) { + const candidate = await traverseWithSingleCandidate(plan, { + ...context, + candidate: context.candidate, + }); + if (candidate === null) { + return null; + } + } + return context.candidate; +} + +async function traverseSingleWithSingleCandidate( + instructionPlan: SingleInstructionPlan, + context: TraverseWithSingleCandidateContext +): Promise { + if (context.candidate === null) { + return null; + } + const ix = instructionPlan.instruction; + if (!isValidCandidate(context.candidate, [ix])) { + return null; + } + await context.addInstructionsToSingleTransactionPlan(context.candidate, [ix]); + return context.candidate; +} + +async function traverseIterableWithSingleCandidate( + instructionPlan: IterableInstructionPlan, + context: TraverseWithSingleCandidateContext +): Promise { + if (context.candidate === null) { + return null; + } + const iterator = instructionPlan.getIterator(); + while (iterator.hasNext()) { + const ix = iterator.next(context.candidate.message); + if (!ix || !isValidCandidate(context.candidate, [ix])) { + return null; + } + await context.addInstructionsToSingleTransactionPlan(context.candidate, [ + ix, + ]); + } + return context.candidate; +} diff --git a/clients/js/test/instructionPlans/_instructionPlanHelpers.ts b/clients/js/test/instructionPlans/_instructionPlanHelpers.ts index d409c0a..c55f29d 100644 --- a/clients/js/test/instructionPlans/_instructionPlanHelpers.ts +++ b/clients/js/test/instructionPlans/_instructionPlanHelpers.ts @@ -33,7 +33,6 @@ export function instructionIteratorFactory() { return { get: getInstruction, kind: 'iterable', - getAll: () => [getInstruction(totalBytes, 0)], getIterator: () => { let offset = 0; return {