queueOrder.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. /**
  2. * The order the scheduler will actually dispatch pending queue items in.
  3. *
  4. * The backend decides this in SQL (`print_scheduler.check_queue`):
  5. *
  6. * ORDER BY printer_id, target_model,
  7. * been_jumped DESC,
  8. * print_time_seconds ASC NULLS LAST,
  9. * position
  10. *
  11. * with the first two columns acting purely as a grouping — a row carries
  12. * either a `printer_id` or a `target_model`, never both — and the rest
  13. * deciding who goes first inside that group. Every UI surface that claims to
  14. * show queue order has to reproduce it, and each one that reproduced it
  15. * privately drifted: the timeline sorted by `position` alone and so ignored
  16. * Shortest-Job-First entirely (#3043). One comparator, three callers.
  17. */
  18. interface OrderableQueueItem {
  19. printer_id?: number | null;
  20. target_model?: string | null;
  21. been_jumped?: boolean;
  22. print_time_seconds?: number | null;
  23. position: number;
  24. }
  25. /**
  26. * Which dispatch group an item belongs to: a named printer, a printer model,
  27. * or neither. Items only compete with others in their own group, so this is
  28. * both the timeline's swimlane and the outer sort key of a flat pending list.
  29. *
  30. * Returned as a string rather than a number because the group is a name, not
  31. * a magnitude. The flat list used to fold `target_model` down to
  32. * `-charCodeAt(0)`, which gave `X1C` and `X2D` (and `P1S` and `P1P`) the same
  33. * key and interleaved two lanes into one.
  34. */
  35. export function queueLaneKey(item: OrderableQueueItem): string {
  36. if (item.printer_id != null) return `printer:${item.printer_id}`;
  37. if (item.target_model) return `model:${item.target_model}`;
  38. return 'unassigned';
  39. }
  40. /**
  41. * Order two items competing for the same printer or model.
  42. *
  43. * @param sjfEnabled the `queue_shortest_first` setting. When off, the
  44. * scheduler orders by position alone and so does this.
  45. */
  46. export function compareQueueOrder(
  47. a: OrderableQueueItem,
  48. b: OrderableQueueItem,
  49. sjfEnabled: boolean,
  50. ): number {
  51. if (sjfEnabled) {
  52. // Starvation guard: an item something else was allowed to jump ahead of
  53. // goes first next time, whatever the print times say.
  54. const aJumped = a.been_jumped ? 1 : 0;
  55. const bJumped = b.been_jumped ? 1 : 0;
  56. if (aJumped !== bJumped) return bJumped - aJumped;
  57. // Shortest first, and an item whose duration we don't know yet sorts last
  58. // rather than winning by looking like a zero-second print (NULLS LAST).
  59. const aTime = a.print_time_seconds ?? Infinity;
  60. const bTime = b.print_time_seconds ?? Infinity;
  61. if (aTime !== bTime) return aTime - bTime;
  62. }
  63. return a.position - b.position;
  64. }
  65. /**
  66. * Order a flat list that spans several groups -- a pending list rather than a
  67. * per-lane one. Groups stay contiguous; within each, the scheduler's own order
  68. * applies.
  69. *
  70. * Groups themselves are ordered for reading, not to mirror the backend: named
  71. * printers by id, then model lanes by name, then unassigned. The backend's own
  72. * answer here is `ORDER BY printer_id` with a NULL in it, which SQLite sorts
  73. * first and PostgreSQL sorts last -- nothing worth reproducing.
  74. */
  75. export function compareQueueOrderAcrossLanes(
  76. a: OrderableQueueItem,
  77. b: OrderableQueueItem,
  78. sjfEnabled: boolean,
  79. ): number {
  80. const aLane = queueLaneKey(a);
  81. const bLane = queueLaneKey(b);
  82. if (aLane !== bLane) {
  83. if (a.printer_id != null && b.printer_id != null) return a.printer_id - b.printer_id;
  84. const aRank = laneRank(a);
  85. const bRank = laneRank(b);
  86. if (aRank !== bRank) return aRank - bRank;
  87. return aLane < bLane ? -1 : 1;
  88. }
  89. return compareQueueOrder(a, b, sjfEnabled);
  90. }
  91. function laneRank(item: OrderableQueueItem): number {
  92. if (item.printer_id != null) return 0;
  93. if (item.target_model) return 1;
  94. return 2;
  95. }