aboutsummaryrefslogtreecommitdiff
path: root/tools/schedule.js
blob: 02dc88340bbd6d0eea93175dfe7459e1aa0e0603 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// A mixed paradigm scheduler

// The core managed data type: a queue will be pushed a number of these and asked to manage it.
function Job(data, prio=0, wt=1){
  // Data is an array;
  // Ideally, Both prio and wt are functions which take one argument: how many write cycles they've been waiting. This helps manage time-sensitive jobs.
  // But this is too computationally difficult, so they are integer constants.

  // Immutable Properties
  this.data = data; //A set of work to be done (like character writes)
  this.prio = prio; // If a.prio > b.prio, all of `a.data` will be sent before any of `b.data`
  this.wt   = wt; // After numerous calls, (amount called of a/amount called of b) = a.wt/b.wt if a.prio = b.prio
  this.maxr = data.length/wt; // A utility calculation: If a job has a lower maxr, it will run out of data earlier.

  // Mutable Properties
  this.wacc = 0; // mutable property: Queue will change this to keep track between dequeues of how much "left over" push real estate this should have.
  this.start; // The write # this job was introduced on. Also for Queue use.
}

function Queue(maxExport, call, delay){
  // Every delay ms, Queue executes `call(maxExport # of objs in queue)`
  let jobs = {}; // Links priorities to unordered arrays of jobs
  let prios = []; // Array of priorities (keys of jobs), sorted.
  let open = false;
  let disab = true;
  let writes = 0;

  this.enqueue = function(job){
    job.start = writes;
    let prio = job.prio;
    if (!jobs[prio]) jobs[prio] = [];
    jobs[prio].splice(0, Math.abs(bs(jobs[prio], job, (el, ne) => el.maxr - ne.maxr)), job);
    push(job);
    prios.splice(0, Math.abs(bs(prios, prio, (el, ne) => el-ne)), prio)
    if (open) dequeue();
  }
  
  this.enable = function(){
    disab = false;
    if (open) dequeue();
  }

  this.disable = function(){
    disab = true;
  }
 
  function dequeue(){
    if (disab){
      open = true;
      return;
    }
    if (prios.length == 0){
      open = true;
      return;
    }
    open = false;
    let data = [];
    while (data.length < maxExport){
      data.push(...getNumOrAll(prios[prios.length-1]), maxExport-data.length);
    }

    writes++;
    call(data);
    setTimeout(dequeue, delayms);
  }

  function getNumOrAll(prio, num){
    /*
      
    Step 1: (Pre-)sort by job.data.length/job.weight.
    Step 2: Start at lowest, and pop all until job.data.length>job.normweight*num (decreasing num as popping and recalc job.normweight). Delete the job.
    Step 3: Then, pop job.normweight*num//1 elems from remaining, without num decrease or normweight recalc. But keep job.wacc = job.normweight*num%1
    Step 4: Shallow copy job array, and sort by job.wacc.
    Step 5: Iterate through array (high->low), and subtract 1 until the length of output is num.
    Step 6: If empty, remove prio && jobs[prio]; return.
    */
    let jobq = jobs[prio];
    let dequeued = [];
    let weightsum = Math.sum(jobq.map(job => job.wt)).reduce((acc, cur)=>acc+cur);
    while (job[0].data.length<(job[0].wacc+job[0].wt*num/weightsum)){
      weightsum -= job.wt;
      dequeued.push(...jobq.shift().data);
    }
    for (job of jobq){
      job.wacc += job.wt*num/weightsum;
      let data = job.data;
      let topop = job.wacc-job.wacc%1;
      job.wacc -= topop
      dequeued.push(...data.splice(-topop));
    }
    for (job of jobq.splice().sort((el, ne) => el.wacc-ne.wacc)){
      if (dequeued.length == num) break;
      job.wacc--;
      dequeue.push(job.pop());
    }
    if (jobq.length == 0){
      delete jobs[prio];
      prios.splice(bs(prios, prio, (el, ne)=>el-ne), 1);
    }
    return dequeued;
  }
}