<commit_before>// This file was developed by Thomas Müller <thomas94@gmx.net>.
// It is published under the BSD 3-Clause License within the LICENSE file.

#include <tev/ImageViewer.h>
#include <tev/Ipc.h>
#include <tev/SharedQueue.h>
#include <tev/ThreadPool.h>

#include <args.hxx>
#include <ImfThreading.h>

#include <utf8.h>

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>

using namespace args;
using namespace filesystem;
using namespace std;

TEV_NAMESPACE_BEGIN

int mainFunc(const vector<string>& arguments) {
    ArgumentParser parser{
        "tev — The EXR Viewer\n"
        "version " TEV_VERSION "\n"
        "Inspection tool for images with high dynamic range",
        "tev was developed by Thomas Müller <thomas94@gmx.net>. "
        "Its source code is available under the BSD 3-Clause License at https://tom94.net",
    };

    ValueFlag<float> exposureFlag{
        parser,
        "EXPOSURE",
        "Scales the brightness of an image prior to tonemapping by 2^EXPOSURE. Default is 0.",
        {'e', "exposure"},
    };

    ValueFlag<string> filterFlag{
        parser,
        "FILTER",
        "Filter visible images and layers according to a supplied string. "
        "The string must have the format 'image:layer'. "
        "Only images whose name contains 'image' and layers whose name contains 'layer' will be visible.",
        {'f', "filter"},
    };

    ValueFlag<float> gammaFlag{
        parser,
        "GAMMA",
        "The exponent used when TONEMAP is 'Gamma'. Default is 2.2.",
        {'g', "gamma"},
    };

    HelpFlag helpFlag{
        parser,
        "HELP",
        "Display this help menu.",
        {'h', "help"},
    };

    ValueFlag<bool> maximizeFlag{
        parser,
        "MAXIMIZE",
        "Maximize the window on startup. "
        "If no images were supplied via the command line, then the default is FALSE. "
        "Otherwise, the default is TRUE.",
        {"max", "maximize"},
    };

    ValueFlag<string> metricFlag{
        parser,
        "METRIC",
        "The metric to use when comparing two images. "
        "The available metrics are:\n"
        "E   - Error\n"
        "AE  - Absolute Error\n"
        "SE  - Squared Error\n"
        "RAE - Relative Absolute Error\n"
        "RSE - Relative Squared Error\n"
        "Default is E.",
        {'m', "metric"},
    };

    Flag newWindowFlag{
        parser,
        "NEW WINDOW",
        "Open a new window of tev, even if one exists already.",
        {'n', "new"},
    };

    ValueFlag<float> offsetFlag{
        parser,
        "OFFSET",
        "Add an absolute offset to the image after EXPOSURE has been applied. Default is 0.",
        {'o', "offset"},
    };

    ValueFlag<string> tonemapFlag{
        parser,
        "TONEMAP",
        "The tonemapping algorithm to use. "
        "The available tonemaps are:\n"
        "sRGB   - sRGB\n"
        "Gamma  - Gamma curve\n"
        "FC     - False Color\n"
        "PN     - Positive=Green, Negative=Red\n"
        "Default is sRGB.",
        {'t', "tonemap"},
    };

    Flag versionFlag{
        parser,
        "VERSION",
        "Display the version of tev.",
        {'v', "version"},
    };

    PositionalList<string> imageFiles{
        parser,
        "images",
        "The image files to be opened by tev. "
        "If an argument starting with a ':' is encountered, "
        "then this argument is not treated as an image file "
        "but as a comma-separated channel selector. Until the next channel "
        "selector is encountered only channels containing "
        "elements from the current selector will be loaded. This is "
        "especially useful for selectively loading a specific "
        "part of a multi-part EXR file.",
    };

    // Parse command line arguments and react to parsing
    // errors using exceptions.
    try {
        TEV_ASSERT(arguments.size() > 0, "Number of arguments must be bigger than 0.");

        parser.Prog(arguments.front());
        parser.ParseArgs(begin(arguments) + 1, end(arguments));
    } catch (const Help&) {
        cout << parser;
        return 0;
    } catch (const ParseError& e) {
        cerr << e.what() << endl;
        cerr << parser;
        return -1;
    } catch (const ValidationError& e) {
        cerr << e.what() << endl;
        cerr << parser;
        return -2;
    }

    if (versionFlag) {
        tlog::none() << "tev — The EXR Viewer\nversion " TEV_VERSION;
        return 0;
    }

    auto ipc = make_shared<Ipc>();

    // If we're not the primary instance and did not request to open a new window,
    // simply send the to-be-opened images to the primary instance.
    if (!ipc->isPrimaryInstance() && !newWindowFlag) {
        string channelSelector;
        for (auto imageFile : get(imageFiles)) {
            if (!imageFile.empty() && imageFile[0] == ':') {
                channelSelector = imageFile.substr(1);
                continue;
            }

            try {
                ipc->sendToPrimaryInstance(tfm::format("%s:%s", path{imageFile}.make_absolute(), channelSelector));
            } catch (runtime_error e) {
                tlog::error() << tfm::format("Invalid file '%s': %s", imageFile, e.what());
            }
        }

        return 0;
    }

    Imf::setGlobalThreadCount(thread::hardware_concurrency());

    tlog::info() << "Loading window...";

    shared_ptr<SharedQueue<ImageAddition>> imagesToAdd = make_shared<SharedQueue<ImageAddition>>();
    auto tryLoadImageBackground = [&imagesToAdd](const path& path, const string& channelSelector, bool shallSelect) {
        ThreadPool::singleWorker().enqueueTask([path, channelSelector, shallSelect, &imagesToAdd] {
            auto image = tryLoadImage(path, channelSelector);
            if (image) {
                imagesToAdd->push({ shallSelect, image });
            }
        });
    };

    atomic<bool> shallShutdown = false;

    // Spawn another background thread, this one dealing with images passed to us
    // via inter-process communication (IPC). This happens when
    // a user starts another instance of tev while one is already running. Note, that this
    // behavior can be overridden by the -n flag, so not _all_ secondary instances send their
    // paths to the primary instance.
    thread ipcThread;
    if (ipc->isPrimaryInstance()) {
        ipcThread = thread{[&]() {
            while (!shallShutdown) {
                while (ipc->receiveFromSecondaryInstance([&](const string& reveicedString) {
                    string imageString = ensureUtf8(reveicedString);
                    size_t colonPos = min(imageString.length() - 1, imageString.find_last_of(":"));
                    tryLoadImageBackground(imageString.substr(0, colonPos), imageString.substr(colonPos + 1), true);
                })) { }

                this_thread::sleep_for(chrono::milliseconds{100});
            }
        }};
    }

    // Load images passed via command line in the background prior to
    // creating our main application such that they are not stalled
    // by the potentially slow initialization of opengl / glfw.
    string channelSelector;
    for (auto imageFile : get(imageFiles)) {
        if (!imageFile.empty() && imageFile[0] == ':') {
            channelSelector = imageFile.substr(1);
            continue;
        }

        tryLoadImageBackground(imageFile, channelSelector, false);
    }

    // Init nanogui application
    nanogui::init();

    {
        auto app = unique_ptr<ImageViewer>{new ImageViewer{imagesToAdd, !imageFiles}};
        app->drawAll();
        app->setVisible(true);

        // Do what the maximize flag tells us---if it exists---and
        // maximize if we have images otherwise.
        if (maximizeFlag ? get(maximizeFlag) : imageFiles) {
            app->maximize();
        }

        // Apply parameter flags
        if (exposureFlag) { app->setExposure(get(exposureFlag)); }
        if (filterFlag)   { app->setFilter(get(filterFlag)); }
        if (gammaFlag)    { app->setGamma(get(gammaFlag)); }
        if (metricFlag)   { app->setMetric(toMetric(get(metricFlag))); }
        if (offsetFlag)   { app->setOffset(get(offsetFlag)); }
        if (tonemapFlag)  { app->setTonemap(toTonemap(get(tonemapFlag))); }

        // Refresh only every 250ms if there are no user interactions.
        // This makes an idling tev surprisingly energy-efficient. :)
        nanogui::mainloop(250);
    }

    shallShutdown = true;

    // On some linux distributions glfwTerminate() (which is called by
    // nanogui::shutdown()) causes segfaults. Since we are done with our
    // program here anyways, let's let the OS clean up after us.
    //nanogui::shutdown();

    if (ipcThread.joinable()) {
        ipcThread.join();
    }

    // Let all threads gracefully terminate.
    ThreadPool::shutdown();

    return 0;
}

TEV_NAMESPACE_END

#ifdef _WIN32
int wmain(int argc, wchar_t* argv[]) {
#else
int main(int argc, char* argv[]) {
#endif
    try {
        vector<string> arguments;
        for (int i = 0; i < argc; ++i) {
#ifdef _WIN32
            arguments.emplace_back(tev::utf16to8(argv[i]));
#else
            string arg = argv[i];
            // OSX sometimes (seemingly sporadically) passes the
            // process serial number via a command line parameter.
            // We would like to ignore this.
            if (arg.find("-psn") != 0) {
                arguments.emplace_back(tev::ensureUtf8(argv[i]));
            }
#endif
        }

        tev::mainFunc(arguments);
    } catch (const exception& e) {
        tlog::error() << tfm::format("Uncaught exception: %s", e.what());
        return 1;
    }
}
<commit_msg>Allow reading images from stdin (separated by newline)<commit_after>// This file was developed by Thomas Müller <thomas94@gmx.net>.
// It is published under the BSD 3-Clause License within the LICENSE file.

#include <tev/ImageViewer.h>
#include <tev/Ipc.h>
#include <tev/SharedQueue.h>
#include <tev/ThreadPool.h>

#include <args.hxx>
#include <ImfThreading.h>

#include <utf8.h>

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>

using namespace args;
using namespace filesystem;
using namespace std;

TEV_NAMESPACE_BEGIN

int mainFunc(const vector<string>& arguments) {
    ArgumentParser parser{
        "tev — The EXR Viewer\n"
        "version " TEV_VERSION "\n"
        "Inspection tool for images with high dynamic range",
        "tev was developed by Thomas Müller <thomas94@gmx.net>. "
        "Its source code is available under the BSD 3-Clause License at https://tom94.net",
    };

    ValueFlag<float> exposureFlag{
        parser,
        "EXPOSURE",
        "Scales the brightness of an image prior to tonemapping by 2^EXPOSURE. Default is 0.",
        {'e', "exposure"},
    };

    ValueFlag<string> filterFlag{
        parser,
        "FILTER",
        "Filter visible images and layers according to a supplied string. "
        "The string must have the format 'image:layer'. "
        "Only images whose name contains 'image' and layers whose name contains 'layer' will be visible.",
        {'f', "filter"},
    };

    ValueFlag<float> gammaFlag{
        parser,
        "GAMMA",
        "The exponent used when TONEMAP is 'Gamma'. Default is 2.2.",
        {'g', "gamma"},
    };

    HelpFlag helpFlag{
        parser,
        "HELP",
        "Display this help menu.",
        {'h', "help"},
    };

    ValueFlag<bool> maximizeFlag{
        parser,
        "MAXIMIZE",
        "Maximize the window on startup. "
        "If no images were supplied via the command line, then the default is FALSE. "
        "Otherwise, the default is TRUE.",
        {"max", "maximize"},
    };

    ValueFlag<string> metricFlag{
        parser,
        "METRIC",
        "The metric to use when comparing two images. "
        "The available metrics are:\n"
        "E   - Error\n"
        "AE  - Absolute Error\n"
        "SE  - Squared Error\n"
        "RAE - Relative Absolute Error\n"
        "RSE - Relative Squared Error\n"
        "Default is E.",
        {'m', "metric"},
    };

    Flag newWindowFlag{
        parser,
        "NEW WINDOW",
        "Open a new window of tev, even if one exists already.",
        {'n', "new"},
    };

    ValueFlag<float> offsetFlag{
        parser,
        "OFFSET",
        "Add an absolute offset to the image after EXPOSURE has been applied. Default is 0.",
        {'o', "offset"},
    };

    ValueFlag<string> tonemapFlag{
        parser,
        "TONEMAP",
        "The tonemapping algorithm to use. "
        "The available tonemaps are:\n"
        "sRGB   - sRGB\n"
        "Gamma  - Gamma curve\n"
        "FC     - False Color\n"
        "PN     - Positive=Green, Negative=Red\n"
        "Default is sRGB.",
        {'t', "tonemap"},
    };

    Flag versionFlag{
        parser,
        "VERSION",
        "Display the version of tev.",
        {'v', "version"},
    };

    PositionalList<string> imageFiles{
        parser,
        "images",
        "The image files to be opened by tev. "
        "If an argument starting with a ':' is encountered, "
        "then this argument is not treated as an image file "
        "but as a comma-separated channel selector. Until the next channel "
        "selector is encountered only channels containing "
        "elements from the current selector will be loaded. This is "
        "especially useful for selectively loading a specific "
        "part of a multi-part EXR file.",
    };

    // Parse command line arguments and react to parsing
    // errors using exceptions.
    try {
        TEV_ASSERT(arguments.size() > 0, "Number of arguments must be bigger than 0.");

        parser.Prog(arguments.front());
        parser.ParseArgs(begin(arguments) + 1, end(arguments));
    } catch (const Help&) {
        cout << parser;
        return 0;
    } catch (const ParseError& e) {
        cerr << e.what() << endl;
        cerr << parser;
        return -1;
    } catch (const ValidationError& e) {
        cerr << e.what() << endl;
        cerr << parser;
        return -2;
    }

    if (versionFlag) {
        tlog::none() << "tev — The EXR Viewer\nversion " TEV_VERSION;
        return 0;
    }

    auto ipc = make_shared<Ipc>();

    // If we're not the primary instance and did not request to open a new window,
    // simply send the to-be-opened images to the primary instance.
    if (!ipc->isPrimaryInstance() && !newWindowFlag) {
        string channelSelector;
        for (auto imageFile : get(imageFiles)) {
            if (!imageFile.empty() && imageFile[0] == ':') {
                channelSelector = imageFile.substr(1);
                continue;
            }

            try {
                ipc->sendToPrimaryInstance(tfm::format("%s:%s", path{imageFile}.make_absolute(), channelSelector));
            } catch (runtime_error e) {
                tlog::error() << tfm::format("Invalid file '%s': %s", imageFile, e.what());
            }
        }

        return 0;
    }

    Imf::setGlobalThreadCount(thread::hardware_concurrency());

    tlog::info() << "Loading window...";

    shared_ptr<SharedQueue<ImageAddition>> imagesToAdd = make_shared<SharedQueue<ImageAddition>>();
    auto tryLoadImageBackground = [&imagesToAdd](const path& path, const string& channelSelector, bool shallSelect) {
        ThreadPool::singleWorker().enqueueTask([path, channelSelector, shallSelect, &imagesToAdd] {
            auto image = tryLoadImage(path, channelSelector);
            if (image) {
                imagesToAdd->push({ shallSelect, image });
            }
        });
    };

    atomic<bool> shallShutdown = false;

    // Spawn a background thread that opens images passed via stdin.
    // To allow whitespace characters in filenames, we use the convention that
    // paths in stdin must be separated by newlines.
    thread stdinThread{[&]() {
        string channelSelector;
        while (!shallShutdown) {
            for (string imageFile; getline(cin, imageFile);) {
                if (imageFile.empty()) {
                    continue;
                }

                if (imageFile[0] == ':') {
                    channelSelector = imageFile.substr(1);
                    continue;
                }

                tryLoadImageBackground(imageFile, channelSelector, false);
            }

            this_thread::sleep_for(chrono::milliseconds{100});
        }
    }};

    // It is unfortunately not easily possible to poll/timeout on cin in a portable manner,
    // so instead we resort to simply detaching this thread, causing it to be forcefully
    // terminated as the main thread terminates.
    stdinThread.detach();

    // Spawn another background thread, this one dealing with images passed to us
    // via inter-process communication (IPC). This happens when
    // a user starts another instance of tev while one is already running. Note, that this
    // behavior can be overridden by the -n flag, so not _all_ secondary instances send their
    // paths to the primary instance.
    thread ipcThread;
    if (ipc->isPrimaryInstance()) {
        ipcThread = thread{[&]() {
            while (!shallShutdown) {
                while (ipc->receiveFromSecondaryInstance([&](const string& reveicedString) {
                    string imageString = ensureUtf8(reveicedString);
                    size_t colonPos = min(imageString.length() - 1, imageString.find_last_of(":"));
                    tryLoadImageBackground(imageString.substr(0, colonPos), imageString.substr(colonPos + 1), true);
                })) { }

                this_thread::sleep_for(chrono::milliseconds{100});
            }
        }};
    }

    // Load images passed via command line in the background prior to
    // creating our main application such that they are not stalled
    // by the potentially slow initialization of opengl / glfw.
    string channelSelector;
    for (auto imageFile : get(imageFiles)) {
        if (!imageFile.empty() && imageFile[0] == ':') {
            channelSelector = imageFile.substr(1);
            continue;
        }

        tryLoadImageBackground(imageFile, channelSelector, false);
    }

    // Init nanogui application
    nanogui::init();

    {
        auto app = unique_ptr<ImageViewer>{new ImageViewer{imagesToAdd, !imageFiles}};
        app->drawAll();
        app->setVisible(true);

        // Do what the maximize flag tells us---if it exists---and
        // maximize if we have images otherwise.
        if (maximizeFlag ? get(maximizeFlag) : imageFiles) {
            app->maximize();
        }

        // Apply parameter flags
        if (exposureFlag) { app->setExposure(get(exposureFlag)); }
        if (filterFlag)   { app->setFilter(get(filterFlag)); }
        if (gammaFlag)    { app->setGamma(get(gammaFlag)); }
        if (metricFlag)   { app->setMetric(toMetric(get(metricFlag))); }
        if (offsetFlag)   { app->setOffset(get(offsetFlag)); }
        if (tonemapFlag)  { app->setTonemap(toTonemap(get(tonemapFlag))); }

        // Refresh only every 250ms if there are no user interactions.
        // This makes an idling tev surprisingly energy-efficient. :)
        nanogui::mainloop(250);
    }

    shallShutdown = true;

    // On some linux distributions glfwTerminate() (which is called by
    // nanogui::shutdown()) causes segfaults. Since we are done with our
    // program here anyways, let's let the OS clean up after us.
    //nanogui::shutdown();

    if (ipcThread.joinable()) {
        ipcThread.join();
    }

    if (stdinThread.joinable()) {
        stdinThread.join();
    }

    // Let all threads gracefully terminate.
    ThreadPool::shutdown();

    return 0;
}

TEV_NAMESPACE_END

#ifdef _WIN32
int wmain(int argc, wchar_t* argv[]) {
#else
int main(int argc, char* argv[]) {
#endif
    try {
        vector<string> arguments;
        for (int i = 0; i < argc; ++i) {
#ifdef _WIN32
            arguments.emplace_back(tev::utf16to8(argv[i]));
#else
            string arg = argv[i];
            // OSX sometimes (seemingly sporadically) passes the
            // process serial number via a command line parameter.
            // We would like to ignore this.
            if (arg.find("-psn") != 0) {
                arguments.emplace_back(tev::ensureUtf8(argv[i]));
            }
#endif
        }

        tev::mainFunc(arguments);
    } catch (const exception& e) {
        tlog::error() << tfm::format("Uncaught exception: %s", e.what());
        return 1;
    }
}
<|endoftext|>


<commit_before>#include <memory>
#include <iostream>
#include <string>
#include <sstream>
#include <functional>
#include <utility>
#include <unordered_map>
#include <vector>
#include <nan.h>
#include <v8.h>
#include <uv.h>

#include "log.h"
#include "queue.h"
#include "worker/worker_thread.h"

using v8::Local;
using v8::Value;
using v8::Object;
using v8::String;
using v8::Number;
using v8::Function;
using v8::FunctionTemplate;
using v8::Array;
using std::shared_ptr;
using std::unique_ptr;
using std::string;
using std::ostringstream;
using std::endl;
using std::unordered_map;
using std::vector;
using std::move;
using std::make_pair;

static void handle_events_helper(uv_async_t *handle);

class Main {
public:
  Main() : worker_thread{&event_handler}
  {
    int err;

    next_command_id = 0;
    next_channel_id = NULL_CHANNEL_ID + 1;

    err = uv_async_init(uv_default_loop(), &event_handler, handle_events_helper);
    if (err) return;

    worker_thread.run();
  }

  ChannelID send_worker_command(
    const CommandAction action,
    const std::string &&root,
    unique_ptr<Nan::Callback> callback,
    bool assign_channel_id = false
  )
  {
    CommandID command_id = next_command_id;
    ChannelID channel_id = NULL_CHANNEL_ID;

    if (assign_channel_id) {
      channel_id = next_channel_id;
      next_channel_id++;
    }

    CommandPayload command_payload(next_command_id, action, move(root), channel_id);
    Message command_message(move(command_payload));

    pending_callbacks.emplace(command_id, move(callback));

    next_command_id++;

    LOGGER << "Sending command " << command_message << " to worker thread." << endl;
    worker_thread.send(move(command_message));

    return channel_id;
  }

  void use_main_log_file(string &&main_log_file)
  {
    Logger::to_file(main_log_file.c_str());
  }

  void use_worker_log_file(string &&worker_log_file, unique_ptr<Nan::Callback> callback)
  {
    send_worker_command(COMMAND_LOG_FILE, move(worker_log_file), move(callback));
  }

  void watch(string &&root, unique_ptr<Nan::Callback> ack_callback, unique_ptr<Nan::Callback> event_callback)
  {
    string root_dup(root);
    ChannelID channel_id = send_worker_command(COMMAND_ADD, move(root), move(ack_callback), true);

    channel_callbacks.emplace(channel_id, move(event_callback));
  }

  void handle_events()
  {
    Nan::HandleScope scope;

    unique_ptr<vector<Message>> accepted = worker_thread.receive_all();
    if (!accepted) {
      return;
    }

    unordered_map<ChannelID, vector<Local<Object>>> to_deliver;

    for (auto it = accepted->begin(); it != accepted->end(); ++it) {
      const AckPayload *ack_message = it->as_ack();
      if (ack_message) {
        LOGGER << "Received ack message " << *it << "." << endl;

        auto maybe_callback = pending_callbacks.find(ack_message->get_key());
        if (maybe_callback == pending_callbacks.end()) {
          LOGGER << "Ignoring unexpected ack " << *it << "." << endl;
          continue;
        }

        unique_ptr<Nan::Callback> callback = move(maybe_callback->second);
        pending_callbacks.erase(maybe_callback);

        callback->Call(0, nullptr);
        continue;
      }

      const FileSystemPayload *filesystem_message = it->as_filesystem();
      if (filesystem_message) {
        LOGGER << "Received filesystem event message " << *it << "." << endl;

        ChannelID channel_id = filesystem_message->get_channel_id();

        Local<Object> js_event = Nan::New<Object>();
        js_event->Set(
          Nan::New<String>("actionType").ToLocalChecked(),
          Nan::New<Number>(static_cast<int>(filesystem_message->get_filesystem_action()))
        );
        js_event->Set(
          Nan::New<String>("entryKind").ToLocalChecked(),
          Nan::New<Number>(static_cast<int>(filesystem_message->get_entry_kind()))
        );
        js_event->Set(
          Nan::New<String>("oldPath").ToLocalChecked(),
          Nan::New<String>(filesystem_message->get_old_path()).ToLocalChecked()
        );
        js_event->Set(
          Nan::New<String>("newPath").ToLocalChecked(),
          Nan::New<String>(filesystem_message->get_new_path()).ToLocalChecked()
        );

        to_deliver[channel_id].push_back(js_event);
        continue;
      }

      LOGGER << "Received unexpected message " << *it << "." << endl;
    }

    for (auto it = to_deliver.begin(); it != to_deliver.end(); ++it) {
      ChannelID channel_id = it->first;
      vector<Local<Object>> js_events = it->second;

      auto maybe_callback = channel_callbacks.find(channel_id);
      if (maybe_callback == channel_callbacks.end()) {
        LOGGER << "Ignoring unexpected filesystem event channel " << channel_id << "." << endl;
        continue;
      }
      shared_ptr<Nan::Callback> callback = maybe_callback->second;

      LOGGER << "Dispatching " << js_events.size()
        << " event(s) on channel " << channel_id << " to node callbacks." << endl;

      Local<Array> js_array = Nan::New<Array>(js_events.size());

      int index = 0;
      for (auto et = js_events.begin(); et != js_events.end(); ++et) {
        js_array->Set(index, *et);
        index++;
      }

      Local<Value> argv[] = {
        Nan::Null(),
        js_array
      };
      callback->Call(2, argv);
    }
  }

private:
  uv_async_t event_handler;

  WorkerThread worker_thread;

  CommandID next_command_id;
  ChannelID next_channel_id;

  unordered_map<CommandID, unique_ptr<Nan::Callback>> pending_callbacks;
  unordered_map<ChannelID, shared_ptr<Nan::Callback>> channel_callbacks;
};

static Main instance;

static void handle_events_helper(uv_async_t *handle)
{
  instance.handle_events();
}

static bool get_string_option(Local<Object>& options, const char *key_name, string &out)
{
  Nan::HandleScope scope;
  const Local<String> key = Nan::New<String>(key_name).ToLocalChecked();

  Nan::MaybeLocal<Value> as_maybe_value = Nan::Get(options, key);
  if (as_maybe_value.IsEmpty()) {
    return true;
  }
  Local<Value> as_value = as_maybe_value.ToLocalChecked();
  if (as_value->IsUndefined()) {
    return true;
  }

  if (!as_value->IsString()) {
    ostringstream message;
    message << "configure() option " << key_name << " must be a String";
    Nan::ThrowError(message.str().c_str());
    return false;
  }

  Nan::Utf8String as_string(as_value);

  if (*as_string == nullptr) {
    ostringstream message;
    message << "configure() option " << key_name << " must be a valid UTF-8 String";
    Nan::ThrowError(message.str().c_str());
    return false;
  }

  out.assign(*as_string, as_string.length());
  return true;
}

void configure(const Nan::FunctionCallbackInfo<Value> &info)
{
  string main_log_file;
  string worker_log_file;
  bool async = false;

  Nan::MaybeLocal<Object> maybe_options = Nan::To<Object>(info[0]);
  if (maybe_options.IsEmpty()) {
    Nan::ThrowError("configure() requires an option object");
    return;
  }

  Local<Object> options = maybe_options.ToLocalChecked();
  if (!get_string_option(options, "mainLogFile", main_log_file)) return;
  if (!get_string_option(options, "workerLogFile", worker_log_file)) return;

  unique_ptr<Nan::Callback> callback(new Nan::Callback(info[1].As<Function>()));

  if (!main_log_file.empty()) {
    instance.use_main_log_file(move(main_log_file));
  }

  if (!worker_log_file.empty()) {
    instance.use_worker_log_file(move(worker_log_file), move(callback));
    async = true;
  }

  if (!async) {
    callback->Call(0, 0);
  }
}

void watch(const Nan::FunctionCallbackInfo<Value> &info)
{
  if (info.Length() != 3) {
    return Nan::ThrowError("watch() requires three arguments");
  }

  Nan::MaybeLocal<String> maybe_root = Nan::To<String>(info[0]);
  if (maybe_root.IsEmpty()) {
    Nan::ThrowError("watch() requires a string as argument one");
    return;
  }
  Local<String> root_v8_string = maybe_root.ToLocalChecked();
  Nan::Utf8String root_utf8(root_v8_string);
  if (*root_utf8 == nullptr) {
    Nan::ThrowError("watch() argument one must be a valid UTF-8 string");
    return;
  }
  string root_str(*root_utf8, root_utf8.length());

  unique_ptr<Nan::Callback> ack_callback(new Nan::Callback(info[1].As<Function>()));
  unique_ptr<Nan::Callback> event_callback(new Nan::Callback(info[2].As<Function>()));

  instance.watch(move(root_str), move(ack_callback), move(event_callback));
}

void unwatch(const Nan::FunctionCallbackInfo<Value> &info)
{
  if (info.Length() != 2) {
    return Nan::ThrowError("watch() requires two arguments");
  }

  unique_ptr<Nan::Callback> ack_callback(new Nan::Callback(info[1].As<Function>()));
  ack_callback->Call(0, nullptr);
}

void initialize(Local<Object> exports)
{
  exports->Set(
    Nan::New<String>("configure").ToLocalChecked(),
    Nan::GetFunction(Nan::New<FunctionTemplate>(configure)).ToLocalChecked()
  );
  exports->Set(
    Nan::New<String>("watch").ToLocalChecked(),
    Nan::GetFunction(Nan::New<FunctionTemplate>(watch)).ToLocalChecked()
  );
  exports->Set(
    Nan::New<String>("unwatch").ToLocalChecked(),
    Nan::GetFunction(Nan::New<FunctionTemplate>(unwatch)).ToLocalChecked()
  );
}

NODE_MODULE(sfw, initialize);
<commit_msg>Assign channel IDs before send_worker_command<commit_after>#include <memory>
#include <iostream>
#include <string>
#include <sstream>
#include <functional>
#include <utility>
#include <unordered_map>
#include <vector>
#include <nan.h>
#include <v8.h>
#include <uv.h>

#include "log.h"
#include "queue.h"
#include "worker/worker_thread.h"

using v8::Local;
using v8::Value;
using v8::Object;
using v8::String;
using v8::Number;
using v8::Function;
using v8::FunctionTemplate;
using v8::Array;
using std::shared_ptr;
using std::unique_ptr;
using std::string;
using std::ostringstream;
using std::endl;
using std::unordered_map;
using std::vector;
using std::move;
using std::make_pair;

static void handle_events_helper(uv_async_t *handle);

class Main {
public:
  Main() : worker_thread{&event_handler}
  {
    int err;

    next_command_id = 0;
    next_channel_id = NULL_CHANNEL_ID + 1;

    err = uv_async_init(uv_default_loop(), &event_handler, handle_events_helper);
    if (err) return;

    worker_thread.run();
  }

  void send_worker_command(
    const CommandAction action,
    const std::string &&root,
    unique_ptr<Nan::Callback> callback,
    ChannelID channel_id = NULL_CHANNEL_ID
  )
  {
    CommandID command_id = next_command_id;

    CommandPayload command_payload(next_command_id, action, move(root), channel_id);
    Message command_message(move(command_payload));

    pending_callbacks.emplace(command_id, move(callback));

    next_command_id++;

    LOGGER << "Sending command " << command_message << " to worker thread." << endl;
    worker_thread.send(move(command_message));
  }

  void use_main_log_file(string &&main_log_file)
  {
    Logger::to_file(main_log_file.c_str());
  }

  void use_worker_log_file(string &&worker_log_file, unique_ptr<Nan::Callback> callback)
  {
    send_worker_command(COMMAND_LOG_FILE, move(worker_log_file), move(callback));
  }

  void watch(string &&root, unique_ptr<Nan::Callback> ack_callback, unique_ptr<Nan::Callback> event_callback)
  {
    ChannelID channel_id = next_channel_id;
    next_channel_id++;

    channel_callbacks.emplace(channel_id, move(event_callback));

    send_worker_command(COMMAND_ADD, move(root), move(ack_callback), channel_id);
  }
  }

  void handle_events()
  {
    Nan::HandleScope scope;

    unique_ptr<vector<Message>> accepted = worker_thread.receive_all();
    if (!accepted) {
      return;
    }

    unordered_map<ChannelID, vector<Local<Object>>> to_deliver;

    for (auto it = accepted->begin(); it != accepted->end(); ++it) {
      const AckPayload *ack_message = it->as_ack();
      if (ack_message) {
        LOGGER << "Received ack message " << *it << "." << endl;

        auto maybe_callback = pending_callbacks.find(ack_message->get_key());
        if (maybe_callback == pending_callbacks.end()) {
          LOGGER << "Ignoring unexpected ack " << *it << "." << endl;
          continue;
        }

        unique_ptr<Nan::Callback> callback = move(maybe_callback->second);
        pending_callbacks.erase(maybe_callback);

        callback->Call(0, nullptr);
        continue;
      }

      const FileSystemPayload *filesystem_message = it->as_filesystem();
      if (filesystem_message) {
        LOGGER << "Received filesystem event message " << *it << "." << endl;

        ChannelID channel_id = filesystem_message->get_channel_id();

        Local<Object> js_event = Nan::New<Object>();
        js_event->Set(
          Nan::New<String>("actionType").ToLocalChecked(),
          Nan::New<Number>(static_cast<int>(filesystem_message->get_filesystem_action()))
        );
        js_event->Set(
          Nan::New<String>("entryKind").ToLocalChecked(),
          Nan::New<Number>(static_cast<int>(filesystem_message->get_entry_kind()))
        );
        js_event->Set(
          Nan::New<String>("oldPath").ToLocalChecked(),
          Nan::New<String>(filesystem_message->get_old_path()).ToLocalChecked()
        );
        js_event->Set(
          Nan::New<String>("newPath").ToLocalChecked(),
          Nan::New<String>(filesystem_message->get_new_path()).ToLocalChecked()
        );

        to_deliver[channel_id].push_back(js_event);
        continue;
      }

      LOGGER << "Received unexpected message " << *it << "." << endl;
    }

    for (auto it = to_deliver.begin(); it != to_deliver.end(); ++it) {
      ChannelID channel_id = it->first;
      vector<Local<Object>> js_events = it->second;

      auto maybe_callback = channel_callbacks.find(channel_id);
      if (maybe_callback == channel_callbacks.end()) {
        LOGGER << "Ignoring unexpected filesystem event channel " << channel_id << "." << endl;
        continue;
      }
      shared_ptr<Nan::Callback> callback = maybe_callback->second;

      LOGGER << "Dispatching " << js_events.size()
        << " event(s) on channel " << channel_id << " to node callbacks." << endl;

      Local<Array> js_array = Nan::New<Array>(js_events.size());

      int index = 0;
      for (auto et = js_events.begin(); et != js_events.end(); ++et) {
        js_array->Set(index, *et);
        index++;
      }

      Local<Value> argv[] = {
        Nan::Null(),
        js_array
      };
      callback->Call(2, argv);
    }
  }

private:
  uv_async_t event_handler;

  WorkerThread worker_thread;

  CommandID next_command_id;
  ChannelID next_channel_id;

  unordered_map<CommandID, unique_ptr<Nan::Callback>> pending_callbacks;
  unordered_map<ChannelID, shared_ptr<Nan::Callback>> channel_callbacks;
};

static Main instance;

static void handle_events_helper(uv_async_t *handle)
{
  instance.handle_events();
}

static bool get_string_option(Local<Object>& options, const char *key_name, string &out)
{
  Nan::HandleScope scope;
  const Local<String> key = Nan::New<String>(key_name).ToLocalChecked();

  Nan::MaybeLocal<Value> as_maybe_value = Nan::Get(options, key);
  if (as_maybe_value.IsEmpty()) {
    return true;
  }
  Local<Value> as_value = as_maybe_value.ToLocalChecked();
  if (as_value->IsUndefined()) {
    return true;
  }

  if (!as_value->IsString()) {
    ostringstream message;
    message << "configure() option " << key_name << " must be a String";
    Nan::ThrowError(message.str().c_str());
    return false;
  }

  Nan::Utf8String as_string(as_value);

  if (*as_string == nullptr) {
    ostringstream message;
    message << "configure() option " << key_name << " must be a valid UTF-8 String";
    Nan::ThrowError(message.str().c_str());
    return false;
  }

  out.assign(*as_string, as_string.length());
  return true;
}

void configure(const Nan::FunctionCallbackInfo<Value> &info)
{
  string main_log_file;
  string worker_log_file;
  bool async = false;

  Nan::MaybeLocal<Object> maybe_options = Nan::To<Object>(info[0]);
  if (maybe_options.IsEmpty()) {
    Nan::ThrowError("configure() requires an option object");
    return;
  }

  Local<Object> options = maybe_options.ToLocalChecked();
  if (!get_string_option(options, "mainLogFile", main_log_file)) return;
  if (!get_string_option(options, "workerLogFile", worker_log_file)) return;

  unique_ptr<Nan::Callback> callback(new Nan::Callback(info[1].As<Function>()));

  if (!main_log_file.empty()) {
    instance.use_main_log_file(move(main_log_file));
  }

  if (!worker_log_file.empty()) {
    instance.use_worker_log_file(move(worker_log_file), move(callback));
    async = true;
  }

  if (!async) {
    callback->Call(0, 0);
  }
}

void watch(const Nan::FunctionCallbackInfo<Value> &info)
{
  if (info.Length() != 3) {
    return Nan::ThrowError("watch() requires three arguments");
  }

  Nan::MaybeLocal<String> maybe_root = Nan::To<String>(info[0]);
  if (maybe_root.IsEmpty()) {
    Nan::ThrowError("watch() requires a string as argument one");
    return;
  }
  Local<String> root_v8_string = maybe_root.ToLocalChecked();
  Nan::Utf8String root_utf8(root_v8_string);
  if (*root_utf8 == nullptr) {
    Nan::ThrowError("watch() argument one must be a valid UTF-8 string");
    return;
  }
  string root_str(*root_utf8, root_utf8.length());

  unique_ptr<Nan::Callback> ack_callback(new Nan::Callback(info[1].As<Function>()));
  unique_ptr<Nan::Callback> event_callback(new Nan::Callback(info[2].As<Function>()));

  instance.watch(move(root_str), move(ack_callback), move(event_callback));
}

void unwatch(const Nan::FunctionCallbackInfo<Value> &info)
{
  if (info.Length() != 2) {
    return Nan::ThrowError("watch() requires two arguments");
  }

  unique_ptr<Nan::Callback> ack_callback(new Nan::Callback(info[1].As<Function>()));
  ack_callback->Call(0, nullptr);
}

void initialize(Local<Object> exports)
{
  exports->Set(
    Nan::New<String>("configure").ToLocalChecked(),
    Nan::GetFunction(Nan::New<FunctionTemplate>(configure)).ToLocalChecked()
  );
  exports->Set(
    Nan::New<String>("watch").ToLocalChecked(),
    Nan::GetFunction(Nan::New<FunctionTemplate>(watch)).ToLocalChecked()
  );
  exports->Set(
    Nan::New<String>("unwatch").ToLocalChecked(),
    Nan::GetFunction(Nan::New<FunctionTemplate>(unwatch)).ToLocalChecked()
  );
}

NODE_MODULE(sfw, initialize);
<|endoftext|>


<commit_before>/*********************************************************************************
 *
 * Inviwo - Interactive Visualization Workshop
 *
 * Copyright (c) 2016 Inviwo Foundation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *********************************************************************************/

#include <modules/base/processors/randommeshgenerator.h>
#include <inviwo/core/datastructures/geometry/basicmesh.h>

namespace inviwo {

// The Class Identifier has to be globally unique. Use a reverse DNS naming scheme
const ProcessorInfo RandomMeshGenerator::processorInfo_{
    "org.inviwo.RandomMeshGenerator",  // Class identifier
    "Random Mesh Generator",           // Display name
    "Undefined",                       // Category
    CodeState::Experimental,           // Code state
    Tags::None,                        // Tags
};

const ProcessorInfo RandomMeshGenerator::getProcessorInfo() const { return processorInfo_; }

RandomMeshGenerator::RandomMeshGenerator()
    : Processor()
    , mesh_("mesh")
    , seed_("seed", "Seed", 0, 0, std::mt19937::max())
    , rand_()
    , reseed_("reseed_", "Seed")
    , numberOfBoxes_("numberOf_", "Number of Boxes", 1, 0, 100)
    , numberOfSpheres_("numberOfSpheres_", "Number of Spheres", 1, 0, 100)
    , numberOfCylinders_("numberOfCylinders_", "Number of cylinders", 1, 0, 100)
    , numberOfCones_("numberOfCones_", "Number of Cones", 1, 0, 100)
    , numberOfToruses_("numberOfToruses", "Number of Toruses", 1, 0, 100)

{

    addPort(mesh_);
    addProperty(numberOfBoxes_);
    addProperty(numberOfSpheres_);
    addProperty(numberOfCylinders_);
    addProperty(numberOfCones_);
    addProperty(numberOfToruses_);

    addProperty(seed_);
    addProperty(reseed_);

    reseed_.onChange([&]() {
        seed_.set(rand());
        rand_.seed(static_cast<std::mt19937::result_type>(seed_.get()));
    });
}

float RandomMeshGenerator::randD() { return dis_(rand_); }

float RandomMeshGenerator::randD(const float min, const float max) {
    return min + randD() * (max - min);
}

inviwo::vec4 RandomMeshGenerator::randColor() {
    float r = randD(0.5, 1.0);
    float g = randD(0.5, 1.0);
    float b = randD(0.5, 1.0);
    return vec4(r, g, b, 1);
}

vec3 RandomMeshGenerator::randVec3(const float min, const float max) {
    float x = randD(min, max);
    float y = randD(min, max);
    float z = randD(min, max);
    return vec3(x, y, z);
}

void RandomMeshGenerator::process() {
    auto mesh = std::make_shared<BasicMesh>();
    mesh_.setData(mesh);

    rand_.seed(static_cast<std::mt19937::result_type>(seed_.get()));

    for (int i = 0; i < numberOfBoxes_.get(); i++) {
        mat4 o = glm::rotate(mat4(1.0f), randD(0, 6.28f), vec3(1, 0, 0));
        o = glm::rotate(o, randD(0, 6.28f), vec3(0, 1, 0));
        o = glm::rotate(o, randD(0, 6.28f), vec3(0, 0, 1));
        o = glm::translate(o, randVec3());
        o = glm::scale(o, randVec3());
        auto mesh2 = BasicMesh::cube(o, randColor());
        mesh->append(mesh2.get());
    }
    for (int i = 0; i < numberOfSpheres_.get(); i++) {
        auto radius = randD(0.1f, 1.0f);
        auto color = randColor();
        auto center = randVec3();
        BasicMesh::sphere(center, radius, color, mesh);
    }
    for (int i = 0; i < numberOfCylinders_.get(); i++) {
        auto radius = randD(0.1f, 1.0f);
        auto color = randColor();
        auto end = randVec3();
        auto start = randVec3();
        auto mesh2 = BasicMesh::cylinder(start, end, color, radius);
        mesh->append(mesh2.get());
    }
    for (int i = 0; i < numberOfCones_.get(); i++) {
        auto radius = randD(0.1f, 1.0f);
        auto color = randColor();
        auto end = randVec3();
        auto start = randVec3();
        auto mesh2 = BasicMesh::cone(start, end, color, radius);
        mesh->append(mesh2.get());
    }

    for (int i = 0; i < numberOfToruses_.get(); i++) {
        auto center = randVec3();
        auto up = glm::normalize(randVec3());
        auto r2 = randD(0.1, 0.5f);
        auto r1 = randD(0.1 + r2, 1.0f + r2);
        auto color = randColor();
        auto mesh2 = BasicMesh::torus(center, up, r1, r2, ivec2(32, 8), color);
        mesh->append(mesh2.get());
    }
}

}  // namespace
<commit_msg>Base: RandomMeshGenerator: Added prcessor category<commit_after>/*********************************************************************************
 *
 * Inviwo - Interactive Visualization Workshop
 *
 * Copyright (c) 2016 Inviwo Foundation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *********************************************************************************/

#include <modules/base/processors/randommeshgenerator.h>
#include <inviwo/core/datastructures/geometry/basicmesh.h>

namespace inviwo {

// The Class Identifier has to be globally unique. Use a reverse DNS naming scheme
const ProcessorInfo RandomMeshGenerator::processorInfo_{
    "org.inviwo.RandomMeshGenerator",  // Class identifier
    "Random Mesh Generator",           // Display name
    "Mesh Creation",                   // Category
    CodeState::Experimental,           // Code state
    Tags::None,                        // Tags
};

const ProcessorInfo RandomMeshGenerator::getProcessorInfo() const { return processorInfo_; }

RandomMeshGenerator::RandomMeshGenerator()
    : Processor()
    , mesh_("mesh")
    , seed_("seed", "Seed", 0, 0, std::mt19937::max())
    , rand_()
    , reseed_("reseed_", "Seed")
    , numberOfBoxes_("numberOf_", "Number of Boxes", 1, 0, 100)
    , numberOfSpheres_("numberOfSpheres_", "Number of Spheres", 1, 0, 100)
    , numberOfCylinders_("numberOfCylinders_", "Number of cylinders", 1, 0, 100)
    , numberOfCones_("numberOfCones_", "Number of Cones", 1, 0, 100)
    , numberOfToruses_("numberOfToruses", "Number of Toruses", 1, 0, 100)

{

    addPort(mesh_);
    addProperty(numberOfBoxes_);
    addProperty(numberOfSpheres_);
    addProperty(numberOfCylinders_);
    addProperty(numberOfCones_);
    addProperty(numberOfToruses_);

    addProperty(seed_);
    addProperty(reseed_);

    reseed_.onChange([&]() {
        seed_.set(rand());
        rand_.seed(static_cast<std::mt19937::result_type>(seed_.get()));
    });
}

float RandomMeshGenerator::randD() { return dis_(rand_); }

float RandomMeshGenerator::randD(const float min, const float max) {
    return min + randD() * (max - min);
}

inviwo::vec4 RandomMeshGenerator::randColor() {
    float r = randD(0.5, 1.0);
    float g = randD(0.5, 1.0);
    float b = randD(0.5, 1.0);
    return vec4(r, g, b, 1);
}

vec3 RandomMeshGenerator::randVec3(const float min, const float max) {
    float x = randD(min, max);
    float y = randD(min, max);
    float z = randD(min, max);
    return vec3(x, y, z);
}

void RandomMeshGenerator::process() {
    auto mesh = std::make_shared<BasicMesh>();
    mesh_.setData(mesh);

    rand_.seed(static_cast<std::mt19937::result_type>(seed_.get()));

    for (int i = 0; i < numberOfBoxes_.get(); i++) {
        mat4 o = glm::rotate(mat4(1.0f), randD(0, 6.28f), vec3(1, 0, 0));
        o = glm::rotate(o, randD(0, 6.28f), vec3(0, 1, 0));
        o = glm::rotate(o, randD(0, 6.28f), vec3(0, 0, 1));
        o = glm::translate(o, randVec3());
        o = glm::scale(o, randVec3());
        auto mesh2 = BasicMesh::cube(o, randColor());
        mesh->append(mesh2.get());
    }
    for (int i = 0; i < numberOfSpheres_.get(); i++) {
        auto radius = randD(0.1f, 1.0f);
        auto color = randColor();
        auto center = randVec3();
        BasicMesh::sphere(center, radius, color, mesh);
    }
    for (int i = 0; i < numberOfCylinders_.get(); i++) {
        auto radius = randD(0.1f, 1.0f);
        auto color = randColor();
        auto end = randVec3();
        auto start = randVec3();
        auto mesh2 = BasicMesh::cylinder(start, end, color, radius);
        mesh->append(mesh2.get());
    }
    for (int i = 0; i < numberOfCones_.get(); i++) {
        auto radius = randD(0.1f, 1.0f);
        auto color = randColor();
        auto end = randVec3();
        auto start = randVec3();
        auto mesh2 = BasicMesh::cone(start, end, color, radius);
        mesh->append(mesh2.get());
    }

    for (int i = 0; i < numberOfToruses_.get(); i++) {
        auto center = randVec3();
        auto up = glm::normalize(randVec3());
        auto r2 = randD(0.1, 0.5f);
        auto r1 = randD(0.1 + r2, 1.0f + r2);
        auto color = randColor();
        auto mesh2 = BasicMesh::torus(center, up, r1, r2, ivec2(32, 8), color);
        mesh->append(mesh2.get());
    }
}

}  // namespace
<|endoftext|>


<commit_before>#include "extend_program_demo.h"
#include "lua_function_caller.h"
#include "print_lua_stack.h"
#include "simple_lua_interpreter.h"

#include "lua_51/lua.hpp"
#include "include/inc.h"
#include <string>

using namespace elloop;
using namespace std;

int main() {

    // simple_lua_interprator();
    // testPrintStackFunction();
    // testExtendProgram();
    // testLuaCaller();

    lua_State* lua = luaL_newstate();

    luaL_loadfile(lua, "res/config.lua");

    lua_pcall(lua, 0, 0, 0);

    lua_getglobal(lua, "f");

    if (!lua_isfunction(lua, -1)) {
        error(lua, "f is not a function");
    }

    lua_pushnumber(lua, 10);

    int luaError = lua_pcall(lua, 1, 1, 0);
    if (luaError) {
        error(lua, "fail to call f: %s", lua_tostring(lua, -1));
    }

    double result = lua_tonumber(lua, -1);

    psln(result); // result = 10*10 + 2 * 10 + 100 = 220

    lua_close(lua);

    return 0;
}
<commit_msg>c++ call lua function: more explain about lua_pcall<commit_after>#include "extend_program_demo.h"
#include "lua_function_caller.h"
#include "print_lua_stack.h"
#include "simple_lua_interpreter.h"

int main() {
    // simple_lua_interprator();
    // testPrintStackFunction();
    // testExtendProgram();
    testLuaCaller();

    return 0;
}
<|endoftext|>


<commit_before>#include "interactive_base.hpp"

TEST_F(InteractiveTest, expand_ctrlc1) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  this->sendLine("echo "
                 "{/*/../*/../*/../*/../*/../*/../*/../*/../*,/*/../*/../*/../*/../*/../*/../*/../"
                 "*/../*,/*/../*/../*/../*/../*/../*/../*/../*/../*}");
  ASSERT_NO_FATAL_FAILURE(
      this->expect(PROMPT + "echo "
                            "{/*/../*/../*/../*/../*/../*/../*/../*/../*,/*/../*/../*/../*/../*/../"
                            "*/../*/../*/../*,/*/../*/../*/../*/../*/../*/../*/../*/../*}\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  this->send(CTRL_C);

  std::string err = format(R"([runtime error]
SystemError: glob expansion is canceled, caused by `%s'
    from (stdin):1 '<toplevel>()'
)",
                           strerror(EINTR));

  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 1));
}

TEST_F(InteractiveTest, expand_ctrlc2) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  this->sendLine("echo {1..9999999999}");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "echo {1..9999999999}\n"));
  this->send(CTRL_C);

  std::string err = format(R"([runtime error]
SystemError: brace expansion is canceled, caused by `%s'
    from (stdin):1 '<toplevel>()'
)",
                           strerror(EINTR));

  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 1));
}

TEST_F(InteractiveTest, wait_ctrlc1) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("var j = while(true){} &"));
  this->sendLine("$j.wait()");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "$j.wait()\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  this->send(CTRL_C);

  std::string err = format(R"([runtime error]
SystemError: wait failed, caused by `%s'
    from (stdin):2 '<toplevel>()'
)",
                           strerror(EINTR));

  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 1));
}

TEST_F(InteractiveTest, wait_ctrlc2) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("while(true){} &", ": Job = %1"));
  this->sendLine("fg");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "fg\nwhile(true){}\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  this->send(CTRL_C);

  std::string err = strsignal(SIGINT);
  err += "\n";
  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 128 + SIGINT));
}

TEST_F(InteractiveTest, ctrlz1) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  this->sendLine("sh -c 'while true; do true; done'");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "sh -c 'while true; do true; done'\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  this->send(CTRL_Z);
  ASSERT_NO_FATAL_FAILURE(
      this->expect(promptAfterCtrlZ(PROMPT), "[1] + Stopped  sh -c while true; do true; done\n"));

  // send CTRL_C, but already stopped.
  this->send(CTRL_C);
  ASSERT_NO_FATAL_FAILURE(this->expect("\n" + PROMPT));

  // resume and kill
  this->sendLine("fg");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "fg\nsh -c while true; do true; done\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  this->send(CTRL_C);

  std::string err = strsignal(SIGINT);
  err += "\n";
  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 128 + SIGINT));
}

TEST_F(InteractiveTest, ctrlz2) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("while(true 1){} &", ": Job = %1"));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("while(true 2){} &", ": Job = %2"));

  // foreground and suspend
  this->sendLine("fg");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "fg\nwhile(true 2){}\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  this->send(CTRL_Z);
  ASSERT_NO_FATAL_FAILURE(
      this->expect(promptAfterCtrlZ(PROMPT), "[2] + Stopped  while(true 2){}\n"));

  // resume all jobs
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("bg %1 %2", "[1]  while(true 1){}\n"
                                                              "[2]  while(true 2){}"));

  // show jobs list
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("jobs", "[1] - Running  while(true 1){}\n"
                                                          "[2] + Running  while(true 2){}"));

  // foreground [2] and interrupt
  this->sendLine("fg");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "fg\nwhile(true 2){}\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(300));
  this->send(CTRL_C);

  std::string err = strsignal(SIGINT);
  err += "\n";
  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));

  // foreground [1] and interrupt
  this->sendLine("fg");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "fg\nwhile(true 1){}\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(300));
  this->send(CTRL_C);

  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));

  // show empty jobs
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("jobs"));

  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 0));
}

TEST_F(InteractiveTest, ctrlz3) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("loop() { while(true $@){} }"));
  this->sendLine("while(true 1){}  |  loop 2");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "while(true 1){}  |  loop 2\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  this->send(CTRL_Z);
  ASSERT_NO_FATAL_FAILURE(
      this->expect(promptAfterCtrlZ(PROMPT), "[1] + Stopped  while(true 1){} | loop 2\n"));

  // resume and kill
  this->sendLine("fg");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "fg\nwhile(true 1){} | loop 2\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  this->send(CTRL_C);

  std::string err = strsignal(SIGINT);
  err += "\n";
  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 128 + SIGINT));
}

TEST_F(InteractiveTest, cmdsub_ctrlz1) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  const char *line = "var a = \"$(while(true){})\"";
  this->sendLine(line);
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + line + "\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));

  // send CTRL_Z, but not stopped (due to ignore SIGTSTP)
  this->send(CTRL_Z);
  ASSERT_NO_FATAL_FAILURE(this->expect(ctrlZChar()));
  std::this_thread::sleep_for(std::chrono::milliseconds(200));

  // send 'echo hello', but still wait output (not stopped)
  this->sendLine("echo hello");
  ASSERT_NO_FATAL_FAILURE(this->expect("echo hello\n"));

  // send CTRL_C and terminated
  this->send(CTRL_C);

  std::string err = format(R"([runtime error]
SystemError: command substitution failed, caused by `%s'
    from (stdin):1 '<toplevel>()'
)",
                           strerror(EINTR));
  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));
  err = format("^\\[1\\] \\+ [0-9]+ %s  while\\(true\\)\\{\\}\n", strsignal(SIGINT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpectRegex("1", ": Int = 1", err));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 1));
}

TEST_F(InteractiveTest, cmdsub_ctrlz2) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));

  // launch new ydsh (new process group)
  this->sendLine("eval $YDSH_BIN --quiet --norc");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "eval $YDSH_BIN --quiet --norc\n" + PROMPT));

  const char *line = "var a = $(while(true){})";
  this->sendLine(line);
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + line + "\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));

  // send CTRL_Z, but not stopped (due to ignore SIGTSTP)
  this->send(CTRL_Z);
  ASSERT_NO_FATAL_FAILURE(this->expect(ctrlZChar()));
  std::this_thread::sleep_for(std::chrono::milliseconds(200));

  // send 'echo hello', but still wait output (not stopped)
  this->sendLine("echo hello");
  ASSERT_NO_FATAL_FAILURE(this->expect("echo hello\n"));

  // send CTRL_C and terminated
  this->send(CTRL_C);

  std::string err = format(R"([runtime error]
SystemError: command substitution failed, caused by `%s'
    from (stdin):1 '<toplevel>()'
)",
                           strerror(EINTR));
  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));
  err = format("^\\[1\\] \\+ [0-9]+ %s  while\\(true\\)\\{\\}\n", strsignal(SIGINT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpectRegex("2", ": Int = 2", err));
  this->send(CTRL_D);
  ASSERT_NO_FATAL_FAILURE(this->expect("\n" + PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 1));
}

TEST_F(InteractiveTest, wait1) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("sleep 1 &", ": Job = %1"));
  std::this_thread::sleep_for(std::chrono::seconds(1));
  ASSERT_NO_FATAL_FAILURE(
      this->sendLineAndExpectRegex("wait", "", "^\\[1\\] \\+ [0-9]+ Done  sleep 1\n"));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 0));
}

TEST_F(InteractiveTest, wait2) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("{ sleep 1; exit 45; } &", ": Job = %1"));
  std::this_thread::sleep_for(std::chrono::seconds(1));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpectRegex(
      "1230 + 4", ": Int = 1234", "^\\[1\\] \\+ [0-9]+ Exit 45  \\{ sleep 1; exit 45; \\}\n"));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 0));
}

TEST_F(InteractiveTest, wait3) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("var j = while(true){} &"));

  std::string err = format("^\\[1\\] \\+ [0-9]+ %s  while\\(true\\)\\{\\}\n", strsignal(SIGKILL));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpectRegex("$j.raise($SIGKILL); wait $j", "", err));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 137));
}

TEST_F(InteractiveTest, wait4) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("var j = while(true){} &!"));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("jobs")); // disowned job is not printed
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("$j.raise($SIGKILL); $j.wait()", ": Int = 137"));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 0));
}

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}<commit_msg>update builtin wait command test cases in interactive mode. close #603<commit_after>#include "interactive_base.hpp"

TEST_F(InteractiveTest, expand_ctrlc1) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  this->sendLine("echo "
                 "{/*/../*/../*/../*/../*/../*/../*/../*/../*,/*/../*/../*/../*/../*/../*/../*/../"
                 "*/../*,/*/../*/../*/../*/../*/../*/../*/../*/../*}");
  ASSERT_NO_FATAL_FAILURE(
      this->expect(PROMPT + "echo "
                            "{/*/../*/../*/../*/../*/../*/../*/../*/../*,/*/../*/../*/../*/../*/../"
                            "*/../*/../*/../*,/*/../*/../*/../*/../*/../*/../*/../*/../*}\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  this->send(CTRL_C);

  std::string err = format(R"([runtime error]
SystemError: glob expansion is canceled, caused by `%s'
    from (stdin):1 '<toplevel>()'
)",
                           strerror(EINTR));

  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 1));
}

TEST_F(InteractiveTest, expand_ctrlc2) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  this->sendLine("echo {1..9999999999}");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "echo {1..9999999999}\n"));
  this->send(CTRL_C);

  std::string err = format(R"([runtime error]
SystemError: brace expansion is canceled, caused by `%s'
    from (stdin):1 '<toplevel>()'
)",
                           strerror(EINTR));

  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 1));
}

TEST_F(InteractiveTest, wait_ctrlc1) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("var j = while(true){} &"));
  this->sendLine("$j.wait()");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "$j.wait()\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  this->send(CTRL_C);

  std::string err = format(R"([runtime error]
SystemError: wait failed, caused by `%s'
    from (stdin):2 '<toplevel>()'
)",
                           strerror(EINTR));

  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 1));
}

TEST_F(InteractiveTest, wait_ctrlc2) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("while(true){} &", ": Job = %1"));
  this->sendLine("fg");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "fg\nwhile(true){}\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  this->send(CTRL_C);

  std::string err = strsignal(SIGINT);
  err += "\n";
  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 128 + SIGINT));
}

TEST_F(InteractiveTest, ctrlz1) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  this->sendLine("sh -c 'while true; do true; done'");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "sh -c 'while true; do true; done'\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  this->send(CTRL_Z);
  ASSERT_NO_FATAL_FAILURE(
      this->expect(promptAfterCtrlZ(PROMPT), "[1] + Stopped  sh -c while true; do true; done\n"));

  // send CTRL_C, but already stopped.
  this->send(CTRL_C);
  ASSERT_NO_FATAL_FAILURE(this->expect("\n" + PROMPT));

  // resume and kill
  this->sendLine("fg");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "fg\nsh -c while true; do true; done\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  this->send(CTRL_C);

  std::string err = strsignal(SIGINT);
  err += "\n";
  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 128 + SIGINT));
}

TEST_F(InteractiveTest, ctrlz2) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("while(true 1){} &", ": Job = %1"));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("while(true 2){} &", ": Job = %2"));

  // foreground and suspend
  this->sendLine("fg");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "fg\nwhile(true 2){}\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  this->send(CTRL_Z);
  ASSERT_NO_FATAL_FAILURE(
      this->expect(promptAfterCtrlZ(PROMPT), "[2] + Stopped  while(true 2){}\n"));

  // resume all jobs
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("bg %1 %2", "[1]  while(true 1){}\n"
                                                              "[2]  while(true 2){}"));

  // show jobs list
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("jobs", "[1] - Running  while(true 1){}\n"
                                                          "[2] + Running  while(true 2){}"));

  // foreground [2] and interrupt
  this->sendLine("fg");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "fg\nwhile(true 2){}\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(300));
  this->send(CTRL_C);

  std::string err = strsignal(SIGINT);
  err += "\n";
  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));

  // foreground [1] and interrupt
  this->sendLine("fg");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "fg\nwhile(true 1){}\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(300));
  this->send(CTRL_C);

  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));

  // show empty jobs
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("jobs"));

  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 0));
}

TEST_F(InteractiveTest, ctrlz3) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("loop() { while(true $@){} }"));
  this->sendLine("while(true 1){}  |  loop 2");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "while(true 1){}  |  loop 2\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  this->send(CTRL_Z);
  ASSERT_NO_FATAL_FAILURE(
      this->expect(promptAfterCtrlZ(PROMPT), "[1] + Stopped  while(true 1){} | loop 2\n"));

  // resume and kill
  this->sendLine("fg");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "fg\nwhile(true 1){} | loop 2\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  this->send(CTRL_C);

  std::string err = strsignal(SIGINT);
  err += "\n";
  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 128 + SIGINT));
}

TEST_F(InteractiveTest, cmdsub_ctrlz1) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  const char *line = "var a = \"$(while(true){})\"";
  this->sendLine(line);
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + line + "\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));

  // send CTRL_Z, but not stopped (due to ignore SIGTSTP)
  this->send(CTRL_Z);
  ASSERT_NO_FATAL_FAILURE(this->expect(ctrlZChar()));
  std::this_thread::sleep_for(std::chrono::milliseconds(200));

  // send 'echo hello', but still wait output (not stopped)
  this->sendLine("echo hello");
  ASSERT_NO_FATAL_FAILURE(this->expect("echo hello\n"));

  // send CTRL_C and terminated
  this->send(CTRL_C);

  std::string err = format(R"([runtime error]
SystemError: command substitution failed, caused by `%s'
    from (stdin):1 '<toplevel>()'
)",
                           strerror(EINTR));
  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));
  err = format("^\\[1\\] \\+ [0-9]+ %s  while\\(true\\)\\{\\}\n", strsignal(SIGINT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpectRegex("1", ": Int = 1", err));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 1));
}

TEST_F(InteractiveTest, cmdsub_ctrlz2) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));

  // launch new ydsh (new process group)
  this->sendLine("eval $YDSH_BIN --quiet --norc");
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + "eval $YDSH_BIN --quiet --norc\n" + PROMPT));

  const char *line = "var a = $(while(true){})";
  this->sendLine(line);
  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT + line + "\n"));
  std::this_thread::sleep_for(std::chrono::milliseconds(500));

  // send CTRL_Z, but not stopped (due to ignore SIGTSTP)
  this->send(CTRL_Z);
  ASSERT_NO_FATAL_FAILURE(this->expect(ctrlZChar()));
  std::this_thread::sleep_for(std::chrono::milliseconds(200));

  // send 'echo hello', but still wait output (not stopped)
  this->sendLine("echo hello");
  ASSERT_NO_FATAL_FAILURE(this->expect("echo hello\n"));

  // send CTRL_C and terminated
  this->send(CTRL_C);

  std::string err = format(R"([runtime error]
SystemError: command substitution failed, caused by `%s'
    from (stdin):1 '<toplevel>()'
)",
                           strerror(EINTR));
  ASSERT_NO_FATAL_FAILURE(this->expect(promptAfterCtrlC(PROMPT), err));
  err = format("^\\[1\\] \\+ [0-9]+ %s  while\\(true\\)\\{\\}\n", strsignal(SIGINT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpectRegex("2", ": Int = 2", err));
  this->send(CTRL_D);
  ASSERT_NO_FATAL_FAILURE(this->expect("\n" + PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 1));
}

TEST_F(InteractiveTest, wait1) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("sleep 1 &", ": Job = %1"));
  std::this_thread::sleep_for(std::chrono::seconds(1));
  ASSERT_NO_FATAL_FAILURE(
      this->sendLineAndExpectRegex("wait", "", "^\\[1\\] \\+ [0-9]+ Done  sleep 1\n"));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 0));
}

TEST_F(InteractiveTest, wait2) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("{ sleep 1; exit 45; } &", ": Job = %1"));
  std::this_thread::sleep_for(std::chrono::seconds(1));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpectRegex(
      "1230 + 4", ": Int = 1234", "^\\[1\\] \\+ [0-9]+ Exit 45  \\{ sleep 1; exit 45; \\}\n"));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 0));
}

TEST_F(InteractiveTest, wait3) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("var j = while(true){} &"));
  ASSERT_NO_FATAL_FAILURE(
      this->sendLineAndExpect("$(wait $j); assert $? == 255", ": [String] = []"));

  std::string err = format("^\\[1\\] \\+ [0-9]+ %s  while\\(true\\)\\{\\}\n", strsignal(SIGKILL));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpectRegex("$j.raise($SIGKILL); wait $j", "", err));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 137));
}

TEST_F(InteractiveTest, wait4) {
  this->invoke("--quiet", "--norc");

  ASSERT_NO_FATAL_FAILURE(this->expect(PROMPT));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("var j = while(true){} &!"));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("jobs")); // disowned job is not printed
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("jobs $j", "", "ydsh: jobs: %1: no such job\n"));
  ASSERT_NO_FATAL_FAILURE(
      this->sendLineAndExpect("$(jobs $j)", ": [String] = []", "ydsh: jobs: %1: no such job\n"));
  ASSERT_NO_FATAL_FAILURE(
      this->sendLineAndExpect("wait $j; assert $? == 127", "", "ydsh: wait: %1: no such job\n"));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndExpect("$j.raise($SIGKILL); $j.wait()", ": Int = 137"));
  ASSERT_NO_FATAL_FAILURE(this->sendLineAndWait("exit", 127));
}

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}<|endoftext|>


<commit_before>#include "coder.h"
#include "exception.h"

namespace LeviDB {
    void Holder::plus(int idx, const int val) noexcept {
        total += val;
        while (true) {
            cum_cnt[idx] += val;
            idx += (idx & (-idx));
            if (idx > CoderConst::holder_size - 1) {
                break;
            }
        }
        if (total >= UINT16_MAX / 4) {
            halve();
        }
    }

    int Holder::getCum(int idx) const noexcept {
        int sum = cum_cnt[0];
        while (idx > 0) {
            sum += cum_cnt[idx];
            idx &= (idx - 1);
        }
        return sum;
    }

    void Holder::halve() noexcept {
        int gap[CoderConst::FN + 1];
        int lo = 0;
        for (int i = 0; i <= CoderConst::FN; ++i) {
            int hi = getCum(i + 1);
            gap[i] = hi - lo;
            lo = hi;
        }

        total = 0;
        memset(cum_cnt, 0, sizeof(cum_cnt));
        for (int i = 0; i <= CoderConst::FN; ++i) {
            if (gap[i] >= 1) {
                plus(i + 1, std::max(1, gap[i] / 2));
            }
        }
    }

    int Holder::firstGreater(const int cum) const noexcept {
        int lo = 0;
        int hi = CoderConst::FN + 1;
        while (lo < hi) {
            int mi = (lo + hi) / 2;
            if (cum < getCum(mi)) {
                hi = mi;
            } else {
                lo = mi + 1;
            }
        }
        return lo;
    }

    template<bool _>
    void SubCoder<_>::pushBit(const bool bit, std::vector<uint8_t> & output, int & nth_bit_out) const noexcept {
        if (bit) {
            output.back() |= (1 << nth_bit_out);
        }
        if (--nth_bit_out < 0) {
            nth_bit_out = CHAR_BIT - 1;
            output.push_back(0);
        }
    }

    template<bool _>
    void SubCoder<_>::encode(const int symbol, std::vector<uint8_t> & output, int & nth_bit_out) noexcept {
        assert(output.size() >= 1);
        assert(symbol >= 0 && symbol <= CoderConst::FN);
        assert(nth_bit_out >= 0 && nth_bit_out <= CHAR_BIT - 1);

        uint16_t o_lower = _lower;
        uint32_t o_range = static_cast<uint32_t>(_upper) - _lower + 1;

        _lower = o_lower + static_cast<uint16_t>(o_range * _holder.getCum(symbol) / _holder.total);
        _upper = o_lower + static_cast<uint16_t>(o_range * _holder.getCum(symbol + 1) / _holder.total - 1);

        while (condition_12() || condition_3()) {
            if (condition_12()) {
                bool bit = static_cast<bool>(_lower & mask_a);
                pushBit(bit, output, nth_bit_out);

                _lower <<= 1;
                _upper <<= 1;
                _upper |= 1;

                bit = !bit;
                while (_cnt_3) {
                    pushBit(bit, output, nth_bit_out);
                    --_cnt_3;
                }
            }

            if (condition_3()) {
                _lower <<= 1;
                _upper <<= 1;
                _upper |= 1;

                _lower ^= mask_a;
                _upper ^= mask_a;
                ++_cnt_3;
            }
        }
    };

    template<bool _>
    void SubCoder<_>::finishEncode(std::vector<uint8_t> & output, int & nth_bit_out) noexcept {
        _bit_q = _lower;
        pushBit(_bit_q[_bit_q.size() - 1], output, nth_bit_out);

        bool bit = !_bit_q[_bit_q.size() - 1];
        while (_cnt_3) {
            pushBit(bit, output, nth_bit_out);
            --_cnt_3;
        }

        for (int i = static_cast<int>(_bit_q.size() - 1 - 1); i >= 0; --i) {
            pushBit(_bit_q[i], output, nth_bit_out);
        }
        return;
    }

    template<bool TRUE_NYT_FALSE_NORMAL>
    bool SubCoder<TRUE_NYT_FALSE_NORMAL>::fetchBit(const Slice & input, size_t & nth_byte_in,
                                                   int & nth_bit_in) const {
        bool bit = 0;
        if (!TRUE_NYT_FALSE_NORMAL) { // normal forward
            if (nth_byte_in > input.size() - 1) {
                throw Exception::corruptionException("bad record length");
            }

            bit = static_cast<bool>(input.data()[nth_byte_in] & (1 << nth_bit_in));
            if (--nth_bit_in < 0) {
                ++nth_byte_in;
                nth_bit_in = CHAR_BIT - 1;
            }
        } else { // NYT backward
            if (nth_bit_in < 0) {
                throw Exception::corruptionException("bad record length");
            }

            bit = static_cast<bool>(input.data()[nth_byte_in] & (1 << nth_bit_in));
            if (--nth_bit_in < 0) {
                if (nth_byte_in >= 1) {
                    --nth_byte_in;
                    nth_bit_in = CHAR_BIT - 1;
                }
            }
        }
        return bit;
    }

    template<bool _>
    int SubCoder<_>::decode(const Slice & input, size_t & nth_byte_in, int & nth_bit_in) {
        if (_bit_q == _lower) {
            return CoderConst::decode_exit;
        }

        unsigned long anchor =
                (_bit_q.to_ulong() - _lower + 1) * _holder.total / (static_cast<uint32_t>(_upper) - _lower + 1);
        int symbol = _holder.firstGreater(static_cast<int>(anchor)) - 1;

        uint16_t o_lower = _lower;
        uint32_t o_range = static_cast<uint32_t>(_upper) - _lower + 1;

        _lower = o_lower + static_cast<uint16_t>(o_range * _holder.getCum(symbol) / _holder.total);
        _upper = o_lower + static_cast<uint16_t>(o_range * _holder.getCum(symbol + 1) / _holder.total - 1);

        while (condition_12() || condition_3()) {
            if (condition_12()) {
                _lower <<= 1;
                _upper <<= 1;
                _upper |= 1;

                _bit_q <<= 1;
                _bit_q[0] = fetchBit(input, nth_byte_in, nth_bit_in);
            }

            if (condition_3()) {
                _lower <<= 1;
                _upper <<= 1;
                _upper |= 1;

                _bit_q <<= 1;
                _bit_q[0] = fetchBit(input, nth_byte_in, nth_bit_in);

                _lower ^= mask_a;
                _upper ^= mask_a;
                _bit_q ^= mask_a;
            }
        }

        return symbol;
    }

    template<bool TRUE_NYT_FALSE_NORMAL>
    void SubCoder<TRUE_NYT_FALSE_NORMAL>::initDecode(const Slice & input, size_t & nth_byte_in,
                                                     int & nth_bit_in) {
        nth_byte_in = (!TRUE_NYT_FALSE_NORMAL) ? 0 : (input.size() - 1);
        nth_bit_in = CHAR_BIT - 1;
        for (int i = static_cast<int>(_bit_q.size() - 1); i >= 0; --i) {
            _bit_q[i] = fetchBit(input, nth_byte_in, nth_bit_in);
        }
    }

    template
    class SubCoder<true>;

    template
    class SubCoder<false>;

    std::vector<uint8_t> Coder::encode(std::vector<int> src) noexcept {
        int nth_bit_normal = CHAR_BIT - 1;
        int nth_bit_NYT = CHAR_BIT - 1;
        std::vector<uint8_t> output_normal(1);
        std::vector<uint8_t> output_NYT(1);

        for (int symbol:src) {
            if (isNew(symbol)) {
                // NYT
                _coder_normal.encode(CoderConst::NYT, output_normal, nth_bit_normal);
                // encode
                _coder_NYT.encode(symbol, output_NYT, nth_bit_NYT);
                // freq
                _coder_normal._holder.plus(symbol + 1, 1);
                _coder_NYT._holder.plus(symbol + 1, -1);
                // if no longer need NYT
                if (_coder_NYT._holder.total == 0) {
                    _coder_normal._holder.plus(CoderConst::NYT + 1, -1);
                }
            } else {
                // encode
                _coder_normal.encode(symbol, output_normal, nth_bit_normal);
                // freq
                _coder_normal._holder.plus(symbol + 1, 1);
            }
        }
        if (output_normal.size() == 1 && nth_bit_normal == CHAR_BIT - 1) { // single char src
        } else {
            _coder_normal.finishEncode(output_normal, nth_bit_normal);
        }
        _coder_NYT.finishEncode(output_NYT, nth_bit_NYT);

        output_normal.insert(output_normal.end(), output_NYT.crbegin(), output_NYT.crend());
        return output_normal;
    }

    std::vector<int> Coder::decode(std::vector<uint8_t> src) {
        int symbol;
        std::vector<int> res;
        int nth_bit_normal;
        int nth_bit_NYT;
        size_t nth_byte_normal;
        size_t nth_byte_NYT;
        Slice input(reinterpret_cast<const char *>(src.data()), src.size());

        _coder_normal.initDecode(input, nth_byte_normal, nth_bit_normal);
        _coder_NYT.initDecode(input, nth_byte_NYT, nth_bit_NYT);

        // get first symbol
        symbol = _coder_NYT.decode(input, nth_byte_NYT, nth_bit_NYT);
        res.push_back(symbol);
        if (nth_bit_NYT < 0) { // singe char src
            return res;
        }
        _coder_normal._holder.plus(symbol + 1, 1);
        _coder_NYT._holder.plus(symbol + 1, -1);

        while ((symbol = _coder_normal.decode(input, nth_byte_normal, nth_bit_normal)) != CoderConst::decode_exit) {
            if (symbol == CoderConst::NYT) {
                symbol = _coder_NYT.decode(input, nth_byte_NYT, nth_bit_NYT);
                res.push_back(symbol);

                _coder_normal._holder.plus(symbol + 1, 1);
                _coder_NYT._holder.plus(symbol + 1, -1);
                if (_coder_NYT._holder.total == 0) {
                    _coder_normal._holder.plus(CoderConst::NYT + 1, -1);
                }
            } else {
                res.push_back(symbol);
                _coder_normal._holder.plus(symbol + 1, 1);
            }
        }

        if (_coder_NYT.decode(input, nth_byte_NYT, nth_bit_NYT) != CoderConst::decode_exit ||
            nth_byte_normal - nth_byte_NYT == 1) {
            throw Exception::corruptionException("record struct damaged");
        }

        return res;
    }

    bool Coder::isNew(const int symbol) const noexcept {
        int lo = _coder_NYT._holder.getCum(symbol);
        int hi = _coder_NYT._holder.getCum(symbol + 1);
        return lo != hi;
    }
}<commit_msg>算术编码fix<commit_after>#include "coder.h"
#include "exception.h"

namespace LeviDB {
    void Holder::plus(int idx, const int val) noexcept {
        total += val;
        while (true) {
            cum_cnt[idx] += val;
            idx += (idx & (-idx));
            if (idx > CoderConst::holder_size - 1) {
                break;
            }
        }
        if (total >= UINT16_MAX / 4) {
            halve();
        }
    }

    int Holder::getCum(int idx) const noexcept {
        int sum = cum_cnt[0];
        while (idx > 0) {
            sum += cum_cnt[idx];
            idx &= (idx - 1);
        }
        return sum;
    }

    void Holder::halve() noexcept {
        int gap[CoderConst::FN + 1];
        int lo = 0;
        for (int i = 0; i <= CoderConst::FN; ++i) {
            int hi = getCum(i + 1);
            gap[i] = hi - lo;
            lo = hi;
        }

        total = 0;
        memset(cum_cnt, 0, sizeof(cum_cnt));
        for (int i = 0; i <= CoderConst::FN; ++i) {
            if (gap[i] >= 1) {
                plus(i + 1, std::max(1, gap[i] / 2));
            }
        }
    }

    int Holder::firstGreater(const int cum) const noexcept {
        int lo = 0;
        int hi = CoderConst::FN + 1;
        while (lo < hi) {
            int mi = (lo + hi) / 2;
            if (cum < getCum(mi)) {
                hi = mi;
            } else {
                lo = mi + 1;
            }
        }
        return lo;
    }

    template<bool _>
    void SubCoder<_>::pushBit(const bool bit, std::vector<uint8_t> & output, int & nth_bit_out) const noexcept {
        if (bit) {
            output.back() |= (1 << nth_bit_out);
        }
        if (--nth_bit_out < 0) {
            nth_bit_out = CHAR_BIT - 1;
            output.push_back(0);
        }
    }

    template<bool _>
    void SubCoder<_>::encode(const int symbol, std::vector<uint8_t> & output, int & nth_bit_out) noexcept {
        assert(output.size() >= 1);
        assert(symbol >= 0 && symbol <= CoderConst::FN);
        assert(nth_bit_out >= 0 && nth_bit_out <= CHAR_BIT - 1);

        uint16_t o_lower = _lower;
        uint32_t o_range = static_cast<uint32_t>(_upper) - _lower + 1;

        _lower = o_lower + static_cast<uint16_t>(o_range * _holder.getCum(symbol) / _holder.total);
        _upper = o_lower + static_cast<uint16_t>(o_range * _holder.getCum(symbol + 1) / _holder.total - 1);

        while (condition_12() || condition_3()) {
            if (condition_12()) {
                bool bit = static_cast<bool>(_lower & mask_a);
                pushBit(bit, output, nth_bit_out);

                _lower <<= 1;
                _upper <<= 1;
                _upper |= 1;

                bit = !bit;
                while (_cnt_3) {
                    pushBit(bit, output, nth_bit_out);
                    --_cnt_3;
                }
            }

            if (condition_3()) {
                _lower <<= 1;
                _upper <<= 1;
                _upper |= 1;

                _lower ^= mask_a;
                _upper ^= mask_a;
                ++_cnt_3;
            }
        }
    };

    template<bool _>
    void SubCoder<_>::finishEncode(std::vector<uint8_t> & output, int & nth_bit_out) noexcept {
        _bit_q = _lower;
        pushBit(_bit_q[_bit_q.size() - 1], output, nth_bit_out);

        bool bit = !_bit_q[_bit_q.size() - 1];
        while (_cnt_3) {
            pushBit(bit, output, nth_bit_out);
            --_cnt_3;
        }

        for (int i = static_cast<int>(_bit_q.size() - 1 - 1); i >= 0; --i) {
            pushBit(_bit_q[i], output, nth_bit_out);
        }
        return;
    }

    template<bool TRUE_NYT_FALSE_NORMAL>
    bool SubCoder<TRUE_NYT_FALSE_NORMAL>::fetchBit(const Slice & input, size_t & nth_byte_in,
                                                   int & nth_bit_in) const {
        bool bit = 0;
        if (!TRUE_NYT_FALSE_NORMAL) { // normal forward
            if (nth_byte_in > input.size() - 1) {
                throw Exception::corruptionException("bad record length");
            }

            bit = static_cast<bool>(input.data()[nth_byte_in] & (1 << nth_bit_in));
            if (--nth_bit_in < 0) {
                ++nth_byte_in;
                nth_bit_in = CHAR_BIT - 1;
            }
        } else { // NYT backward
            if (nth_bit_in < 0) {
                throw Exception::corruptionException("bad record length");
            }

            bit = static_cast<bool>(input.data()[nth_byte_in] & (1 << nth_bit_in));
            if (--nth_bit_in < 0) {
                if (nth_byte_in >= 1) {
                    --nth_byte_in;
                    nth_bit_in = CHAR_BIT - 1;
                }
            }
        }
        return bit;
    }

    template<bool _>
    int SubCoder<_>::decode(const Slice & input, size_t & nth_byte_in, int & nth_bit_in) {
        if (_bit_q == _lower) {
            return CoderConst::decode_exit;
        }

        unsigned long anchor = ((_bit_q.to_ulong() - _lower + 1) * _holder.total - 1)
                               / (static_cast<uint32_t>(_upper) - _lower + 1);
        int symbol = _holder.firstGreater(static_cast<int>(anchor)) - 1;

        uint16_t o_lower = _lower;
        uint32_t o_range = static_cast<uint32_t>(_upper) - _lower + 1;

        _lower = o_lower + static_cast<uint16_t>(o_range * _holder.getCum(symbol) / _holder.total);
        _upper = o_lower + static_cast<uint16_t>(o_range * _holder.getCum(symbol + 1) / _holder.total - 1);

        while (condition_12() || condition_3()) {
            if (condition_12()) {
                _lower <<= 1;
                _upper <<= 1;
                _upper |= 1;

                _bit_q <<= 1;
                _bit_q[0] = fetchBit(input, nth_byte_in, nth_bit_in);
            }

            if (condition_3()) {
                _lower <<= 1;
                _upper <<= 1;
                _upper |= 1;

                _bit_q <<= 1;
                _bit_q[0] = fetchBit(input, nth_byte_in, nth_bit_in);

                _lower ^= mask_a;
                _upper ^= mask_a;
                _bit_q ^= mask_a;
            }
        }

        return symbol;
    }

    template<bool TRUE_NYT_FALSE_NORMAL>
    void SubCoder<TRUE_NYT_FALSE_NORMAL>::initDecode(const Slice & input, size_t & nth_byte_in,
                                                     int & nth_bit_in) {
        nth_byte_in = (!TRUE_NYT_FALSE_NORMAL) ? 0 : (input.size() - 1);
        nth_bit_in = CHAR_BIT - 1;
        for (int i = static_cast<int>(_bit_q.size() - 1); i >= 0; --i) {
            _bit_q[i] = fetchBit(input, nth_byte_in, nth_bit_in);
        }
    }

    template
    class SubCoder<true>;

    template
    class SubCoder<false>;

    std::vector<uint8_t> Coder::encode(std::vector<int> src) noexcept {
        int nth_bit_normal = CHAR_BIT - 1;
        int nth_bit_NYT = CHAR_BIT - 1;
        std::vector<uint8_t> output_normal(1);
        std::vector<uint8_t> output_NYT(1);

        for (int symbol:src) {
            if (isNew(symbol)) {
                // NYT
                _coder_normal.encode(CoderConst::NYT, output_normal, nth_bit_normal);
                // encode
                _coder_NYT.encode(symbol, output_NYT, nth_bit_NYT);
                // freq
                _coder_normal._holder.plus(symbol + 1, 1);
                _coder_NYT._holder.plus(symbol + 1, -1);
                // if no longer need NYT
                if (_coder_NYT._holder.total == 0) {
                    _coder_normal._holder.plus(CoderConst::NYT + 1, -1);
                }
            } else {
                // encode
                _coder_normal.encode(symbol, output_normal, nth_bit_normal);
                // freq
                _coder_normal._holder.plus(symbol + 1, 1);
            }
        }
        if (output_normal.size() == 1 && nth_bit_normal == CHAR_BIT - 1) { // single char src
        } else {
            _coder_normal.finishEncode(output_normal, nth_bit_normal);
        }
        _coder_NYT.finishEncode(output_NYT, nth_bit_NYT);

        output_normal.insert(output_normal.end(), output_NYT.crbegin(), output_NYT.crend());
        return output_normal;
    }

    std::vector<int> Coder::decode(std::vector<uint8_t> src) {
        int symbol;
        std::vector<int> res;
        int nth_bit_normal;
        int nth_bit_NYT;
        size_t nth_byte_normal;
        size_t nth_byte_NYT;
        Slice input(reinterpret_cast<const char *>(src.data()), src.size());

        _coder_normal.initDecode(input, nth_byte_normal, nth_bit_normal);
        _coder_NYT.initDecode(input, nth_byte_NYT, nth_bit_NYT);

        // get first symbol
        symbol = _coder_NYT.decode(input, nth_byte_NYT, nth_bit_NYT);
        res.push_back(symbol);
        if (nth_bit_NYT < 0) { // singe char src
            return res;
        }
        _coder_normal._holder.plus(symbol + 1, 1);
        _coder_NYT._holder.plus(symbol + 1, -1);

        while ((symbol = _coder_normal.decode(input, nth_byte_normal, nth_bit_normal)) != CoderConst::decode_exit) {
            if (symbol == CoderConst::NYT) {
                symbol = _coder_NYT.decode(input, nth_byte_NYT, nth_bit_NYT);
                res.push_back(symbol);

                _coder_normal._holder.plus(symbol + 1, 1);
                _coder_NYT._holder.plus(symbol + 1, -1);
                if (_coder_NYT._holder.total == 0) {
                    _coder_normal._holder.plus(CoderConst::NYT + 1, -1);
                }
            } else {
                res.push_back(symbol);
                _coder_normal._holder.plus(symbol + 1, 1);
            }
        }

        if (_coder_NYT.decode(input, nth_byte_NYT, nth_bit_NYT) != CoderConst::decode_exit ||
            nth_byte_normal - nth_byte_NYT == 1) {
            throw Exception::corruptionException("record struct damaged");
        }

        return res;
    }

    bool Coder::isNew(const int symbol) const noexcept {
        int lo = _coder_NYT._holder.getCum(symbol);
        int hi = _coder_NYT._holder.getCum(symbol + 1);
        return lo != hi;
    }
}<|endoftext|>


<commit_before>#include <iostream>
#include "Emulator.h"
#include "Debugger.h"

enum MainFlags {
	F_DEFAULT,
	F_ROMINFO,
	F_DEBUG
};

int main(int argc, char **argv) {
	std::cout << "mfemu v." << VERSION << " rev." << COMMIT << std::endl << std::endl;

	std::string romFile("test.gb");
	MainFlags flags(F_DEFAULT);

	for (int i = 1; i < argc; i += 1) {
		if (argv[i][0] == '-') {
			switch (argv[i][1]) {
			case 'v':
				return 0; // we already printed the version
			case 'i':
				// only print ROM information and exit
				flags = F_ROMINFO;
				break;
			case 'd':
				flags = F_DEBUG;
				break;
			default:
				std::cout << "Usage: " << argv[0] << " [-hivd] [file.gb]" << std::endl;
				return 0;
			}
		} else {
			romFile = std::string(argv[i]);
		}
	}

	if (flags == F_ROMINFO) {
		ROM rom = ROM::FromFile(romFile);
		rom.debugPrintData();
		return 0;
	}

	if (flags == F_DEBUG) {
		Emulator emulator(romFile, false);
		Debugger debugger(&emulator, Debug::DBG_NOGRAPHICS);
		debugger.Run();
	} else {
		// Create CPU and load ROM into it
		Emulator emulator(romFile);
		std::cout << "Starting emulation..." << std::endl;
		emulator.Run();
	}
}
<commit_msg>Fix the last warning in MSVC<commit_after>#include <iostream>
#include "Emulator.h"
#include "Debugger.h"

enum MainFlags {
	F_DEFAULT,
	F_ROMINFO,
	F_DEBUG
};

int main(int argc, char **argv) {
	std::cout << "mfemu v." << VERSION << " rev." << COMMIT << std::endl << std::endl;

	std::string romFile("test.gb");
	MainFlags flags(F_DEFAULT);

	for (int i = 1; i < argc; i += 1) {
		if (argv[i][0] == '-') {
			switch (argv[i][1]) {
			case 'v':
				return 0; // we already printed the version
			case 'i':
				// only print ROM information and exit
				flags = F_ROMINFO;
				break;
			case 'd':
				flags = F_DEBUG;
				break;
			default:
				std::cout << "Usage: " << argv[0] << " [-hivd] [file.gb]" << std::endl;
				return 0;
			}
		} else {
			romFile = std::string(argv[i]);
		}
	}

	if (flags == F_ROMINFO) {
		ROM rom = ROM::FromFile(romFile);
		rom.debugPrintData();
		return 0;
	}

	if (flags == F_DEBUG) {
		Emulator emulator(romFile, false);
		Debugger debugger(&emulator, Debug::DBG_NOGRAPHICS);
		debugger.Run();
	} else {
		// Create CPU and load ROM into it
		Emulator emulator(romFile);
		std::cout << "Starting emulation..." << std::endl;
		emulator.Run();
	}

	return 0;
}
<|endoftext|>