<commit_before>/*
 * compile both driver code and kernel code with nvcc, as in:
 * 			nvcc simple.c simple.cu
 */
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <string>
#include <vector>
#include <cmath>
#include <ctime>
using namespace std;

extern "C" void gpu_sobel(int *l_source_array, int *l_result_array,
                          int src_rows, int src_column_size);

#pragma pack(1)
typedef struct {
  char id[2];
  int file_size;
  int reserved;
  int offset;
} header_type;

#pragma pack(1)
typedef struct {
  int header_size;
  int width;
  int height;
  unsigned short int color_planes;
  unsigned short int color_depth;
  unsigned int compression;
  int image_size;
  int xresolution;
  int yresolution;
  int num_colors;
  int num_important_colors;
} information_type;

int main(int argc, char *argv[]) {
  header_type header;
  information_type information;
  string imageFileName, newImageFileName;
  unsigned char tempData[3];
  int row, col, row_bytes, padding;

  // prepare files
  // cout << "Original imagefile? ";
  imageFileName = argv[1];
  ifstream imageFile;
  imageFile.open(imageFileName.c_str(), ios::binary);
  if (!imageFile) {
    cerr << "file not found" << endl;
    exit(-1);
  }
  newImageFileName = argv[2];
  ofstream newImageFile;
  newImageFile.open(newImageFileName.c_str(), ios::binary);

  // read file header
  imageFile.read((char *)&header, sizeof(header_type));
  if (header.id[0] != 'B' || header.id[1] != 'M') {
    cerr << "Does not appear to be a .bmp file.  Goodbye." << endl;
    exit(-1);
  }
  // read/compute image information
  imageFile.read((char *)&information, sizeof(information_type));
  row_bytes = information.width * 3;
  padding = row_bytes % 4;
  if (padding)
    padding = 4 - padding;

  // extract image data, initialize vectors
  int src_rows = information.height + 2;
  int src_column_size = information.width + 2;
  int src_size = src_rows * src_column_size;
  int **data = (int **)malloc(src_size * sizeof(int *));
  for (row = 1; row <= information.height; row++) {
    data[row] = (int *)malloc(src_column_size * sizeof(int));
    for (col = 0; col <= information.width; col++) {
      if (col == 0) {
        data[row][0] = 0;
      } else {
        imageFile.read((char *)tempData, 3 * sizeof(unsigned char));
        data[row][col] = ((int)tempData[0]);
      }
    }
    // pad last column
    data[row][col + 1] = 0;
    if (padding)
      imageFile.read((char *)tempData, padding * sizeof(unsigned char));
  }

  // pad first & last row
  int last_row = src_rows - 1;
  data[0] = (int *)malloc(src_column_size * sizeof(int));
  data[last_row] = (int *)malloc(src_column_size * sizeof(int));
  for (col = 0; col < src_column_size; col++) {
    data[0][col] = 0;
    data[src_rows - 1][col] = 0;
  }
  std::cout << imageFileName << std::endl
            << "columns (x): " << information.width
            << " rows (y): " << information.height << std::endl;

  // linear-ize source array
  int dest_size = information.width * information.height;
  int *l_source_array = 0;
  l_source_array = new int[src_size];
  for (row = 0; row < src_rows; row++) {
    for (col = 0; col < src_column_size; col++) {
      l_source_array[row * src_column_size + col] = data[row][col];
    }
  }
  int *l_result_array = 0;
  l_result_array = new int[dest_size];
  std::cout << "src_rows: " << src_rows
            << " src_column_size: " << src_column_size;
  clock_t gpu_start = clock();
  gpu_sobel(l_source_array, l_result_array, src_rows, src_column_size);
  clock_t gpu_stop = clock();
  double elapsed_gpu = double(gpu_stop - gpu_start) / (CLOCKS_PER_SEC / 1000);

  std::cout << "GPU Time Taken (msec): " << elapsed_gpu << std::endl;

  int result_rows = information.height;
  int result_column_size = information.width;
  // de-linearize result array
  int **newData = (int **)malloc(dest_size * sizeof(int *));
  for (row = 0; row < result_rows; row++) {
    newData[row] = new int[result_column_size];
    for (col = 0; col < result_column_size; col++) {
      newData[row][col] = l_result_array[(row * result_column_size) + col];
    }
  }

  // write header to new image file
  newImageFile.write((char *)&header, sizeof(header_type));
  newImageFile.write((char *)&information, sizeof(information_type));

  // write new image data to new image file
  for (row = 0; row < information.height; row++) {
    for (col = 0; col < information.width; col++) {
      tempData[0] = (unsigned char)newData[row][col];
      tempData[1] = (unsigned char)newData[row][col];
      tempData[2] = (unsigned char)newData[row][col];
      newImageFile.write((char *)tempData, 3 * sizeof(unsigned char));
    }
    if (padding) {
      tempData[0] = 0;
      tempData[1] = 0;
      tempData[2] = 0;
      newImageFile.write((char *)tempData, padding * sizeof(unsigned char));
    }
  }
  cout << newImageFileName << " done." << endl;
  imageFile.close();
  newImageFile.close();

  return 0;
}
<commit_msg>more cleanup<commit_after>/*
 * compile both driver code and kernel code with nvcc, as in:
 * 			nvcc simple.c simple.cu
 */
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <string>
#include <vector>
#include <cmath>
#include <ctime>
using namespace std;

extern "C" void gpu_sobel(int *l_source_array, int *l_result_array,
                          int src_rows, int src_column_size);

#pragma pack(1)
typedef struct {
  char id[2];
  int file_size;
  int reserved;
  int offset;
} header_type;

#pragma pack(1)
typedef struct {
  int header_size;
  int width;
  int height;
  unsigned short int color_planes;
  unsigned short int color_depth;
  unsigned int compression;
  int image_size;
  int xresolution;
  int yresolution;
  int num_colors;
  int num_important_colors;
} information_type;

int main(int argc, char *argv[]) {
  header_type header;
  information_type information;
  string imageFileName, newImageFileName;
  unsigned char tempData[3];
  int row, col, row_bytes, padding;

  // prepare files
  // cout << "Original imagefile? ";
  imageFileName = argv[1];
  ifstream imageFile;
  imageFile.open(imageFileName.c_str(), ios::binary);
  if (!imageFile) {
    cerr << "file not found" << endl;
    exit(-1);
  }
  newImageFileName = argv[2];
  ofstream newImageFile;
  newImageFile.open(newImageFileName.c_str(), ios::binary);

  // read file header
  imageFile.read((char *)&header, sizeof(header_type));
  if (header.id[0] != 'B' || header.id[1] != 'M') {
    cerr << "Does not appear to be a .bmp file.  Goodbye." << endl;
    exit(-1);
  }
  // read/compute image information
  imageFile.read((char *)&information, sizeof(information_type));
  row_bytes = information.width * 3;
  padding = row_bytes % 4;
  if (padding)
    padding = 4 - padding;

  // extract image data, initialize vectors
  int src_rows = information.height + 2;
  int src_column_size = information.width + 2;
  int src_size = src_rows * src_column_size;
  int **data = (int **)malloc(src_size * sizeof(int *));
  for (row = 1; row <= information.height; row++) {
    data[row] = (int *)malloc(src_column_size * sizeof(int));
    for (col = 0; col <= information.width; col++) {
      if (col == 0) {
        data[row][0] = 0;
      } else {
        imageFile.read((char *)tempData, 3 * sizeof(unsigned char));
        data[row][col] = ((int)tempData[0]);
      }
    }
    // pad last column
    data[row][col + 1] = 0;
    if (padding)
      imageFile.read((char *)tempData, padding * sizeof(unsigned char));
  }

  // pad first & last row
  int last_row = src_rows - 1;
  data[0] = (int *)malloc(src_column_size * sizeof(int));
  data[last_row] = (int *)malloc(src_column_size * sizeof(int));
  for (col = 0; col < src_column_size; col++) {
    data[0][col] = 0;
    data[src_rows - 1][col] = 0;
  }
  std::cout << imageFileName << std::endl
            << "columns (x): " << information.width
            << " rows (y): " << information.height << std::endl;

  // linear-ize source array
  int dest_size = information.width * information.height;
  int *l_source_array = 0;
  l_source_array = new int[src_size];
  for (row = 0; row < src_rows; row++) {
    for (col = 0; col < src_column_size; col++) {
      l_source_array[row * src_column_size + col] = data[row][col];
    }
  }
  int *l_result_array = 0;
  l_result_array = new int[dest_size];
  clock_t gpu_start = clock();
  gpu_sobel(l_source_array, l_result_array, src_rows, src_column_size);
  clock_t gpu_stop = clock();
  double elapsed_gpu = double(gpu_stop - gpu_start) / (CLOCKS_PER_SEC / 1000);
  std::cout << "GPU Time Taken (msec): " << elapsed_gpu << std::endl;

  int result_rows = information.height;
  int result_column_size = information.width;
  // de-linearize result array
  int **newData = (int **)malloc(dest_size * sizeof(int *));
  for (row = 0; row < result_rows; row++) {
    newData[row] = new int[result_column_size];
    for (col = 0; col < result_column_size; col++) {
      newData[row][col] = l_result_array[(row * result_column_size) + col];
    }
  }

  // write header to new image file
  newImageFile.write((char *)&header, sizeof(header_type));
  newImageFile.write((char *)&information, sizeof(information_type));

  // write new image data to new image file
  for (row = 0; row < information.height; row++) {
    for (col = 0; col < information.width; col++) {
      tempData[0] = (unsigned char)newData[row][col];
      tempData[1] = (unsigned char)newData[row][col];
      tempData[2] = (unsigned char)newData[row][col];
      newImageFile.write((char *)tempData, 3 * sizeof(unsigned char));
    }
    if (padding) {
      tempData[0] = 0;
      tempData[1] = 0;
      tempData[2] = 0;
      newImageFile.write((char *)tempData, padding * sizeof(unsigned char));
    }
  }
  cout << newImageFileName << " done." << endl;
  imageFile.close();
  newImageFile.close();

  return 0;
}
<|endoftext|>


<commit_before>#include "otc/otcli.h"
using namespace otc;
typedef RootedTree<RTNodeNoData, RTreeNoData> Tree_t;
struct PerTreeStats {
    OttIdSet leafSet; // full leaf set
    OttIdSet leavesInIngroupSet; // leaves whose parents are not the root
    std::size_t numInformativeGroups;
};
struct PerSubproblemStats {
    std::list<PerTreeStats> pts;
};
struct SubproblemStatsState : public TaxonomyDependentTreeProcessor<TreeMappedEmptyNodes> {
    int numErrors;
    std::map<std::string, PerSubproblemStats> name2Stats;

};

// global - will inhibit parallelization, but this is not time-critical...
static SubproblemStatsState * gSubproblemStatsState = nullptr;
int summarize(OTCLI & otCLI);
template<typename T>
bool recordSubproblemStats(OTCLI & otCLI, std::unique_ptr<T> tree) {
    auto & pss = gSubproblemStatsState->name2Stats[otCLI.currentFilename];
    pss.pts.push_back(PerTreeStats());
    auto & pts = *(pss.pts.rbegin());
    auto root = tree->getRoot();
    for (auto nd : iter_node_const(*tree)) {
        if (nd == root) {
            continue;
        }
        if (nd->isTip()) {
            assert(nd->hasOttId());
            const auto ottId = nd->getOttId();
            pts.leafSet.insert(ottId);
            if (nd->getParent() != root) {
                pts.leavesInIngroupSet.insert(ottId);
            }
        } else {
            pts.numInformativeGroups += 1;
        }
    }
    return true;
}

int summarize(OTCLI & otCLI) {
    auto & nts = gSubproblemStatsState->name2Stats;
    otCLI.err << nts.size() << " subproblems\n";
    otCLI.out << "Subproblem" ;
    otCLI.out << '\t' << "InSp";
    otCLI.out << '\t' << "LSS";
    otCLI.out << '\t' << "ILSS";
    otCLI.out << '\t' << "TreeSummaryName";
    otCLI.out << '\n';

    for (auto ntsP : nts) {
        const auto & name = ntsP.first;
        const auto & pss = ntsP.second;
        std::size_t i = 0;
        OttIdSet leafSet;
        OttIdSet leavesInIngroupSet;
        std::size_t numInformativeGroups = 0;
        OttIdSet allButTaxLeafSet;
        OttIdSet allButTaxLeavesInIngroupSet;
        std::size_t allButTaxNumInformativeGroups = 0;

        for (const auto & pts : pss.pts) {
            allButTaxLeafSet = leafSet;
            allButTaxLeavesInIngroupSet = leavesInIngroupSet;
            allButTaxNumInformativeGroups = numInformativeGroups;
            otCLI.out << name ;
            otCLI.out << '\t' << pts.numInformativeGroups;
            otCLI.out << '\t' << pts.leafSet.size();
            otCLI.out << '\t' << pts.leavesInIngroupSet.size();
            otCLI.out << '\t' << i++;
            otCLI.out << '\n';
            leafSet.insert(begin(pts.leafSet), end(pts.leafSet));
            leavesInIngroupSet.insert(begin(pts.leavesInIngroupSet), end(pts.leavesInIngroupSet));
            numInformativeGroups += pts.numInformativeGroups;
        }
        otCLI.out << name ;
        otCLI.out << '\t' << allButTaxNumInformativeGroups;
        otCLI.out << '\t' << allButTaxLeafSet.size();
        otCLI.out << '\t' << allButTaxLeavesInIngroupSet.size();
        otCLI.out << '\t' << "Phylo-only";
        otCLI.out << '\n';
        otCLI.out << name ;
        otCLI.out << '\t' << numInformativeGroups;
        otCLI.out << '\t' << leafSet.size();
        otCLI.out << '\t' << leavesInIngroupSet.size();
        otCLI.out << '\t' << "Total";
        otCLI.out << '\n';
   }
    return 0;
}

int main(int argc, char *argv[]) {
    OTCLI otCLI("otc-subproblem-stats",
                "takes at series of tree files. Each is treated as a subproblem.\n" \
                "For each subproblem there is tab-separated report for:\n" \
                "    Subproblem name\n" \
                "    InSp = # of informative (nontrivial) splits\n" \
                "    LSS = size of the leaf label set\n" \
                "    ILSS = size of the set of labels included in at least one \"ingroup\"\n" \
                "    TreeSummaryName = tree index or summary name\n" \
                "where the summary name can be Phylo-only or Total. \n" \
                "  \"Total\" summarizes info all trees in the file (including the taxonomy).\n" \
                "  \"Phylo-only\" former summarizes all of the phylogenetic inputs",
                "taxonomy.tre supertree/step_7_scratch/export-sub-temp/ott1000236.tre supertree/step_7_scratch/export-sub-temp/ott1000250.tre");
    std::function<bool (OTCLI &, std::unique_ptr<Tree_t>)> rss = recordSubproblemStats<Tree_t>;
    SubproblemStatsState sss;
    gSubproblemStatsState = &sss;
    SubproblemStatsState proc;
    return treeProcessingMain<Tree_t>(otCLI, argc, argv, rss, summarize, 1);

}
<commit_msg>reporting the number of trees in each subproblem for convenience<commit_after>#include "otc/otcli.h"
using namespace otc;
typedef RootedTree<RTNodeNoData, RTreeNoData> Tree_t;
struct PerTreeStats {
    OttIdSet leafSet; // full leaf set
    OttIdSet leavesInIngroupSet; // leaves whose parents are not the root
    std::size_t numInformativeGroups;
};
struct PerSubproblemStats {
    std::list<PerTreeStats> pts;
};
struct SubproblemStatsState : public TaxonomyDependentTreeProcessor<TreeMappedEmptyNodes> {
    int numErrors;
    std::map<std::string, PerSubproblemStats> name2Stats;

};

// global - will inhibit parallelization, but this is not time-critical...
static SubproblemStatsState * gSubproblemStatsState = nullptr;
int summarize(OTCLI & otCLI);
template<typename T>
bool recordSubproblemStats(OTCLI & otCLI, std::unique_ptr<T> tree) {
    auto & pss = gSubproblemStatsState->name2Stats[otCLI.currentFilename];
    pss.pts.push_back(PerTreeStats());
    auto & pts = *(pss.pts.rbegin());
    auto root = tree->getRoot();
    for (auto nd : iter_node_const(*tree)) {
        if (nd == root) {
            continue;
        }
        if (nd->isTip()) {
            assert(nd->hasOttId());
            const auto ottId = nd->getOttId();
            pts.leafSet.insert(ottId);
            if (nd->getParent() != root) {
                pts.leavesInIngroupSet.insert(ottId);
            }
        } else {
            pts.numInformativeGroups += 1;
        }
    }
    return true;
}

int summarize(OTCLI & otCLI) {
    auto & nts = gSubproblemStatsState->name2Stats;
    otCLI.err << nts.size() << " subproblems\n";
    otCLI.out << "Subproblem" ;
    otCLI.out << '\t' << "InSp";
    otCLI.out << '\t' << "LSS";
    otCLI.out << '\t' << "ILSS";
    otCLI.out << '\t' << "NT";
    otCLI.out << '\t' << "TreeSummaryName";
    otCLI.out << '\n';

    for (auto ntsP : nts) {
        const auto & name = ntsP.first;
        const auto & pss = ntsP.second;
        std::size_t i = 0;
        OttIdSet leafSet;
        OttIdSet leavesInIngroupSet;
        std::size_t numInformativeGroups = 0;
        OttIdSet allButTaxLeafSet;
        OttIdSet allButTaxLeavesInIngroupSet;
        std::size_t allButTaxNumInformativeGroups = 0;

        for (const auto & pts : pss.pts) {
            allButTaxLeafSet = leafSet;
            allButTaxLeavesInIngroupSet = leavesInIngroupSet;
            allButTaxNumInformativeGroups = numInformativeGroups;
            otCLI.out << name ;
            otCLI.out << '\t' << pts.numInformativeGroups;
            otCLI.out << '\t' << pts.leafSet.size();
            otCLI.out << '\t' << pts.leavesInIngroupSet.size();
            otCLI.out << '\t'; // number of trees
            otCLI.out << '\t' << i++;
            otCLI.out << '\n';
            leafSet.insert(begin(pts.leafSet), end(pts.leafSet));
            leavesInIngroupSet.insert(begin(pts.leavesInIngroupSet), end(pts.leavesInIngroupSet));
            numInformativeGroups += pts.numInformativeGroups;
        }
        otCLI.out << name ;
        otCLI.out << '\t' << allButTaxNumInformativeGroups;
        otCLI.out << '\t' << allButTaxLeafSet.size();
        otCLI.out << '\t' << allButTaxLeavesInIngroupSet.size();
        otCLI.out << '\t' << pss.pts.size() - 1;
        otCLI.out << '\t' << "Phylo-only";
        otCLI.out << '\n';
        otCLI.out << name ;
        otCLI.out << '\t' << numInformativeGroups;
        otCLI.out << '\t' << leafSet.size();
        otCLI.out << '\t' << leavesInIngroupSet.size();
        otCLI.out << '\t' << pss.pts.size();
        otCLI.out << '\t' << "Total";
        otCLI.out << '\n';
   }
    return 0;
}

int main(int argc, char *argv[]) {
    OTCLI otCLI("otc-subproblem-stats",
                "takes at series of tree files. Each is treated as a subproblem.\n" \
                "For each subproblem there is tab-separated report for:\n" \
                "    Subproblem name\n" \
                "    InSp = # of informative (nontrivial) splits\n" \
                "    LSS = size of the leaf label set\n" \
                "    ILSS = size of the set of labels included in at least one \"ingroup\"\n" \
                "    NT = The number of trees.\n" \
                "    TreeSummaryName = tree index or summary name\n" \
                "where the summary name can be Phylo-only or Total. \n" \
                "  \"Total\" summarizes info all trees in the file (including the taxonomy).\n" \
                "  \"Phylo-only\" former summarizes all of the phylogenetic inputs",
                "taxonomy.tre supertree/step_7_scratch/export-sub-temp/ott1000236.tre supertree/step_7_scratch/export-sub-temp/ott1000250.tre");
    std::function<bool (OTCLI &, std::unique_ptr<Tree_t>)> rss = recordSubproblemStats<Tree_t>;
    SubproblemStatsState sss;
    gSubproblemStatsState = &sss;
    SubproblemStatsState proc;
    return treeProcessingMain<Tree_t>(otCLI, argc, argv, rss, summarize, 1);

}
<|endoftext|>


<commit_before>/*
 * Copyright 2018 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SlideDir.h"

#include "SkAnimTimer.h"
#include "SkCanvas.h"
#include "SkMakeUnique.h"
#include "SkSGColor.h"
#include "SkSGDraw.h"
#include "SkSGGroup.h"
#include "SkSGRect.h"
#include "SkSGRenderNode.h"
#include "SkSGScene.h"
#include "SkSGText.h"
#include "SkSGTransform.h"
#include "SkTypeface.h"

#include <cmath>

namespace {

static constexpr float  kAspectRatio   = 1.5f;
static constexpr float  kLabelSize     = 12.0f;
static constexpr SkSize kPadding       = { 12.0f , 24.0f };
static constexpr SkSize kFocusInset    = { 100.0f, 100.0f };
static constexpr float  kFocusDuration = 250;

// TODO: better unfocus binding?
static constexpr SkUnichar kUnfocusKey = ' ';

class SlideAdapter final : public sksg::RenderNode {
public:
    explicit SlideAdapter(sk_sp<Slide> slide)
        : fSlide(std::move(slide)) {
        SkASSERT(fSlide);
    }

    std::unique_ptr<sksg::Animator> makeForwardingAnimator() {
        // Trivial sksg::Animator -> skottie::Animation tick adapter
        class ForwardingAnimator final : public sksg::Animator {
        public:
            explicit ForwardingAnimator(sk_sp<SlideAdapter> adapter)
                : fAdapter(std::move(adapter)) {}

        protected:
            void onTick(float t) override {
                fAdapter->tick(SkScalarRoundToInt(t));
            }

        private:
            sk_sp<SlideAdapter> fAdapter;
        };

        return skstd::make_unique<ForwardingAnimator>(sk_ref_sp(this));
    }

protected:
    SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
        const auto isize = fSlide->getDimensions();
        return SkRect::MakeIWH(isize.width(), isize.height());
    }

    void onRender(SkCanvas* canvas) const override {
        fSlide->draw(canvas);
    }

private:
    void tick(SkMSec t) {
        fSlide->animate(SkAnimTimer(0, t * 1e6, SkAnimTimer::kRunning_State));
        this->invalidate();
    }

    const sk_sp<Slide> fSlide;

    using INHERITED = sksg::RenderNode;
};

SkMatrix SlideMatrix(const sk_sp<Slide>& slide, const SkRect& dst) {
    const auto slideSize = slide->getDimensions();
    return SkMatrix::MakeRectToRect(SkRect::MakeIWH(slideSize.width(), slideSize.height()),
                                    dst,
                                    SkMatrix::kCenter_ScaleToFit);
}

} // namespace

struct SlideDir::Rec {
    sk_sp<Slide>           fSlide;
    sk_sp<sksg::Transform> fTransform;
    SkRect                 fRect;
};

class SlideDir::FocusController final : public sksg::Animator {
public:
    FocusController(const SlideDir* dir, const SkRect& focusRect)
        : fDir(dir)
        , fRect(focusRect)
        , fTarget(nullptr)
        , fState(State::kIdle) {}

    bool hasFocus() const { return fState == State::kFocused; }

    void startFocus(const Rec* target) {
        fTarget = target;
        fM0 = SlideMatrix(fTarget->fSlide, fTarget->fRect);
        fM1 = SlideMatrix(fTarget->fSlide, fRect);

        fTimeBase = 0;
        fState = State::kFocusing;
    }

    void startUnfocus() {
        SkASSERT(fTarget);

        SkTSwap(fM1, fM0);

        fTimeBase = 0;
        fState = State::kUnfocusing;
    }

    bool onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state, uint32_t modifiers) {
        SkASSERT(fTarget);

        // Map coords to slide space.
        const auto xform = SkMatrix::MakeRectToRect(fRect,
                                                    SkRect::MakeSize(fDir->fWinSize),
                                                    SkMatrix::kCenter_ScaleToFit);
        const auto pt = xform.mapXY(x, y);

        return fTarget->fSlide->onMouse(pt.x(), pt.y(), state, modifiers);
    }

    bool onChar(SkUnichar c) {
        SkASSERT(fTarget);

        return fTarget->fSlide->onChar(c);
    }

protected:
    void onTick(float t) {
        if (!this->isAnimating())
            return;

        if (!fTimeBase) {
            fTimeBase = t;
        }

        const auto rel_t = SkTPin((t - fTimeBase) / kFocusDuration, 0.0f, 1.0f);

        SkMatrix m;
        for (int i = 0; i < 9; ++i) {
            m[i] = fM0[i] + rel_t * (fM1[i] - fM0[i]);
        }

        SkASSERT(fTarget);
        fTarget->fTransform->getMatrix()->setMatrix(m);

        if (rel_t < 1)
            return;

        switch (fState) {
        case State::kFocusing:
            fState = State::kFocused;
            break;
        case State::kUnfocusing:
            fState  = State::kIdle;
            break;

        case State::kIdle:
        case State::kFocused:
            SkASSERT(false);
            break;
        }
    }

private:
    enum class State {
        kIdle,
        kFocusing,
        kUnfocusing,
        kFocused,
    };

    bool isAnimating() const { return fState == State::kFocusing || fState == State::kUnfocusing; }

    const SlideDir* fDir;
    const SkRect    fRect;
    const Rec*      fTarget;

    SkMatrix        fM0       = SkMatrix::I(),
                    fM1       = SkMatrix::I();
    float           fTimeBase = 0;
    State           fState    = State::kIdle;

    using INHERITED = sksg::Animator;
};

SlideDir::SlideDir(const SkString& name, SkTArray<sk_sp<Slide>, true>&& slides, int columns)
    : fSlides(std::move(slides))
    , fColumns(columns) {
    fName = name;
}

static sk_sp<sksg::RenderNode> MakeLabel(const SkString& txt,
                                         const SkPoint& pos,
                                         const SkMatrix& dstXform) {
    const auto size = kLabelSize / std::sqrt(dstXform.getScaleX() * dstXform.getScaleY());
    auto text = sksg::Text::Make(nullptr, txt);
    text->setFlags(SkPaint::kAntiAlias_Flag);
    text->setSize(size);
    text->setAlign(SkPaint::kCenter_Align);
    text->setPosition(pos + SkPoint::Make(0, size));

    return sksg::Draw::Make(std::move(text), sksg::Color::Make(SK_ColorBLACK));
}

void SlideDir::load(SkScalar winWidth, SkScalar winHeight) {
    // Build a global scene using transformed animation fragments:
    //
    // [Group(root)]
    //     [Transform]
    //         [Group]
    //             [AnimationWrapper]
    //             [Draw]
    //                 [Text]
    //                 [Color]
    //     [Transform]
    //         [Group]
    //             [AnimationWrapper]
    //             [Draw]
    //                 [Text]
    //                 [Color]
    //     ...
    //

    fWinSize = SkSize::Make(winWidth, winHeight);
    const auto  cellWidth =  winWidth / fColumns;
    fCellSize = SkSize::Make(cellWidth, cellWidth / kAspectRatio);

    sksg::AnimatorList sceneAnimators;
    fRoot = sksg::Group::Make();

    for (int i = 0; i < fSlides.count(); ++i) {
        const auto& slide     = fSlides[i];
        slide->load(winWidth, winHeight);

        const auto  slideSize = slide->getDimensions();
        const auto  cell      = SkRect::MakeXYWH(fCellSize.width()  * (i % fColumns),
                                                 fCellSize.height() * (i / fColumns),
                                                 fCellSize.width(),
                                                 fCellSize.height()),
                    slideRect = cell.makeInset(kPadding.width(), kPadding.height());

        auto slideMatrix = SlideMatrix(slide, slideRect);
        auto adapter     = sk_make_sp<SlideAdapter>(slide);
        auto slideGrp    = sksg::Group::Make();
        slideGrp->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeIWH(slideSize.width(),
                                                                             slideSize.height())),
                                            sksg::Color::Make(0xfff0f0f0)));
        slideGrp->addChild(adapter);
        slideGrp->addChild(MakeLabel(slide->getName(),
                                     SkPoint::Make(slideSize.width() / 2, slideSize.height()),
                                     slideMatrix));
        auto slideTransform = sksg::Transform::Make(std::move(slideGrp), slideMatrix);

        sceneAnimators.push_back(adapter->makeForwardingAnimator());

        fRoot->addChild(slideTransform);
        fRecs.push_back({ slide, slideTransform, slideRect });
    }

    fScene = sksg::Scene::Make(fRoot, std::move(sceneAnimators));

    const auto focusRect = SkRect::MakeSize(fWinSize).makeInset(kFocusInset.width(),
                                                                kFocusInset.height());
    fFocusController = skstd::make_unique<FocusController>(this, focusRect);
}

void SlideDir::unload() {
    for (const auto& slide : fSlides) {
        slide->unload();
    }

    fRecs.reset();
    fScene.reset();
    fFocusController.reset();
    fRoot.reset();
    fTimeBase = 0;
}

SkISize SlideDir::getDimensions() const {
    return  SkSize::Make(fWinSize.width(),
                         fCellSize.height() * (fSlides.count() / fColumns)).toCeil();
}

void SlideDir::draw(SkCanvas* canvas) {
    fScene->render(canvas);
}

bool SlideDir::animate(const SkAnimTimer& timer) {
    if (fTimeBase == 0) {
        // Reset the animation time.
        fTimeBase = timer.msec();
    }

    const auto t = timer.msec() - fTimeBase;
    fScene->animate(t);
    fFocusController->tick(t);

    return true;
}

bool SlideDir::onChar(SkUnichar c) {
    if (fFocusController->hasFocus()) {
        if (c == kUnfocusKey) {
            fFocusController->startUnfocus();
            return true;
        }
        return fFocusController->onChar(c);
    }

    return false;
}

bool SlideDir::onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state,
                       uint32_t modifiers) {
    if (state == sk_app::Window::kMove_InputState || modifiers)
        return false;

    if (fFocusController->hasFocus()) {
        return fFocusController->onMouse(x, y, state, modifiers);
    }

    const auto* cell = this->findCell(x, y);
    if (!cell)
        return false;

    static constexpr SkScalar kClickMoveTolerance = 4;

    switch (state) {
    case sk_app::Window::kDown_InputState:
        fTrackingCell = cell;
        fTrackingPos = SkPoint::Make(x, y);
        break;
    case sk_app::Window::kUp_InputState:
        if (cell == fTrackingCell &&
            SkPoint::Distance(fTrackingPos, SkPoint::Make(x, y)) < kClickMoveTolerance) {

            // Move the slide cell to front.
            fRoot->removeChild(cell->fTransform);
            fRoot->addChild(cell->fTransform);

            fFocusController->startFocus(cell);
        }
        break;
    default:
        break;
    }

    return false;
}

const SlideDir::Rec* SlideDir::findCell(float x, float y) const {
    // TODO: use SG hit testing instead of layout info?
    const auto size = this->getDimensions();
    if (x < 0 || y < 0 || x >= size.width() || y >= size.height()) {
        return nullptr;
    }

    const int col = static_cast<int>(x / fCellSize.width()),
              row = static_cast<int>(y / fCellSize.height()),
              idx = row * fColumns + col;

    return idx <= fRecs.count() ? &fRecs[idx] : nullptr;
}
<commit_msg>SlideDir focus tweaks<commit_after>/*
 * Copyright 2018 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SlideDir.h"

#include "SkAnimTimer.h"
#include "SkCanvas.h"
#include "SkCubicMap.h"
#include "SkMakeUnique.h"
#include "SkSGColor.h"
#include "SkSGDraw.h"
#include "SkSGGroup.h"
#include "SkSGRect.h"
#include "SkSGRenderNode.h"
#include "SkSGScene.h"
#include "SkSGText.h"
#include "SkSGTransform.h"
#include "SkTypeface.h"

#include <cmath>

namespace {

static constexpr float  kAspectRatio   = 1.5f;
static constexpr float  kLabelSize     = 12.0f;
static constexpr SkSize kPadding       = { 12.0f , 24.0f };

static constexpr float   kFocusDuration = 500;
static constexpr SkSize  kFocusInset    = { 100.0f, 100.0f };
static constexpr SkPoint kFocusCtrl0    = {   0.3f,   1.0f };
static constexpr SkPoint kFocusCtrl1    = {   0.0f,   1.0f };
static constexpr SkColor kFocusShade    = 0xa0000000;

// TODO: better unfocus binding?
static constexpr SkUnichar kUnfocusKey = ' ';

class SlideAdapter final : public sksg::RenderNode {
public:
    explicit SlideAdapter(sk_sp<Slide> slide)
        : fSlide(std::move(slide)) {
        SkASSERT(fSlide);
    }

    std::unique_ptr<sksg::Animator> makeForwardingAnimator() {
        // Trivial sksg::Animator -> skottie::Animation tick adapter
        class ForwardingAnimator final : public sksg::Animator {
        public:
            explicit ForwardingAnimator(sk_sp<SlideAdapter> adapter)
                : fAdapter(std::move(adapter)) {}

        protected:
            void onTick(float t) override {
                fAdapter->tick(SkScalarRoundToInt(t));
            }

        private:
            sk_sp<SlideAdapter> fAdapter;
        };

        return skstd::make_unique<ForwardingAnimator>(sk_ref_sp(this));
    }

protected:
    SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
        const auto isize = fSlide->getDimensions();
        return SkRect::MakeIWH(isize.width(), isize.height());
    }

    void onRender(SkCanvas* canvas) const override {
        fSlide->draw(canvas);
    }

private:
    void tick(SkMSec t) {
        fSlide->animate(SkAnimTimer(0, t * 1e6, SkAnimTimer::kRunning_State));
        this->invalidate();
    }

    const sk_sp<Slide> fSlide;

    using INHERITED = sksg::RenderNode;
};

SkMatrix SlideMatrix(const sk_sp<Slide>& slide, const SkRect& dst) {
    const auto slideSize = slide->getDimensions();
    return SkMatrix::MakeRectToRect(SkRect::MakeIWH(slideSize.width(), slideSize.height()),
                                    dst,
                                    SkMatrix::kCenter_ScaleToFit);
}

} // namespace

struct SlideDir::Rec {
    sk_sp<Slide>           fSlide;
    sk_sp<sksg::Transform> fTransform;
    SkRect                 fRect;
};

class SlideDir::FocusController final : public sksg::Animator {
public:
    FocusController(const SlideDir* dir, const SkRect& focusRect)
        : fDir(dir)
        , fRect(focusRect)
        , fTarget(nullptr)
        , fState(State::kIdle) {
        fMap.setPts(kFocusCtrl1, kFocusCtrl0);

        fShadePaint = sksg::Color::Make(kFocusShade);
        fShade = sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeSize(dir->fWinSize)), fShadePaint);
    }

    bool hasFocus() const { return fState == State::kFocused; }

    void startFocus(const Rec* target) {
        if (fState != State::kIdle)
            return;

        fTarget = target;

        // Move the shade & slide to front.
        fDir->fRoot->removeChild(fTarget->fTransform);
        fDir->fRoot->addChild(fShade);
        fDir->fRoot->addChild(fTarget->fTransform);

        fM0 = SlideMatrix(fTarget->fSlide, fTarget->fRect);
        fM1 = SlideMatrix(fTarget->fSlide, fRect);

        fOpacity0 = 0;
        fOpacity1 = 1;

        fTimeBase = 0;
        fState = State::kFocusing;

        // Push initial state to the scene graph.
        this->onTick(fTimeBase);
    }

    void startUnfocus() {
        SkASSERT(fTarget);

        SkTSwap(fM0, fM1);
        SkTSwap(fOpacity0, fOpacity1);

        fTimeBase = 0;
        fState = State::kUnfocusing;
    }

    bool onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state, uint32_t modifiers) {
        SkASSERT(fTarget);

        // TODO: no SkRect::contains(SkPoint) ?!
        if (x < fRect.fLeft || x > fRect.fRight || y < fRect.fTop || y > fRect.fBottom) {
            this->startUnfocus();
            return true;
        }

        // Map coords to slide space.
        const auto xform = SkMatrix::MakeRectToRect(fRect,
                                                    SkRect::MakeSize(fDir->fWinSize),
                                                    SkMatrix::kCenter_ScaleToFit);
        const auto pt = xform.mapXY(x, y);

        return fTarget->fSlide->onMouse(pt.x(), pt.y(), state, modifiers);
    }

    bool onChar(SkUnichar c) {
        SkASSERT(fTarget);

        return fTarget->fSlide->onChar(c);
    }

protected:
    void onTick(float t) {
        if (!this->isAnimating())
            return;

        if (!fTimeBase) {
            fTimeBase = t;
        }

        const auto rel_t = (t - fTimeBase) / kFocusDuration,
                   map_t = SkTPin(fMap.computeYFromX(rel_t), 0.0f, 1.0f);

        SkMatrix m;
        for (int i = 0; i < 9; ++i) {
            m[i] = fM0[i] + map_t * (fM1[i] - fM0[i]);
        }

        SkASSERT(fTarget);
        fTarget->fTransform->getMatrix()->setMatrix(m);

        const auto shadeOpacity = fOpacity0 + map_t * (fOpacity1 - fOpacity0);
        fShadePaint->setOpacity(shadeOpacity);

        if (rel_t < 1)
            return;

        switch (fState) {
        case State::kFocusing:
            fState = State::kFocused;
            break;
        case State::kUnfocusing:
            fState  = State::kIdle;
            fDir->fRoot->removeChild(fShade);
            break;

        case State::kIdle:
        case State::kFocused:
            SkASSERT(false);
            break;
        }
    }

private:
    enum class State {
        kIdle,
        kFocusing,
        kUnfocusing,
        kFocused,
    };

    bool isAnimating() const { return fState == State::kFocusing || fState == State::kUnfocusing; }

    const SlideDir*         fDir;
    const SkRect            fRect;
    const Rec*              fTarget;

    SkCubicMap              fMap;
    sk_sp<sksg::RenderNode> fShade;
    sk_sp<sksg::PaintNode>  fShadePaint;

    SkMatrix        fM0       = SkMatrix::I(),
                    fM1       = SkMatrix::I();
    float           fOpacity0 = 0,
                    fOpacity1 = 1,
                    fTimeBase = 0;
    State           fState    = State::kIdle;

    using INHERITED = sksg::Animator;
};

SlideDir::SlideDir(const SkString& name, SkTArray<sk_sp<Slide>, true>&& slides, int columns)
    : fSlides(std::move(slides))
    , fColumns(columns) {
    fName = name;
}

static sk_sp<sksg::RenderNode> MakeLabel(const SkString& txt,
                                         const SkPoint& pos,
                                         const SkMatrix& dstXform) {
    const auto size = kLabelSize / std::sqrt(dstXform.getScaleX() * dstXform.getScaleY());
    auto text = sksg::Text::Make(nullptr, txt);
    text->setFlags(SkPaint::kAntiAlias_Flag);
    text->setSize(size);
    text->setAlign(SkPaint::kCenter_Align);
    text->setPosition(pos + SkPoint::Make(0, size));

    return sksg::Draw::Make(std::move(text), sksg::Color::Make(SK_ColorBLACK));
}

void SlideDir::load(SkScalar winWidth, SkScalar winHeight) {
    // Build a global scene using transformed animation fragments:
    //
    // [Group(root)]
    //     [Transform]
    //         [Group]
    //             [AnimationWrapper]
    //             [Draw]
    //                 [Text]
    //                 [Color]
    //     [Transform]
    //         [Group]
    //             [AnimationWrapper]
    //             [Draw]
    //                 [Text]
    //                 [Color]
    //     ...
    //

    fWinSize = SkSize::Make(winWidth, winHeight);
    const auto  cellWidth =  winWidth / fColumns;
    fCellSize = SkSize::Make(cellWidth, cellWidth / kAspectRatio);

    sksg::AnimatorList sceneAnimators;
    fRoot = sksg::Group::Make();

    for (int i = 0; i < fSlides.count(); ++i) {
        const auto& slide     = fSlides[i];
        slide->load(winWidth, winHeight);

        const auto  slideSize = slide->getDimensions();
        const auto  cell      = SkRect::MakeXYWH(fCellSize.width()  * (i % fColumns),
                                                 fCellSize.height() * (i / fColumns),
                                                 fCellSize.width(),
                                                 fCellSize.height()),
                    slideRect = cell.makeInset(kPadding.width(), kPadding.height());

        auto slideMatrix = SlideMatrix(slide, slideRect);
        auto adapter     = sk_make_sp<SlideAdapter>(slide);
        auto slideGrp    = sksg::Group::Make();
        slideGrp->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeIWH(slideSize.width(),
                                                                             slideSize.height())),
                                            sksg::Color::Make(0xfff0f0f0)));
        slideGrp->addChild(adapter);
        slideGrp->addChild(MakeLabel(slide->getName(),
                                     SkPoint::Make(slideSize.width() / 2, slideSize.height()),
                                     slideMatrix));
        auto slideTransform = sksg::Transform::Make(std::move(slideGrp), slideMatrix);

        sceneAnimators.push_back(adapter->makeForwardingAnimator());

        fRoot->addChild(slideTransform);
        fRecs.push_back({ slide, slideTransform, slideRect });
    }

    fScene = sksg::Scene::Make(fRoot, std::move(sceneAnimators));

    const auto focusRect = SkRect::MakeSize(fWinSize).makeInset(kFocusInset.width(),
                                                                kFocusInset.height());
    fFocusController = skstd::make_unique<FocusController>(this, focusRect);
}

void SlideDir::unload() {
    for (const auto& slide : fSlides) {
        slide->unload();
    }

    fRecs.reset();
    fScene.reset();
    fFocusController.reset();
    fRoot.reset();
    fTimeBase = 0;
}

SkISize SlideDir::getDimensions() const {
    return  SkSize::Make(fWinSize.width(),
                         fCellSize.height() * (fSlides.count() / fColumns)).toCeil();
}

void SlideDir::draw(SkCanvas* canvas) {
    fScene->render(canvas);
}

bool SlideDir::animate(const SkAnimTimer& timer) {
    if (fTimeBase == 0) {
        // Reset the animation time.
        fTimeBase = timer.msec();
    }

    const auto t = timer.msec() - fTimeBase;
    fScene->animate(t);
    fFocusController->tick(t);

    return true;
}

bool SlideDir::onChar(SkUnichar c) {
    if (fFocusController->hasFocus()) {
        if (c == kUnfocusKey) {
            fFocusController->startUnfocus();
            return true;
        }
        return fFocusController->onChar(c);
    }

    return false;
}

bool SlideDir::onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state,
                       uint32_t modifiers) {
    if (state == sk_app::Window::kMove_InputState || modifiers)
        return false;

    if (fFocusController->hasFocus()) {
        return fFocusController->onMouse(x, y, state, modifiers);
    }

    const auto* cell = this->findCell(x, y);
    if (!cell)
        return false;

    static constexpr SkScalar kClickMoveTolerance = 4;

    switch (state) {
    case sk_app::Window::kDown_InputState:
        fTrackingCell = cell;
        fTrackingPos = SkPoint::Make(x, y);
        break;
    case sk_app::Window::kUp_InputState:
        if (cell == fTrackingCell &&
            SkPoint::Distance(fTrackingPos, SkPoint::Make(x, y)) < kClickMoveTolerance) {
            fFocusController->startFocus(cell);
        }
        break;
    default:
        break;
    }

    return false;
}

const SlideDir::Rec* SlideDir::findCell(float x, float y) const {
    // TODO: use SG hit testing instead of layout info?
    const auto size = this->getDimensions();
    if (x < 0 || y < 0 || x >= size.width() || y >= size.height()) {
        return nullptr;
    }

    const int col = static_cast<int>(x / fCellSize.width()),
              row = static_cast<int>(y / fCellSize.height()),
              idx = row * fColumns + col;

    return idx <= fRecs.count() ? &fRecs[idx] : nullptr;
}
<|endoftext|>


<commit_before>/* editorwindow.cpp
 * implementation fle for the main editor window */

#include <stdio.h>
#include <QMouseEvent>
#include <QMessageBox>
#include <QFileDialog>
#include "editorwindow.h"
#include "newdialog.h"
#include "ui_mainwindow.h"
#include "ui_newmap.h"

/* shows a generic popup message */
void popup(const char* message) {
    QMessageBox msgBox;
    msgBox.setWindowTitle("GBA Tile Editor");
    msgBox.setText(message);
    msgBox.exec();
}

/* constructor which stores the app so we can quit later */
EditorWindow::EditorWindow(QApplication* app) {
    this->app = app;
    map = NULL;
    tiles_loaded = false;
    setWindowTitle("GBA Tile Editor");
    current_tile = 0;
    just_saved = true;
}

/* set the map and palette areas */
void EditorWindow::set_areas( QGraphicsScene* map, QGraphicsScene* palette) {
    this->map_scene = map;
    this->palette_scene = palette;
}

/* set up all the signal and slot triggers for each action */
void EditorWindow::setup_triggers(Ui_MainWindow* ui) {
    /* the main window actions */
    QObject::connect(ui->actionNew, SIGNAL(triggered()), this, SLOT(on_new()));
    QObject::connect(ui->actionOpen, SIGNAL(triggered()), this, SLOT(on_open()));
    QObject::connect(ui->actionSave, SIGNAL(triggered()), this, SLOT(on_save()));
    QObject::connect(ui->actionSave_As, SIGNAL(triggered()), this, SLOT(on_save_as()));
    QObject::connect(ui->actionQuit, SIGNAL(triggered()), this, SLOT(on_quit()));
    QObject::connect(ui->actionUndo, SIGNAL(triggered()), this, SLOT(on_undo()));
    QObject::connect(ui->actionRedo, SIGNAL(triggered()), this, SLOT(on_redo()));
    QObject::connect(ui->actionShow_Grid, SIGNAL(triggered()), this, SLOT(on_grid()));
    QObject::connect(ui->actionCut, SIGNAL(triggered()), this, SLOT(on_cut()));
    QObject::connect(ui->actionCopy, SIGNAL(triggered()), this, SLOT(on_copy()));
    QObject::connect(ui->actionPaste, SIGNAL(triggered()), this, SLOT(on_paste()));
    QObject::connect(ui->actionZoom_In, SIGNAL(triggered()), this, SLOT(on_zoom_in()));
    QObject::connect(ui->actionZoom_Out, SIGNAL(triggered()), this, SLOT(on_zoom_out()));
    QObject::connect(ui->actionChange_Properties, SIGNAL(triggered()), this, SLOT(on_change_properties()));
}

/* called when the various actions are taken */
void EditorWindow::on_new() {
    if (!tiles_loaded) {
        popup("Set a tile image file first!");
        return;
    }

    if (!just_saved) {
        if (!check_save()) {
            return;
        }
    }

    NewDialog* dialog = new NewDialog();
    Ui_NewMapDialog ui;
    ui.setupUi(dialog);

    /* add the options for tile maps - these are regular, not affine ones... */
    ui.comboBox->addItem("16x16 (Affine Only)");
    ui.comboBox->addItem("32x32");
    ui.comboBox->addItem("32x64 (Regular Only)");
    ui.comboBox->addItem("64x32 (Regular Only)");
    ui.comboBox->addItem("64x64");
    ui.comboBox->addItem("128x128 (Affine Only)");

    /* setup the triggers for this thing */
    dialog->setup_triggers(&ui);
    dialog->exec();

    /* get the outcome of this */
    int choice = dialog->get_selection();

    /* if we actually chose an image size */
    if (choice != -1) {
        switch (choice) {
            case 0: map = new Map(16, 16); break;
            case 1: map = new Map(32, 32); break;
            case 2: map = new Map(32, 64); break;
            case 3: map = new Map(64, 32); break;
            case 4: map = new Map(64, 64); break;
            case 5: map = new Map(128, 128); break;
        }

        /* apply the map */
        QPixmap p = map->get_pixmap(&tiles);
        map_scene->clear();
        map_scene->addPixmap(p);
        just_saved = true;
    }
}
void EditorWindow::closeEvent(QCloseEvent* event) {
    if (!just_saved) {
        if (!check_save()) {
            event->ignore();
        }
    }
    event->accept();
}

/* ask the user if they want to save first - returns true if we should
 * carry on and false if we should abort whatever it is we are doing */
bool EditorWindow::check_save() {
    QMessageBox msgBox;
    msgBox.setWindowTitle("GBA Tile Editor");
    msgBox.setText("The document has been modified.");
    msgBox.setInformativeText("Do you want to save your changes?");
    msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
    msgBox.setDefaultButton(QMessageBox::Save);
    switch (msgBox.exec()) {
        case QMessageBox::Save:
            on_save();
            return true;
        case QMessageBox::Discard:
            return true;
        case QMessageBox::Cancel:
            return false;
        default:
            return false;
    }
}

void EditorWindow::on_open() {
    if (!tiles_loaded) {
        popup("Set a tile image file first!");
        return;
    }

    if (!just_saved) {
        if (!check_save()) {
            return;
        }
    }

    filename = QFileDialog::getOpenFileName(this, tr("Open File"), "", tr("Map Headers (*.h)"));
    if (filename != "") {
        filename_valid = true;
        if (map) {
            delete map;
        }
        map = new Map();
        if (map->read(filename.toStdString())) {
            /* apply the map */
            QPixmap p = map->get_pixmap(&tiles);
            map_scene->clear();
            map_scene->addPixmap(p);
            just_saved = true;
        } else {
            popup("The file does not appear to be a valid map header");
        }
    }
}

/* used for saving stuff */
QString EditorWindow::get_save_name() {
    QString f = QFileDialog::getSaveFileName(this, tr("Save File"), "", tr("Map Headers (*.h)"));
    return f;
}

void EditorWindow::save_to_file() {
    if (map) {
        map->write(filename.toStdString());
        just_saved = true;
    } else {
        popup("There is nothing to save yet!");
    }
}

void EditorWindow::on_save() {
    if (filename_valid) {
        save_to_file();
    } else {
        on_save_as();
    }
}

void EditorWindow::on_save_as() {
    filename_valid = true;
    filename = get_save_name();
    if (filename != "") {
        save_to_file();
    }
}

void EditorWindow::on_change_properties() {
    /* get the file name of the file they wish to use */
    QString file = QFileDialog::getOpenFileName(this,
         tr("Set Image"), "", tr("Image Files (*.png)"));

    /* check if the string is null */
    if (file.isNull()) {
        return;
    }

    /* set the image itself */
    tiles.load(file);
    tiles_loaded = true;

    /* display it in the bottom area */
    QPixmap p = QPixmap::fromImage(tiles);
    palette_scene->clear();
    palette_scene->addPixmap(p);
}

void EditorWindow::palette_click(int x, int y) {
    /* if there is no palette, then bail */
    if (!tiles_loaded) {
        return;
    }

    /* if the click is out of bounds, then bail */
    if (x > tiles.width() || y > tiles.height()) {
        return;
    }

    /* figure out which tile they clicked */
    int tile = (y / 8) * (tiles.width() / 8) + (x / 8);

    /* set the current tile based on this */
    current_tile = tile;
}

void EditorWindow::map_click(int x, int y) {
    /* if there is no map, then bail */
    if (!map) {
        return;
    }

    /* if the click is out of bounds, then bail */
    if (x > (map->get_width() * 8) || y > (map->get_height() * 8)) {
        return;
    }

    /* figure out which tile they clicked */
    int tile = (y / 8) * map->get_width() + (x / 8);

    /* apply this tile */
    just_saved = false;
    map->set_tile(tile, current_tile);
    QPixmap p = map->get_pixmap(&tiles);
    map_scene->clear();
    map_scene->addPixmap(p);
}

void EditorWindow::on_quit() {
    if (!just_saved) {
        if (!check_save()) {
            return;
        }
    }
    app->exit();
}

void EditorWindow::on_undo() {
    if (map) {
        map->undo();

        /* apply the map */
        QPixmap p = map->get_pixmap(&tiles);
        map_scene->clear();
        map_scene->addPixmap(p);
    }
}

void EditorWindow::on_redo() {
    if (map) {
        map->redo();

        /* apply the map */
        QPixmap p = map->get_pixmap(&tiles);
        map_scene->clear();
        map_scene->addPixmap(p);
    }
}

void EditorWindow::on_cut() {
    popup("Cut is not implemented yet :(");
}

void EditorWindow::on_copy() {
    popup("Copy is not implemented yet :(");
}

void EditorWindow::on_paste() {
    popup("Paste is not implemented yet :(");
}

void EditorWindow::on_grid() {
    popup("Grid is not implemented yet :(");
}

void EditorWindow::on_zoom_in() {
    popup("Zoom is not implemented yet :(");
}

void EditorWindow::on_zoom_out() {
    popup("Zoom is not implemented yet :(");
}

<commit_msg>Fixed the bug with the window closing even on "cancel" with unsaved work<commit_after>/* editorwindow.cpp
 * implementation fle for the main editor window */

#include <stdio.h>
#include <QMouseEvent>
#include <QMessageBox>
#include <QFileDialog>
#include "editorwindow.h"
#include "newdialog.h"
#include "ui_mainwindow.h"
#include "ui_newmap.h"

/* shows a generic popup message */
void popup(const char* message) {
    QMessageBox msgBox;
    msgBox.setWindowTitle("GBA Tile Editor");
    msgBox.setText(message);
    msgBox.exec();
}

/* constructor which stores the app so we can quit later */
EditorWindow::EditorWindow(QApplication* app) {
    this->app = app;
    map = NULL;
    tiles_loaded = false;
    setWindowTitle("GBA Tile Editor");
    current_tile = 0;
    just_saved = true;
}

/* set the map and palette areas */
void EditorWindow::set_areas( QGraphicsScene* map, QGraphicsScene* palette) {
    this->map_scene = map;
    this->palette_scene = palette;
}

/* set up all the signal and slot triggers for each action */
void EditorWindow::setup_triggers(Ui_MainWindow* ui) {
    /* the main window actions */
    QObject::connect(ui->actionNew, SIGNAL(triggered()), this, SLOT(on_new()));
    QObject::connect(ui->actionOpen, SIGNAL(triggered()), this, SLOT(on_open()));
    QObject::connect(ui->actionSave, SIGNAL(triggered()), this, SLOT(on_save()));
    QObject::connect(ui->actionSave_As, SIGNAL(triggered()), this, SLOT(on_save_as()));
    QObject::connect(ui->actionQuit, SIGNAL(triggered()), this, SLOT(on_quit()));
    QObject::connect(ui->actionUndo, SIGNAL(triggered()), this, SLOT(on_undo()));
    QObject::connect(ui->actionRedo, SIGNAL(triggered()), this, SLOT(on_redo()));
    QObject::connect(ui->actionShow_Grid, SIGNAL(triggered()), this, SLOT(on_grid()));
    QObject::connect(ui->actionCut, SIGNAL(triggered()), this, SLOT(on_cut()));
    QObject::connect(ui->actionCopy, SIGNAL(triggered()), this, SLOT(on_copy()));
    QObject::connect(ui->actionPaste, SIGNAL(triggered()), this, SLOT(on_paste()));
    QObject::connect(ui->actionZoom_In, SIGNAL(triggered()), this, SLOT(on_zoom_in()));
    QObject::connect(ui->actionZoom_Out, SIGNAL(triggered()), this, SLOT(on_zoom_out()));
    QObject::connect(ui->actionChange_Properties, SIGNAL(triggered()), this, SLOT(on_change_properties()));
}

/* called when the various actions are taken */
void EditorWindow::on_new() {
    if (!tiles_loaded) {
        popup("Set a tile image file first!");
        return;
    }

    if (!just_saved) {
        if (!check_save()) {
            return;
        }
    }

    NewDialog* dialog = new NewDialog();
    Ui_NewMapDialog ui;
    ui.setupUi(dialog);

    /* add the options for tile maps - these are regular, not affine ones... */
    ui.comboBox->addItem("16x16 (Affine Only)");
    ui.comboBox->addItem("32x32");
    ui.comboBox->addItem("32x64 (Regular Only)");
    ui.comboBox->addItem("64x32 (Regular Only)");
    ui.comboBox->addItem("64x64");
    ui.comboBox->addItem("128x128 (Affine Only)");

    /* setup the triggers for this thing */
    dialog->setup_triggers(&ui);
    dialog->exec();

    /* get the outcome of this */
    int choice = dialog->get_selection();

    /* if we actually chose an image size */
    if (choice != -1) {
        switch (choice) {
            case 0: map = new Map(16, 16); break;
            case 1: map = new Map(32, 32); break;
            case 2: map = new Map(32, 64); break;
            case 3: map = new Map(64, 32); break;
            case 4: map = new Map(64, 64); break;
            case 5: map = new Map(128, 128); break;
        }

        /* apply the map */
        QPixmap p = map->get_pixmap(&tiles);
        map_scene->clear();
        map_scene->addPixmap(p);
        just_saved = true;
    }
}
void EditorWindow::closeEvent(QCloseEvent* event) {
    if (!just_saved) {
        if (!check_save()) {
            event->ignore();
            return;
        }
    }
    event->accept();
}

/* ask the user if they want to save first - returns true if we should
 * carry on and false if we should abort whatever it is we are doing */
bool EditorWindow::check_save() {
    QMessageBox msgBox;
    msgBox.setWindowTitle("GBA Tile Editor");
    msgBox.setText("The document has been modified.");
    msgBox.setInformativeText("Do you want to save your changes?");
    msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
    msgBox.setDefaultButton(QMessageBox::Save);
    switch (msgBox.exec()) {
        case QMessageBox::Save:
            on_save();
            return true;
        case QMessageBox::Discard:
            return true;
        case QMessageBox::Cancel:
            return false;
        default:
            return false;
    }
}

void EditorWindow::on_open() {
    if (!tiles_loaded) {
        popup("Set a tile image file first!");
        return;
    }

    if (!just_saved) {
        if (!check_save()) {
            return;
        }
    }

    filename = QFileDialog::getOpenFileName(this, tr("Open File"), "", tr("Map Headers (*.h)"));
    if (filename != "") {
        filename_valid = true;
        if (map) {
            delete map;
        }
        map = new Map();
        if (map->read(filename.toStdString())) {
            /* apply the map */
            QPixmap p = map->get_pixmap(&tiles);
            map_scene->clear();
            map_scene->addPixmap(p);
            just_saved = true;
        } else {
            popup("The file does not appear to be a valid map header");
        }
    }
}

/* used for saving stuff */
QString EditorWindow::get_save_name() {
    QString f = QFileDialog::getSaveFileName(this, tr("Save File"), "", tr("Map Headers (*.h)"));
    return f;
}

void EditorWindow::save_to_file() {
    if (map) {
        map->write(filename.toStdString());
        just_saved = true;
    } else {
        popup("There is nothing to save yet!");
    }
}

void EditorWindow::on_save() {
    if (filename_valid) {
        save_to_file();
    } else {
        on_save_as();
    }
}

void EditorWindow::on_save_as() {
    filename_valid = true;
    filename = get_save_name();
    if (filename != "") {
        save_to_file();
    }
}

void EditorWindow::on_change_properties() {
    /* get the file name of the file they wish to use */
    QString file = QFileDialog::getOpenFileName(this,
         tr("Set Image"), "", tr("Image Files (*.png)"));

    /* check if the string is null */
    if (file.isNull()) {
        return;
    }

    /* set the image itself */
    tiles.load(file);
    tiles_loaded = true;

    /* display it in the bottom area */
    QPixmap p = QPixmap::fromImage(tiles);
    palette_scene->clear();
    palette_scene->addPixmap(p);
}

void EditorWindow::palette_click(int x, int y) {
    /* if there is no palette, then bail */
    if (!tiles_loaded) {
        return;
    }

    /* if the click is out of bounds, then bail */
    if (x > tiles.width() || y > tiles.height()) {
        return;
    }

    /* figure out which tile they clicked */
    int tile = (y / 8) * (tiles.width() / 8) + (x / 8);

    /* set the current tile based on this */
    current_tile = tile;
}

void EditorWindow::map_click(int x, int y) {
    /* if there is no map, then bail */
    if (!map) {
        return;
    }

    /* if the click is out of bounds, then bail */
    if (x > (map->get_width() * 8) || y > (map->get_height() * 8)) {
        return;
    }

    /* figure out which tile they clicked */
    int tile = (y / 8) * map->get_width() + (x / 8);

    /* apply this tile */
    just_saved = false;
    map->set_tile(tile, current_tile);
    QPixmap p = map->get_pixmap(&tiles);
    map_scene->clear();
    map_scene->addPixmap(p);
}

void EditorWindow::on_quit() {
    if (!just_saved) {
        if (!check_save()) {
            return;
        }
    }
    app->exit();
}

void EditorWindow::on_undo() {
    if (map) {
        map->undo();

        /* apply the map */
        QPixmap p = map->get_pixmap(&tiles);
        map_scene->clear();
        map_scene->addPixmap(p);
    }
}

void EditorWindow::on_redo() {
    if (map) {
        map->redo();

        /* apply the map */
        QPixmap p = map->get_pixmap(&tiles);
        map_scene->clear();
        map_scene->addPixmap(p);
    }
}

void EditorWindow::on_cut() {
    popup("Cut is not implemented yet :(");
}

void EditorWindow::on_copy() {
    popup("Copy is not implemented yet :(");
}

void EditorWindow::on_paste() {
    popup("Paste is not implemented yet :(");
}

void EditorWindow::on_grid() {
    popup("Grid is not implemented yet :(");
}

void EditorWindow::on_zoom_in() {
    popup("Zoom is not implemented yet :(");
}

void EditorWindow::on_zoom_out() {
    popup("Zoom is not implemented yet :(");
}

<|endoftext|>


<commit_before>/*******************************************************************************
 * c7a/api/sort.hpp
 *
 * Part of Project c7a.
 *
 * Copyright (C) 2015 Alexander Noe <aleexnoe@gmail.com>
 *
 * This file has no license. Only Chuck Norris can compile it.
 ******************************************************************************/

#pragma once
#ifndef C7A_API_SORT_HEADER
#define C7A_API_SORT_HEADER

#define ROUND_DOWN(x, s) ((x) & ~((s) - 1))

#include <c7a/api/context.hpp>
#include <c7a/api/dia.hpp>
#include <c7a/api/function_stack.hpp>
#include <c7a/common/logger.hpp>
#include <c7a/net/collective_communication.hpp>
#include <c7a/net/flow_control_channel.hpp>
#include <c7a/net/flow_control_manager.hpp>
#include <c7a/net/group.hpp>

#include <c7a/common/partitioning/tree_builder.hpp>
#include <c7a/common/partitioning/trivial_target_determination.hpp>

#include <cmath>

namespace c7a {
namespace api {

//! \addtogroup api Interface
//! \{

/*!
 * A DIANode which performs a Sort operation. Sort sorts a DIA according to a given
 * compare function
 *
 * \tparam ValueType Type of DIA elements
 * \tparam Stack Function stack, which contains the chained lambdas between the last and this DIANode.
 * \tparam CompareFunction Type of the compare function
 */
template <typename ValueType, typename ParentStack, typename CompareFunction>
class SortNode : public DOpNode<ValueType>
{
    static const bool debug = false;

    using Super = DOpNode<ValueType>;
    using Super::context_;
    using Super::result_file_;

    using ParentInput = typename ParentStack::Input;

public:
    /*!
     * Constructor for a sort node.
     *
     * \param ctx Context for this operation
     * \param parent Previous DIANode in the computation chain
     * \param parent_stack Stack of lambda functions between parent and this node
     * \param compare_function Function comparing two elements.
     */
    SortNode(Context& ctx,
             std::shared_ptr<DIANode<ParentInput> > parent,
             const ParentStack& parent_stack,
             CompareFunction compare_function)
        : DOpNode<ValueType>(ctx, { parent }, "Sort"),
          compare_function_(compare_function),
          channel_id_samples_(ctx.data_manager().GetNewChannel()),
          emitters_samples_(channel_id_samples_->OpenWriters()),
          channel_id_data_(ctx.data_manager().GetNewChannel()),
          emitters_data_(channel_id_data_->OpenWriters()),
          parent_(parent)
    {
        // Hook PreOp(s)
        auto pre_op_fn = [ = ](const ValueType& input) {
                             PreOp(input);
                         };

        lop_chain_ = parent_stack.push(pre_op_fn).emit();

        parent_->RegisterChild(lop_chain_);
    }

    virtual ~SortNode() {
        parent_->UnregisterChild(lop_chain_);
    }

    //! Executes the sum operation.
    void Execute() override {
        this->StartExecutionTimer();
        MainOp();
        this->StopExecutionTimer();
    }

    /*!
     * Produces an 'empty' function stack, which only contains the identity
     * emitter function.
     *
     * \return Empty function stack
     */
    auto ProduceStack() {
        // Hook Identity
        auto id_fn = [ = ](ValueType t, auto emit_func) {
                         return emit_func(t);
                     };

        return MakeFunctionStack<ValueType>(id_fn);
    }

    /*!
     * Returns "[SortNode]" as a string.
     * \return "[SortNode]"
     */
    std::string ToString() override {
        return "[SortNode] Id:" + result_file_.ToString();
    }

private:
    //! The sum function which is applied to two elements.
    CompareFunction compare_function_;
    //! Local data
    std::vector<ValueType> data_;

    //!Emitter to send samples to process 0
    data::ChannelSPtr channel_id_samples_;
    std::vector<data::BlockWriter> emitters_samples_;

    //!Emitters to send data to other workers specified by splitters.
    data::ChannelSPtr channel_id_data_;
    std::vector<data::BlockWriter> emitters_data_;

    //epsilon
    double desired_imbalance = 0.25;

    std::shared_ptr<DIANode<ParentInput>> parent_;
    common::delegate<void(ParentInput)> lop_chain_;

    void PreOp(ValueType input) {
        data_.push_back(input);
    }

    void FindAndSendSplitters(std::vector<ValueType>& splitters) {
        //Get samples from other workers
        size_t num_workers = context_.number_worker();
        size_t samplesize = std::ceil(log2((double)data_.size()) *
                                      (1 / (desired_imbalance *
                                            desired_imbalance)));

        std::vector<ValueType> samples;
        samples.reserve(samplesize * num_workers);
        auto reader = channel_id_samples_->OpenReader();

        while (reader.HasNext()) {
            samples.push_back(reader.template Next<ValueType>());
        }

        //Find splitters
        std::sort(samples.begin(), samples.end(), compare_function_);

        size_t splitting_size = samples.size() / num_workers;

        //Send splitters to other workers
        for (size_t i = 1; i < num_workers; i++) {
            splitters.push_back(samples[i * splitting_size]);
            for (size_t j = 1; j < num_workers; j++) {
                emitters_samples_[j](samples[i * splitting_size]);
            }
        }

        for (size_t j = 1; j < num_workers; j++) {
            emitters_samples_[j].Close();
        }
    }

    void MainOp() {
        size_t num_workers = context_.number_worker();
        size_t samplesize = std::ceil(log2((double)data_.size()) *
                                      (1 / (desired_imbalance * desired_imbalance)));

        net::FlowControlChannel& channel = context_.flow_control_channel();

        size_t prefix_elem = channel.PrefixSum(data_.size());
        size_t total_elem = channel.AllReduce(data_.size());

        LOG << prefix_elem << " elements, out of " << total_elem;

        std::random_device random_device;
        std::default_random_engine generator(random_device());
        std::uniform_int_distribution<int> distribution(0, data_.size() - 1);

        //Send samples to worker 0
        for (size_t i = 0; i < samplesize; i++) {
            size_t sample = distribution(generator);
            emitters_samples_[0](data_[sample]);
        }
        emitters_samples_[0].Close();

        //Get the ceiling of log(num_workers), as SSSS needs 2^n buckets.
        double log_workers = std::log2(num_workers);
        const bool powof2 = (std::ceil(log_workers) - log_workers) < 0.0000001;
        size_t ceil_log = std::ceil(log_workers);
        size_t workers_algo = 1 << ceil_log;
        size_t splitter_count_algo = workers_algo - 1;

        std::vector<ValueType> splitters;
        splitters.reserve(workers_algo);

        if (context_.rank() == 0) {
            FindAndSendSplitters(splitters);
        }
        else {
            //Close unused emitters
            for (size_t j = 1; j < num_workers; j++) {
                emitters_samples_[j].Close();
            }
            auto reader = channel_id_samples_->OpenReader();
            while (reader.HasNext()) {
                splitters.push_back(reader.template Next<ValueType>());
            }
        }

        //code from SS2NPartition, slightly altered

        ValueType* splitter_tree = new ValueType[workers_algo];

        if (!powof2) {
            for (size_t i = num_workers; i < workers_algo; i++) {
                splitters.push_back(splitters.back());
            }
        }

        sort::TreeBuilder<ValueType>(splitter_tree,
                                     splitters.data(),
                                     splitter_count_algo);

        sort::BucketEmitter<ValueType, CompareFunction>::emitToBuckets(
            data_.data(),
            data_.size(),
            splitter_tree, // Tree. sizeof |splitter|
            workers_algo,  // Number of buckets
            ceil_log,
            emitters_data_,
            num_workers,
            compare_function_,
            splitters.data(),
            prefix_elem,
            total_elem);

        //end of SS2N

        for (size_t i = 0; i < emitters_data_.size(); i++) {
            emitters_data_[i].Close();
        }

        data_.clear();

        auto reader = channel_id_data_->OpenReader();

        while (reader.HasNext()) {
            data_.push_back(reader.template Next<ValueType>());
        }

        std::sort(data_.begin(), data_.end(), compare_function_);

        for (size_t i = 0; i < data_.size(); i++) {
            for (auto func : DIANode<ValueType>::callbacks_) {
                func(data_[i]);
            }
        }
        std::vector<ValueType>().swap(data_);
    }

    void PostOp() { }
};

template <typename ValueType, typename Stack>
template <typename CompareFunction>
auto DIARef<ValueType, Stack>::Sort(const CompareFunction &compare_function) const {

    using SortResultNode
              = SortNode<ValueType, Stack, CompareFunction>;

    static_assert(
        std::is_same<
            typename common::FunctionTraits<CompareFunction>::template arg<0>,
            ValueType>::value ||
        std::is_same<CompareFunction, common::LessThan<ValueType> >::value,
        "CompareFunction has the wrong input type");

    static_assert(
        std::is_same<
            typename common::FunctionTraits<CompareFunction>::template arg<1>,
            ValueType>::value ||
        std::is_same<CompareFunction, common::LessThan<ValueType> >::value,
        "CompareFunction has the wrong input type");

    static_assert(
        std::is_same<
            typename common::FunctionTraits<CompareFunction>::result_type,
            ValueType>::value ||
        std::is_same<CompareFunction, common::LessThan<ValueType> >::value,
        "CompareFunction has the wrong input type");

    auto shared_node
        = std::make_shared<SortResultNode>(node_->context(),
                                           node_,
                                           stack_,
                                           compare_function);

    auto sort_stack = shared_node->ProduceStack();

    return DIARef<ValueType, decltype(sort_stack)>(
        std::move(shared_node), sort_stack);
}

//! \}
} // namespace api
} // namespace c7a

#endif // !C7A_API_SORT_HEADER

/******************************************************************************/
<commit_msg>fix sample size formula<commit_after>/*******************************************************************************
 * c7a/api/sort.hpp
 *
 * Part of Project c7a.
 *
 * Copyright (C) 2015 Alexander Noe <aleexnoe@gmail.com>
 *
 * This file has no license. Only Chuck Norris can compile it.
 ******************************************************************************/

#pragma once
#ifndef C7A_API_SORT_HEADER
#define C7A_API_SORT_HEADER

#define ROUND_DOWN(x, s) ((x) & ~((s) - 1))

#include <c7a/api/context.hpp>
#include <c7a/api/dia.hpp>
#include <c7a/api/function_stack.hpp>
#include <c7a/common/logger.hpp>
#include <c7a/net/collective_communication.hpp>
#include <c7a/net/flow_control_channel.hpp>
#include <c7a/net/flow_control_manager.hpp>
#include <c7a/net/group.hpp>

#include <c7a/common/partitioning/tree_builder.hpp>
#include <c7a/common/partitioning/trivial_target_determination.hpp>

#include <cmath>

namespace c7a {
namespace api {

//! \addtogroup api Interface
//! \{

/*!
 * A DIANode which performs a Sort operation. Sort sorts a DIA according to a given
 * compare function
 *
 * \tparam ValueType Type of DIA elements
 * \tparam Stack Function stack, which contains the chained lambdas between the last and this DIANode.
 * \tparam CompareFunction Type of the compare function
 */
template <typename ValueType, typename ParentStack, typename CompareFunction>
class SortNode : public DOpNode<ValueType>
{
    static const bool debug = false;

    using Super = DOpNode<ValueType>;
    using Super::context_;
    using Super::result_file_;

    using ParentInput = typename ParentStack::Input;

public:
    /*!
     * Constructor for a sort node.
     *
     * \param ctx Context for this operation
     * \param parent Previous DIANode in the computation chain
     * \param parent_stack Stack of lambda functions between parent and this node
     * \param compare_function Function comparing two elements.
     */
    SortNode(Context& ctx,
             std::shared_ptr<DIANode<ParentInput> > parent,
             const ParentStack& parent_stack,
             CompareFunction compare_function)
        : DOpNode<ValueType>(ctx, { parent }, "Sort"),
          compare_function_(compare_function),
          channel_id_samples_(ctx.data_manager().GetNewChannel()),
          emitters_samples_(channel_id_samples_->OpenWriters()),
          channel_id_data_(ctx.data_manager().GetNewChannel()),
          emitters_data_(channel_id_data_->OpenWriters()),
          parent_(parent)
    {
        // Hook PreOp(s)
        auto pre_op_fn = [ = ](const ValueType& input) {
                             PreOp(input);
                         };

        lop_chain_ = parent_stack.push(pre_op_fn).emit();

        parent_->RegisterChild(lop_chain_);
    }

    virtual ~SortNode() {
        parent_->UnregisterChild(lop_chain_);
    }

    //! Executes the sum operation.
    void Execute() override {
        this->StartExecutionTimer();
        MainOp();
        this->StopExecutionTimer();
    }

    /*!
     * Produces an 'empty' function stack, which only contains the identity
     * emitter function.
     *
     * \return Empty function stack
     */
    auto ProduceStack() {
        // Hook Identity
        auto id_fn = [ = ](ValueType t, auto emit_func) {
                         return emit_func(t);
                     };

        return MakeFunctionStack<ValueType>(id_fn);
    }

    /*!
     * Returns "[SortNode]" as a string.
     * \return "[SortNode]"
     */
    std::string ToString() override {
        return "[SortNode] Id:" + result_file_.ToString();
    }

private:
    //! The sum function which is applied to two elements.
    CompareFunction compare_function_;
    //! Local data
    std::vector<ValueType> data_;

    //!Emitter to send samples to process 0
    data::ChannelSPtr channel_id_samples_;
    std::vector<data::BlockWriter> emitters_samples_;

    //!Emitters to send data to other workers specified by splitters.
    data::ChannelSPtr channel_id_data_;
    std::vector<data::BlockWriter> emitters_data_;

    //epsilon
    double desired_imbalance = 0.25;

    std::shared_ptr<DIANode<ParentInput>> parent_;
    common::delegate<void(ParentInput)> lop_chain_;

    void PreOp(ValueType input) {
        data_.push_back(input);
    }

    void FindAndSendSplitters(std::vector<ValueType>& splitters) {
        //Get samples from other workers
        size_t num_workers = context_.number_worker();
        size_t samplesize = std::ceil(log2((double)data_.size()) *
                                      (1 / (desired_imbalance *
                                            desired_imbalance)));

        std::vector<ValueType> samples;
        samples.reserve(samplesize * num_workers);
        auto reader = channel_id_samples_->OpenReader();

        while (reader.HasNext()) {
            samples.push_back(reader.template Next<ValueType>());
        }

        //Find splitters
        std::sort(samples.begin(), samples.end(), compare_function_);

        size_t splitting_size = samples.size() / num_workers;

        //Send splitters to other workers
        for (size_t i = 1; i < num_workers; i++) {
            splitters.push_back(samples[i * splitting_size]);
            for (size_t j = 1; j < num_workers; j++) {
                emitters_samples_[j](samples[i * splitting_size]);
            }
        }

        for (size_t j = 1; j < num_workers; j++) {
            emitters_samples_[j].Close();
        }
    }

    void MainOp() {
        net::FlowControlChannel& channel = context_.flow_control_channel();

        size_t prefix_elem = channel.PrefixSum(data_.size());
        size_t total_elem = channel.AllReduce(data_.size());

        size_t num_workers = context_.number_worker();
        size_t samplesize = std::ceil(log2((double)total_elem) *
                                      (1 / (desired_imbalance * desired_imbalance)));

        LOG << prefix_elem << " elements, out of " << total_elem;

        std::random_device random_device;
        std::default_random_engine generator(random_device());
        std::uniform_int_distribution<int> distribution(0, data_.size() - 1);

        //Send samples to worker 0
        for (size_t i = 0; i < samplesize; i++) {
            size_t sample = distribution(generator);
            emitters_samples_[0](data_[sample]);
        }
        emitters_samples_[0].Close();

        //Get the ceiling of log(num_workers), as SSSS needs 2^n buckets.
        double log_workers = std::log2(num_workers);
        const bool powof2 = (std::ceil(log_workers) - log_workers) < 0.0000001;
        size_t ceil_log = std::ceil(log_workers);
        size_t workers_algo = 1 << ceil_log;
        size_t splitter_count_algo = workers_algo - 1;

        std::vector<ValueType> splitters;
        splitters.reserve(workers_algo);

        if (context_.rank() == 0) {
            FindAndSendSplitters(splitters);
        }
        else {
            //Close unused emitters
            for (size_t j = 1; j < num_workers; j++) {
                emitters_samples_[j].Close();
            }
            auto reader = channel_id_samples_->OpenReader();
            while (reader.HasNext()) {
                splitters.push_back(reader.template Next<ValueType>());
            }
        }

        //code from SS2NPartition, slightly altered

        ValueType* splitter_tree = new ValueType[workers_algo];

        if (!powof2) {
            for (size_t i = num_workers; i < workers_algo; i++) {
                splitters.push_back(splitters.back());
            }
        }

        sort::TreeBuilder<ValueType>(splitter_tree,
                                     splitters.data(),
                                     splitter_count_algo);

        sort::BucketEmitter<ValueType, CompareFunction>::emitToBuckets(
            data_.data(),
            data_.size(),
            splitter_tree, // Tree. sizeof |splitter|
            workers_algo,  // Number of buckets
            ceil_log,
            emitters_data_,
            num_workers,
            compare_function_,
            splitters.data(),
            prefix_elem,
            total_elem);

        //end of SS2N

        for (size_t i = 0; i < emitters_data_.size(); i++) {
            emitters_data_[i].Close();
        }

        data_.clear();

        auto reader = channel_id_data_->OpenReader();

        while (reader.HasNext()) {
            data_.push_back(reader.template Next<ValueType>());
        }

        std::sort(data_.begin(), data_.end(), compare_function_);

        for (size_t i = 0; i < data_.size(); i++) {
            for (auto func : DIANode<ValueType>::callbacks_) {
                func(data_[i]);
            }
        }
        std::vector<ValueType>().swap(data_);
    }

    void PostOp() { }
};

template <typename ValueType, typename Stack>
template <typename CompareFunction>
auto DIARef<ValueType, Stack>::Sort(const CompareFunction &compare_function) const {

    using SortResultNode
              = SortNode<ValueType, Stack, CompareFunction>;

    static_assert(
        std::is_same<
            typename common::FunctionTraits<CompareFunction>::template arg<0>,
            ValueType>::value ||
        std::is_same<CompareFunction, common::LessThan<ValueType> >::value,
        "CompareFunction has the wrong input type");

    static_assert(
        std::is_same<
            typename common::FunctionTraits<CompareFunction>::template arg<1>,
            ValueType>::value ||
        std::is_same<CompareFunction, common::LessThan<ValueType> >::value,
        "CompareFunction has the wrong input type");

    static_assert(
        std::is_same<
            typename common::FunctionTraits<CompareFunction>::result_type,
            ValueType>::value ||
        std::is_same<CompareFunction, common::LessThan<ValueType> >::value,
        "CompareFunction has the wrong input type");

    auto shared_node
        = std::make_shared<SortResultNode>(node_->context(),
                                           node_,
                                           stack_,
                                           compare_function);

    auto sort_stack = shared_node->ProduceStack();

    return DIARef<ValueType, decltype(sort_stack)>(
        std::move(shared_node), sort_stack);
}

//! \}
} // namespace api
} // namespace c7a

#endif // !C7A_API_SORT_HEADER

/******************************************************************************/
<|endoftext|>