// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// A test utility for pinging objects back and forth among a pool of workers.
// Use by calling {RunWorkerPingTest} with a {config} object.
{
// Reference config object for demonstrating the interface.
let config = {
  numThings: 4,       // size of circular buffer
  numWorkers: 4,      // number of workers
  numMessages: 100,   // number of messages sent to each worker
  allocInterval: 11,  // interval for allocating new things per worker
  traceScript: false, // print the script
  traceAlloc: false,  // print each allocation attempt
  traceIteration: 10, // print diagnostics every so many iterations
  abortOnFail: false, // kill worker if allocation fails

  // Note that because the functions are appended to a worker script
  // *as source*, they need to be named properly.

  // The function that allocates things. Required.
  AllocThing: function AllocThing(id) {
    return new Array(2);
  },
  // Before message send behavior. Optional.
  BeforeSend: function BeforeSend(msg) { },
  // Before message reception behavior. Optional.
  BeforeReceive: function BeforeReceive(msg) { },
}
}

function RunWorkerPingTest(config) {
  let workers = [];
  let beforeSend = (typeof config.BeforeSend == "function") ?
      config.BeforeSend :
      function BeforeSend(msg) { };
  let beforeReceive = (typeof config.BeforeReceive == "function") ?
      config.BeforeReceive :
      function BeforeReceive(msg) { };

  // Each worker has a circular buffer of size {config.numThings}, recording
  // received things into the buffer and responding with a previous thing.
  // Every {config.allocInterval}, a worker creates a new thing by
  // {config.AllocThing}.

  let script =
`const kNumThings = ${config.numThings};
  const kAllocInterval = ${config.allocInterval};
  let index = 0;
  let total = 0;
  let id = 0;
  let things = new Array(kNumThings);
  for (let i = 0; i < kNumThings; i++) {
    things[i] = TryAllocThing();
  }

  function TryAllocThing() {
     try {
       let thing = AllocThing(id++);
       ${config.traceAlloc ? "print(\"alloc success\");" : ""}
       return thing;
     } catch(e) {
       ${config.abortOnFail ? "postMessage({error: e.toString()}); throw e;" : "" }
       ${config.traceAlloc ? "print(\"alloc fail: \" + e);" : ""}
     }
  }

  onmessage = function(msg) {
    BeforeReceive(msg);
    if (msg.thing !== undefined) {
      let reply = things[index];
      if ((total % kAllocInterval) == 0) {
        reply = TryAllocThing();
      }
      things[index] = msg.thing;
      postMessage({thing : reply});
      index = (index + 1) % kNumThings;
      total++;
    }
  }
  ${config.AllocThing.toString()}
  ${beforeReceive.toString()}
  `;

  if (config.traceScript) {
    print("========== Worker script ==========");
    print(script);
    print("===================================");
  }

  for (let i = 0; i < config.numWorkers; i++) {
    let worker = new Worker(script, {type : 'string'});
    workers.push(worker);
  }

  let time = performance.now();

  // The main thread posts {config.numMessages} messages to {config.numWorkers}
  // workers, with each message containing a "thing" created by {config.AllocThing}.
  let thing = config.AllocThing(-1);
  for (let i = 0; i < config.numMessages; i++) {
    if ((i % config.traceIteration) == 0) {
      let now = performance.now();
      print(`iteration ${i}, Δ = ${(now - time).toFixed(3)} ms`);
      time = now;
    }

    for (let worker of workers) {
      let msg = {thing: thing};
      beforeSend(msg);
      worker.postMessage(msg);
      msg = worker.getMessage();
      if (msg.thing) {
        thing = msg.thing;
      } else if (msg.error) {
        print('Error in worker:', msg.error);
        worker.terminate();
        throw msg.error;
      }
    }
  }
  print('Terminating workers.');
  for (let worker of workers) {
    worker.terminate();
  }
  print('Workers terminated.');
}