/*
 * Copyright 2014 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "src/core/SkTaskGroup.h"

#include "include/core/SkExecutor.h"

#include <type_traits>
#include <utility>

SkTaskGroup::SkTaskGroup(SkExecutor& executor) : fPending(0), fExecutor(executor) {}

void SkTaskGroup::add(std::function<void(void)> fn) {
    this->add(std::move(fn), /* workList= */ 0);
}

void SkTaskGroup::add(std::function<void(void)> fn, int workList) {
    fPending.fetch_add(+1, std::memory_order_relaxed);
    fExecutor.add([this, fn{std::move(fn)}] {
                      fn();
                      fPending.fetch_add(-1, std::memory_order_release);
                  },
                  workList);
}

void SkTaskGroup::discardAllPendingWork() {
    int numDiscarded = fExecutor.discardAllPendingWork();
    fPending.fetch_add(-numDiscarded, std::memory_order_release);
}

void SkTaskGroup::batch(int N, std::function<void(int)> fn) {
    // TODO: I really thought we had some sort of more clever chunking logic.
    fPending.fetch_add(+N, std::memory_order_relaxed);
    for (int i = 0; i < N; i++) {
        fExecutor.add([fn, i, this] {
            fn(i);
            fPending.fetch_add(-1, std::memory_order_release);
        });
    }
}

bool SkTaskGroup::done() const {
    return fPending.load(std::memory_order_acquire) == 0;
}

void SkTaskGroup::wait() {
    // Actively help the executor do work until our task group is done.
    // This lets SkTaskGroups nest arbitrarily deep on a single SkExecutor:
    // no thread ever blocks waiting for others to do its work.
    // (We may end up doing work that's not part of our task group.  That's fine.)
    while (!this->done()) {
        fExecutor.borrow();
    }
}

SkTaskGroup::Enabler::Enabler(int threads) {
    if (threads) {
        fThreadPool = SkExecutor::MakeLIFOThreadPool(threads);
        SkExecutor::SetDefault(fThreadPool.get());
    }
}
