<commit_before>#include <node.h>
#include <node_buffer.h>
#include "gn_encoder.h"
#include "gn_playlist.h"
#include "gn_playlist_item.h"

using namespace v8;

GNEncoder::GNEncoder() {};
GNEncoder::~GNEncoder() {
    groove_encoder_destroy(encoder);
};

Persistent<Function> GNEncoder::constructor;

template <typename target_t, typename func_t>
static void AddGetter(target_t tpl, const char* name, func_t fn) {
    tpl->PrototypeTemplate()->SetAccessor(String::NewSymbol(name), fn);
}

template <typename target_t, typename func_t>
static void AddMethod(target_t tpl, const char* name, func_t fn) {
    tpl->PrototypeTemplate()->Set(String::NewSymbol(name),
            FunctionTemplate::New(fn)->GetFunction());
}

void GNEncoder::Init() {
    // Prepare constructor template
    Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
    tpl->SetClassName(String::NewSymbol("GrooveEncoder"));
    tpl->InstanceTemplate()->SetInternalFieldCount(1);
    // Fields
    AddGetter(tpl, "id", GetId);
    AddGetter(tpl, "playlist", GetPlaylist);
    // Methods
    AddMethod(tpl, "attach", Attach);
    AddMethod(tpl, "detach", Detach);
    AddMethod(tpl, "getBuffer", GetBuffer);

    constructor = Persistent<Function>::New(tpl->GetFunction());
}

Handle<Value> GNEncoder::New(const Arguments& args) {
    HandleScope scope;

    GNEncoder *obj = new GNEncoder();
    obj->Wrap(args.This());

    return scope.Close(args.This());
}

Handle<Value> GNEncoder::NewInstance(GrooveEncoder *encoder) {
    HandleScope scope;

    Local<Object> instance = constructor->NewInstance();

    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(instance);
    gn_encoder->encoder = encoder;

    return scope.Close(instance);
}

Handle<Value> GNEncoder::GetId(Local<String> property, const AccessorInfo &info) {
    HandleScope scope;
    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(info.This());
    char buf[64];
    snprintf(buf, sizeof(buf), "%p", gn_encoder->encoder);
    return scope.Close(String::New(buf));
}

Handle<Value> GNEncoder::GetPlaylist(Local<String> property,
        const AccessorInfo &info)
{
    HandleScope scope;
    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(info.This());
    GroovePlaylist *playlist = gn_encoder->encoder->playlist;
    if (playlist) {
        return scope.Close(GNPlaylist::NewInstance(playlist));
    } else {
        return scope.Close(Null());
    }
}

struct AttachReq {
    uv_work_t req;
    Persistent<Function> callback;
    GrooveEncoder *encoder;
    GroovePlaylist *playlist;
    int errcode;
    Persistent<Object> instance;
    String::Utf8Value *format_short_name;
    String::Utf8Value *codec_short_name;
    String::Utf8Value *filename;
    String::Utf8Value *mime_type;
};

static void AttachAsync(uv_work_t *req) {
    AttachReq *r = reinterpret_cast<AttachReq *>(req->data);

    r->encoder->format_short_name = r->format_short_name ? **r->format_short_name : NULL;
    r->encoder->codec_short_name = r->codec_short_name ? **r->codec_short_name : NULL;
    r->encoder->filename = r->filename ? **r->filename : NULL;
    r->encoder->mime_type = r->mime_type ? **r->mime_type : NULL;

    r->errcode = groove_encoder_attach(r->encoder, r->playlist);
    if (r->format_short_name) {
        delete r->format_short_name;
        r->format_short_name = NULL;
    }
    if (r->codec_short_name) {
        delete r->codec_short_name;
        r->codec_short_name = NULL;
    }
    if (r->filename) {
        delete r->filename;
        r->filename = NULL;
    }
    if (r->mime_type) {
        delete r->mime_type;
        r->mime_type = NULL;
    }
}

static void AttachAfter(uv_work_t *req) {
    HandleScope scope;
    AttachReq *r = reinterpret_cast<AttachReq *>(req->data);

    const unsigned argc = 1;
    Handle<Value> argv[argc];
    if (r->errcode < 0) {
        argv[0] = Exception::Error(String::New("encoder attach failed"));
    } else {
        argv[0] = Null();

        Local<Object> actualAudioFormat = Object::New();
        actualAudioFormat->Set(String::NewSymbol("sampleRate"),
                Number::New(r->encoder->actual_audio_format.sample_rate));
        actualAudioFormat->Set(String::NewSymbol("channelLayout"),
                Number::New(r->encoder->actual_audio_format.channel_layout));
        actualAudioFormat->Set(String::NewSymbol("sampleFormat"),
                Number::New(r->encoder->actual_audio_format.sample_fmt));

        r->instance->Set(String::NewSymbol("actualAudioFormat"), actualAudioFormat);
    }

    TryCatch try_catch;
    r->callback->Call(Context::GetCurrent()->Global(), argc, argv);

    delete r;

    if (try_catch.HasCaught()) {
        node::FatalException(try_catch);
    }
}

Handle<Value> GNEncoder::Create(const Arguments& args) {
    HandleScope scope;

    GrooveEncoder *encoder = groove_encoder_create();
    Handle<Object> instance = NewInstance(encoder)->ToObject();

    // set properties on the instance with default values from
    // GrooveEncoder struct
    Local<Object> targetAudioFormat = Object::New();
    targetAudioFormat->Set(String::NewSymbol("sampleRate"),
            Number::New(encoder->target_audio_format.sample_rate));
    targetAudioFormat->Set(String::NewSymbol("channelLayout"),
            Number::New(encoder->target_audio_format.channel_layout));
    targetAudioFormat->Set(String::NewSymbol("sampleFormat"),
            Number::New(encoder->target_audio_format.sample_fmt));

    instance->Set(String::NewSymbol("bitRate"), Number::New(encoder->bit_rate));
    instance->Set(String::NewSymbol("actualAudioFormat"), Null());
    instance->Set(String::NewSymbol("targetAudioFormat"), targetAudioFormat);
    instance->Set(String::NewSymbol("formatShortName"), Null());
    instance->Set(String::NewSymbol("codecShortName"), Null());
    instance->Set(String::NewSymbol("filename"), Null());
    instance->Set(String::NewSymbol("mimeType"), Null());

    return scope.Close(instance);
}

Handle<Value> GNEncoder::Attach(const Arguments& args) {
    HandleScope scope;

    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(args.This());

    if (args.Length() < 1 || !args[0]->IsObject()) {
        ThrowException(Exception::TypeError(String::New("Expected object arg[0]")));
        return scope.Close(Undefined());
    }
    if (args.Length() < 2 || !args[1]->IsFunction()) {
        ThrowException(Exception::TypeError(String::New("Expected function arg[1]")));
        return scope.Close(Undefined());
    }

    Local<Object> instance = args.This();
    Local<Value> targetAudioFormatValue = instance->Get(String::NewSymbol("targetAudioFormat"));
    if (!targetAudioFormatValue->IsObject()) {
        ThrowException(Exception::TypeError(String::New("Expected targetAudioFormat to be an object")));
        return scope.Close(Undefined());
    }

    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args[0]->ToObject());

    AttachReq *request = new AttachReq;

    request->req.data = request;
    request->callback = Persistent<Function>::New(Local<Function>::Cast(args[1]));
    request->instance = Persistent<Object>::New(args.This());
    request->playlist = gn_playlist->playlist;
    GrooveEncoder *encoder = gn_encoder->encoder;
    request->encoder = encoder;

    // copy the properties from our instance to the encoder
    Local<Value> formatShortName = instance->Get(String::NewSymbol("formatShortName"));
    if (formatShortName->IsNull() || formatShortName->IsUndefined()) {
        request->format_short_name = NULL;
    } else {
        request->format_short_name = new String::Utf8Value(formatShortName->ToString());
    }
    Local<Value> codecShortName = instance->Get(String::NewSymbol("codecShortName"));
    if (codecShortName->IsNull() || codecShortName->IsUndefined()) {
        request->codec_short_name = NULL;
    } else {
        request->codec_short_name = new String::Utf8Value(codecShortName->ToString());
    }
    Local<Value> filenameStr = instance->Get(String::NewSymbol("filename"));
    if (filenameStr->IsNull() || filenameStr->IsUndefined()) {
        request->filename = NULL;
    } else {
        request->filename = new String::Utf8Value(filenameStr->ToString());
    }
    Local<Value> mimeType = instance->Get(String::NewSymbol("mimeType"));
    if (mimeType->IsNull() || mimeType->IsUndefined()) {
        request->mime_type = NULL;
    } else {
        request->mime_type = new String::Utf8Value(mimeType->ToString());
    }

    Local<Object> targetAudioFormat = targetAudioFormatValue->ToObject();
    Local<Value> sampleRate = targetAudioFormat->Get(String::NewSymbol("sampleRate"));
    double sample_rate = sampleRate->NumberValue();
    double channel_layout = targetAudioFormat->Get(String::NewSymbol("channelLayout"))->NumberValue();
    double sample_fmt = targetAudioFormat->Get(String::NewSymbol("sampleFormat"))->NumberValue();
    encoder->target_audio_format.sample_rate = (int)sample_rate;
    encoder->target_audio_format.channel_layout = (int)channel_layout;
    encoder->target_audio_format.sample_fmt = (enum GrooveSampleFormat)(int)sample_fmt;

    double bit_rate = instance->Get(String::NewSymbol("bitRate"))->NumberValue();
    encoder->bit_rate = (int)bit_rate;

    uv_queue_work(uv_default_loop(), &request->req, AttachAsync,
            (uv_after_work_cb)AttachAfter);

    return scope.Close(Undefined());
}

struct DetachReq {
    uv_work_t req;
    GrooveEncoder *encoder;
    Persistent<Function> callback;
    int errcode;
};

static void DetachAsync(uv_work_t *req) {
    DetachReq *r = reinterpret_cast<DetachReq *>(req->data);
    r->errcode = groove_encoder_detach(r->encoder);
}

static void DetachAfter(uv_work_t *req) {
    HandleScope scope;
    DetachReq *r = reinterpret_cast<DetachReq *>(req->data);

    const unsigned argc = 1;
    Handle<Value> argv[argc];
    if (r->errcode < 0) {
        argv[0] = Exception::Error(String::New("encoder detach failed"));
    } else {
        argv[0] = Null();
    }
    TryCatch try_catch;
    r->callback->Call(Context::GetCurrent()->Global(), argc, argv);

    delete r;

    if (try_catch.HasCaught()) {
        node::FatalException(try_catch);
    }
}

Handle<Value> GNEncoder::Detach(const Arguments& args) {
    HandleScope scope;
    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(args.This());

    if (args.Length() < 1 || !args[0]->IsFunction()) {
        ThrowException(Exception::TypeError(String::New("Expected function arg[0]")));
        return scope.Close(Undefined());
    }

    GrooveEncoder *encoder = gn_encoder->encoder;

    if (!encoder->playlist) {
        ThrowException(Exception::Error(String::New("detach: not attached")));
        return scope.Close(Undefined());
    }

    DetachReq *request = new DetachReq;

    request->req.data = request;
    request->callback = Persistent<Function>::New(Local<Function>::Cast(args[0]));
    request->encoder = encoder;

    uv_queue_work(uv_default_loop(), &request->req, DetachAsync,
            (uv_after_work_cb)DetachAfter);

    return scope.Close(Undefined());
}

static void buffer_free(char *data, void *hint) {
    GrooveBuffer *buffer = reinterpret_cast<GrooveBuffer*>(hint);
    groove_buffer_unref(buffer);
}

Handle<Value> GNEncoder::GetBuffer(const Arguments& args) {
    HandleScope scope;
    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(args.This());
    GrooveEncoder *encoder = gn_encoder->encoder;

    GrooveBuffer *buffer;
    switch (groove_encoder_get_buffer(encoder, &buffer, 0)) {
        case GROOVE_BUFFER_YES: {
            Local<Object> object = Object::New();

            Handle<Object> bufferObject = node::Buffer::New(
                    reinterpret_cast<char*>(buffer->data[0]), buffer->size,
                    buffer_free, buffer)->handle_;
            object->Set(String::NewSymbol("buffer"), bufferObject);

            object->Set(String::NewSymbol("item"), GNPlaylistItem::NewInstance(buffer->item));
            object->Set(String::NewSymbol("pos"), Number::New(buffer->pos));
            return scope.Close(object);
        }
        case GROOVE_BUFFER_END: {
            Local<Object> object = Object::New();
            object->Set(String::NewSymbol("buffer"), Null());
            object->Set(String::NewSymbol("item"), Null());
            object->Set(String::NewSymbol("pos"), Null());
            return scope.Close(object);
        }
        default:
            return scope.Close(Null());
    }
}
<commit_msg>encoder: set playlist item to null when appropriate<commit_after>#include <node.h>
#include <node_buffer.h>
#include "gn_encoder.h"
#include "gn_playlist.h"
#include "gn_playlist_item.h"

using namespace v8;

GNEncoder::GNEncoder() {};
GNEncoder::~GNEncoder() {
    groove_encoder_destroy(encoder);
};

Persistent<Function> GNEncoder::constructor;

template <typename target_t, typename func_t>
static void AddGetter(target_t tpl, const char* name, func_t fn) {
    tpl->PrototypeTemplate()->SetAccessor(String::NewSymbol(name), fn);
}

template <typename target_t, typename func_t>
static void AddMethod(target_t tpl, const char* name, func_t fn) {
    tpl->PrototypeTemplate()->Set(String::NewSymbol(name),
            FunctionTemplate::New(fn)->GetFunction());
}

void GNEncoder::Init() {
    // Prepare constructor template
    Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
    tpl->SetClassName(String::NewSymbol("GrooveEncoder"));
    tpl->InstanceTemplate()->SetInternalFieldCount(1);
    // Fields
    AddGetter(tpl, "id", GetId);
    AddGetter(tpl, "playlist", GetPlaylist);
    // Methods
    AddMethod(tpl, "attach", Attach);
    AddMethod(tpl, "detach", Detach);
    AddMethod(tpl, "getBuffer", GetBuffer);

    constructor = Persistent<Function>::New(tpl->GetFunction());
}

Handle<Value> GNEncoder::New(const Arguments& args) {
    HandleScope scope;

    GNEncoder *obj = new GNEncoder();
    obj->Wrap(args.This());

    return scope.Close(args.This());
}

Handle<Value> GNEncoder::NewInstance(GrooveEncoder *encoder) {
    HandleScope scope;

    Local<Object> instance = constructor->NewInstance();

    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(instance);
    gn_encoder->encoder = encoder;

    return scope.Close(instance);
}

Handle<Value> GNEncoder::GetId(Local<String> property, const AccessorInfo &info) {
    HandleScope scope;
    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(info.This());
    char buf[64];
    snprintf(buf, sizeof(buf), "%p", gn_encoder->encoder);
    return scope.Close(String::New(buf));
}

Handle<Value> GNEncoder::GetPlaylist(Local<String> property,
        const AccessorInfo &info)
{
    HandleScope scope;
    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(info.This());
    GroovePlaylist *playlist = gn_encoder->encoder->playlist;
    if (playlist) {
        return scope.Close(GNPlaylist::NewInstance(playlist));
    } else {
        return scope.Close(Null());
    }
}

struct AttachReq {
    uv_work_t req;
    Persistent<Function> callback;
    GrooveEncoder *encoder;
    GroovePlaylist *playlist;
    int errcode;
    Persistent<Object> instance;
    String::Utf8Value *format_short_name;
    String::Utf8Value *codec_short_name;
    String::Utf8Value *filename;
    String::Utf8Value *mime_type;
};

static void AttachAsync(uv_work_t *req) {
    AttachReq *r = reinterpret_cast<AttachReq *>(req->data);

    r->encoder->format_short_name = r->format_short_name ? **r->format_short_name : NULL;
    r->encoder->codec_short_name = r->codec_short_name ? **r->codec_short_name : NULL;
    r->encoder->filename = r->filename ? **r->filename : NULL;
    r->encoder->mime_type = r->mime_type ? **r->mime_type : NULL;

    r->errcode = groove_encoder_attach(r->encoder, r->playlist);
    if (r->format_short_name) {
        delete r->format_short_name;
        r->format_short_name = NULL;
    }
    if (r->codec_short_name) {
        delete r->codec_short_name;
        r->codec_short_name = NULL;
    }
    if (r->filename) {
        delete r->filename;
        r->filename = NULL;
    }
    if (r->mime_type) {
        delete r->mime_type;
        r->mime_type = NULL;
    }
}

static void AttachAfter(uv_work_t *req) {
    HandleScope scope;
    AttachReq *r = reinterpret_cast<AttachReq *>(req->data);

    const unsigned argc = 1;
    Handle<Value> argv[argc];
    if (r->errcode < 0) {
        argv[0] = Exception::Error(String::New("encoder attach failed"));
    } else {
        argv[0] = Null();

        Local<Object> actualAudioFormat = Object::New();
        actualAudioFormat->Set(String::NewSymbol("sampleRate"),
                Number::New(r->encoder->actual_audio_format.sample_rate));
        actualAudioFormat->Set(String::NewSymbol("channelLayout"),
                Number::New(r->encoder->actual_audio_format.channel_layout));
        actualAudioFormat->Set(String::NewSymbol("sampleFormat"),
                Number::New(r->encoder->actual_audio_format.sample_fmt));

        r->instance->Set(String::NewSymbol("actualAudioFormat"), actualAudioFormat);
    }

    TryCatch try_catch;
    r->callback->Call(Context::GetCurrent()->Global(), argc, argv);

    delete r;

    if (try_catch.HasCaught()) {
        node::FatalException(try_catch);
    }
}

Handle<Value> GNEncoder::Create(const Arguments& args) {
    HandleScope scope;

    GrooveEncoder *encoder = groove_encoder_create();
    Handle<Object> instance = NewInstance(encoder)->ToObject();

    // set properties on the instance with default values from
    // GrooveEncoder struct
    Local<Object> targetAudioFormat = Object::New();
    targetAudioFormat->Set(String::NewSymbol("sampleRate"),
            Number::New(encoder->target_audio_format.sample_rate));
    targetAudioFormat->Set(String::NewSymbol("channelLayout"),
            Number::New(encoder->target_audio_format.channel_layout));
    targetAudioFormat->Set(String::NewSymbol("sampleFormat"),
            Number::New(encoder->target_audio_format.sample_fmt));

    instance->Set(String::NewSymbol("bitRate"), Number::New(encoder->bit_rate));
    instance->Set(String::NewSymbol("actualAudioFormat"), Null());
    instance->Set(String::NewSymbol("targetAudioFormat"), targetAudioFormat);
    instance->Set(String::NewSymbol("formatShortName"), Null());
    instance->Set(String::NewSymbol("codecShortName"), Null());
    instance->Set(String::NewSymbol("filename"), Null());
    instance->Set(String::NewSymbol("mimeType"), Null());

    return scope.Close(instance);
}

Handle<Value> GNEncoder::Attach(const Arguments& args) {
    HandleScope scope;

    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(args.This());

    if (args.Length() < 1 || !args[0]->IsObject()) {
        ThrowException(Exception::TypeError(String::New("Expected object arg[0]")));
        return scope.Close(Undefined());
    }
    if (args.Length() < 2 || !args[1]->IsFunction()) {
        ThrowException(Exception::TypeError(String::New("Expected function arg[1]")));
        return scope.Close(Undefined());
    }

    Local<Object> instance = args.This();
    Local<Value> targetAudioFormatValue = instance->Get(String::NewSymbol("targetAudioFormat"));
    if (!targetAudioFormatValue->IsObject()) {
        ThrowException(Exception::TypeError(String::New("Expected targetAudioFormat to be an object")));
        return scope.Close(Undefined());
    }

    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args[0]->ToObject());

    AttachReq *request = new AttachReq;

    request->req.data = request;
    request->callback = Persistent<Function>::New(Local<Function>::Cast(args[1]));
    request->instance = Persistent<Object>::New(args.This());
    request->playlist = gn_playlist->playlist;
    GrooveEncoder *encoder = gn_encoder->encoder;
    request->encoder = encoder;

    // copy the properties from our instance to the encoder
    Local<Value> formatShortName = instance->Get(String::NewSymbol("formatShortName"));
    if (formatShortName->IsNull() || formatShortName->IsUndefined()) {
        request->format_short_name = NULL;
    } else {
        request->format_short_name = new String::Utf8Value(formatShortName->ToString());
    }
    Local<Value> codecShortName = instance->Get(String::NewSymbol("codecShortName"));
    if (codecShortName->IsNull() || codecShortName->IsUndefined()) {
        request->codec_short_name = NULL;
    } else {
        request->codec_short_name = new String::Utf8Value(codecShortName->ToString());
    }
    Local<Value> filenameStr = instance->Get(String::NewSymbol("filename"));
    if (filenameStr->IsNull() || filenameStr->IsUndefined()) {
        request->filename = NULL;
    } else {
        request->filename = new String::Utf8Value(filenameStr->ToString());
    }
    Local<Value> mimeType = instance->Get(String::NewSymbol("mimeType"));
    if (mimeType->IsNull() || mimeType->IsUndefined()) {
        request->mime_type = NULL;
    } else {
        request->mime_type = new String::Utf8Value(mimeType->ToString());
    }

    Local<Object> targetAudioFormat = targetAudioFormatValue->ToObject();
    Local<Value> sampleRate = targetAudioFormat->Get(String::NewSymbol("sampleRate"));
    double sample_rate = sampleRate->NumberValue();
    double channel_layout = targetAudioFormat->Get(String::NewSymbol("channelLayout"))->NumberValue();
    double sample_fmt = targetAudioFormat->Get(String::NewSymbol("sampleFormat"))->NumberValue();
    encoder->target_audio_format.sample_rate = (int)sample_rate;
    encoder->target_audio_format.channel_layout = (int)channel_layout;
    encoder->target_audio_format.sample_fmt = (enum GrooveSampleFormat)(int)sample_fmt;

    double bit_rate = instance->Get(String::NewSymbol("bitRate"))->NumberValue();
    encoder->bit_rate = (int)bit_rate;

    uv_queue_work(uv_default_loop(), &request->req, AttachAsync,
            (uv_after_work_cb)AttachAfter);

    return scope.Close(Undefined());
}

struct DetachReq {
    uv_work_t req;
    GrooveEncoder *encoder;
    Persistent<Function> callback;
    int errcode;
};

static void DetachAsync(uv_work_t *req) {
    DetachReq *r = reinterpret_cast<DetachReq *>(req->data);
    r->errcode = groove_encoder_detach(r->encoder);
}

static void DetachAfter(uv_work_t *req) {
    HandleScope scope;
    DetachReq *r = reinterpret_cast<DetachReq *>(req->data);

    const unsigned argc = 1;
    Handle<Value> argv[argc];
    if (r->errcode < 0) {
        argv[0] = Exception::Error(String::New("encoder detach failed"));
    } else {
        argv[0] = Null();
    }
    TryCatch try_catch;
    r->callback->Call(Context::GetCurrent()->Global(), argc, argv);

    delete r;

    if (try_catch.HasCaught()) {
        node::FatalException(try_catch);
    }
}

Handle<Value> GNEncoder::Detach(const Arguments& args) {
    HandleScope scope;
    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(args.This());

    if (args.Length() < 1 || !args[0]->IsFunction()) {
        ThrowException(Exception::TypeError(String::New("Expected function arg[0]")));
        return scope.Close(Undefined());
    }

    GrooveEncoder *encoder = gn_encoder->encoder;

    if (!encoder->playlist) {
        ThrowException(Exception::Error(String::New("detach: not attached")));
        return scope.Close(Undefined());
    }

    DetachReq *request = new DetachReq;

    request->req.data = request;
    request->callback = Persistent<Function>::New(Local<Function>::Cast(args[0]));
    request->encoder = encoder;

    uv_queue_work(uv_default_loop(), &request->req, DetachAsync,
            (uv_after_work_cb)DetachAfter);

    return scope.Close(Undefined());
}

static void buffer_free(char *data, void *hint) {
    GrooveBuffer *buffer = reinterpret_cast<GrooveBuffer*>(hint);
    groove_buffer_unref(buffer);
}

Handle<Value> GNEncoder::GetBuffer(const Arguments& args) {
    HandleScope scope;
    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(args.This());
    GrooveEncoder *encoder = gn_encoder->encoder;

    GrooveBuffer *buffer;
    switch (groove_encoder_get_buffer(encoder, &buffer, 0)) {
        case GROOVE_BUFFER_YES: {
            Local<Object> object = Object::New();

            Handle<Object> bufferObject = node::Buffer::New(
                    reinterpret_cast<char*>(buffer->data[0]), buffer->size,
                    buffer_free, buffer)->handle_;
            object->Set(String::NewSymbol("buffer"), bufferObject);

            if (buffer->item) {
                object->Set(String::NewSymbol("item"), GNPlaylistItem::NewInstance(buffer->item));
            } else {
                object->Set(String::NewSymbol("item"), Null());
            }
            object->Set(String::NewSymbol("pos"), Number::New(buffer->pos));
            return scope.Close(object);
        }
        case GROOVE_BUFFER_END: {
            Local<Object> object = Object::New();
            object->Set(String::NewSymbol("buffer"), Null());
            object->Set(String::NewSymbol("item"), Null());
            object->Set(String::NewSymbol("pos"), Null());
            return scope.Close(object);
        }
        default:
            return scope.Close(Null());
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <algorithm>
#include <cstring>
#include <fstream>
#include <iostream>
#include <thread>

#include <math/vec3.h>

#include <image/ImageOps.h>

#include <imageio/ImageDecoder.h>
#include <imageio/ImageEncoder.h>

#include <utils/JobSystem.h>
#include <utils/Path.h>

#include <getopt/getopt.h>

using namespace math;
using namespace image;
using namespace utils;

static ImageEncoder::Format g_format = ImageEncoder::Format::PNG_LINEAR;
static bool g_formatSpecified = false;
static bool g_linearOutput = false;
static std::string g_compression;

static Path g_roughnessMap;
static float g_roughness = 0.0;

static void printUsage(const char* name) {
    std::string execName(Path(name).getName());
    std::string usage(
            "ROUGHNESSPREFILTER generates pre-filtered roughness maps from normal maps\n"
            "to help mitigate specular aliasing.\n"
            "Usage:\n"
            "    ROUGHNESSPREFILTER [options] <normal-map> <output-roughness-map>\n"
            "\n"
            "Output note:\n"
            "    One file will be generated per mip-level. The size of the first level will be\n"
            "    the greater of the input normal map and input roughness map.\n"
            "\n"
            "Supported input formats:\n"
            "    PNG, 8 and 16 bits\n"
            "    Radiance (.hdr)\n"
            "    Photoshop (.psd), 16 and 32 bits\n"
            "    OpenEXR (.exr)\n"
            "\n"
            "Options:\n"
            "   --help, -h\n"
            "       print this message\n\n"
            "   --license\n"
            "       Print copyright and license information\n\n"
            "   --roughness=[0..1], -r [0..1]\n"
            "       desired constant roughness, ignored if --roughness-map is specified\n\n"
            "   --roughness-map=<input-roughness-map>, -m <input-roughness-map>\n"
            "       input roughness map\n\n"
            "   --format=[exr|hdr|psd|png|dds], -f [exr|hdr|psd|png|dds]\n"
            "       specify output file format, inferred from file name if omitted\n\n"
            "   --compression=COMPRESSION, -c COMPRESSION\n"
            "       format specific compression:\n"
            "           PNG: Ignored\n"
            "           Radiance: Ignored\n"
            "           Photoshop: 16 (default), 32\n"
            "           OpenEXR: RAW, RLE, ZIPS, ZIP, PIZ (default)\n"
            "           DDS: 8, 16 (default), 32\n\n"
            "   --linear, -l\n"
            "       force linear output when the PNG format is selected\n\n"
    );

    const std::string from("ROUGHNESSPREFILTER");
    for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
        usage.replace(pos, from.length(), execName);
    }
    printf("%s", usage.c_str());
}

static void license() {
    std::cout <<
    #include "licenses/licenses.inc"
    ;
}

static int handleArguments(int argc, char* argv[]) {
    static constexpr const char* OPTSTR = "hr:c:f:m:l";
    static const struct option OPTIONS[] = {
            { "help",                 no_argument, nullptr, 'h' },
            { "license",              no_argument, nullptr, 's' },
            { "format",         required_argument, nullptr, 'f' },
            { "compression",    required_argument, nullptr, 'c' },
            { "roughness",      required_argument, nullptr, 'r' },
            { "roughness-map",  required_argument, nullptr, 'm' },
            { "linear",               no_argument, nullptr, 'l' },
            { nullptr, 0, nullptr, 0 }  // termination of the option list
    };

    int opt;
    int optionIndex = 0;

    while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &optionIndex)) >= 0) {
        std::string arg(optarg ? optarg : "");
        switch (opt) {
            default:
            case 'h':
                printUsage(argv[0]);
                exit(0);
                // break;
            case 's':
                license();
                exit(0);
                // break;
            case 'f':
                if (arg == "png") {
                    g_format = ImageEncoder::Format::PNG;
                    g_formatSpecified = true;
                }
                if (arg == "hdr") {
                    g_format = ImageEncoder::Format::HDR;
                    g_formatSpecified = true;
                }
                if (arg == "exr") {
                    g_format = ImageEncoder::Format::EXR;
                    g_formatSpecified = true;
                }
                if (arg == "psd") {
                    g_format = ImageEncoder::Format::PSD;
                    g_formatSpecified = true;
                }
                if (arg == "dds") {
                    g_format = ImageEncoder::Format::DDS_LINEAR;
                    g_formatSpecified = true;
                }
                break;
            case 'c':
                g_compression = arg;
                break;
            case 'r':
                try {
                    g_roughness = std::stof(arg);
                } catch (std::invalid_argument& e) {
                    std::cerr << "The specified roughness must be [0..1]: " << arg << std::endl;
                    exit(1);
                } catch (std::out_of_range& e) {
                    std::cerr << "The specified roughness must be [0..1]: " << arg << std::endl;
                    exit(1);
                }
                break;
            case 'm':
                g_roughnessMap = arg;
                break;
            case 'l':
                g_linearOutput = true;
                break;
        }
    }

    return optind;
}

inline bool isPOT(size_t x) {
    return !(x & (x - 1));
}

float solveVMF(const float2& pos, const size_t sampleCount, const float roughness,
        const LinearImage& normal) {

    float3 averageNormal(0.0f);
    float2 topLeft(-float(sampleCount) / 2.0f + 0.5f);

    for (size_t y = 0; y < sampleCount; y++) {
        for (size_t x = 0; x < sampleCount; x++) {
            float2 offset(topLeft + float2(x, y));
            float2 samplePos(floor(pos + offset) + 0.5f);
            float3 sampleNormal = *normal.get<float3>(size_t(samplePos.x), size_t(samplePos.y));
            sampleNormal = sampleNormal * 2.0f - 1.0f;

            averageNormal += normalize(sampleNormal);
        }
    }

    averageNormal /= (sampleCount * sampleCount);

    float r = length(averageNormal);
    float kappa = 10000.0f;

    if (r < 1.0f) {
        kappa = (3.0f * r - r * r * r) / (1.0f - r * r);
    }

    // See: Karis, 2018, "Normal map filtering using vMF (part 3)"
    // The original formulation in Neubelt et al. 2013,
    // "Crafting a Next-Gen Material Pipeline for The Order: 1886" contains an error
    // and defines alpha' = sqrt(alpha^2 + 1 / kappa)
    return std::sqrt(roughness * roughness + (2.0f / kappa));
}

void prefilter(const LinearImage& normal, const size_t mipLevel, LinearImage& output) {
    const size_t width = output.getWidth();
    const size_t height = output.getHeight();
    const size_t sampleCount = 1u << mipLevel;

    for (size_t y = 0; y < height; y++) {
        auto outputRow = output.get<float3>(0, y);
        for (size_t x = 0; x < width; x++, outputRow++) {
            const float2 uv = (float2(x, y) + 0.5f) / float2(width, height);
            const float2 pos = uv * normal.getWidth();
            const float roughness = g_roughness;
            *outputRow = float3(solveVMF(pos, sampleCount, roughness, normal));
        }
    }
}

template<bool FIRST_MIP>
void prefilter(const LinearImage& normal, const LinearImage& roughness, const size_t mipLevel,
        LinearImage& output) {
    const size_t width = output.getWidth();
    const size_t height = output.getHeight();
    const size_t sampleCount = 1u << mipLevel;

    for (size_t y = 0; y < height; y++) {
        auto outputRow = output.get<float3>(0, y);
        for (size_t x = 0; x < width; x++, outputRow++) {
            auto data = roughness.get<float3>(x, y);
            if (FIRST_MIP) {
                *outputRow = *data;
            } else {
                const float2 uv = (float2(x, y) + 0.5f) / float2(width, height);
                const float2 pos = uv * normal.getWidth();
                *outputRow = float3(solveVMF(pos, sampleCount, data->r, normal));
            }
        }
    }
}

int main(int argc, char* argv[]) {
    int optionIndex = handleArguments(argc, argv);

    int numArgs = argc - optionIndex;
    if (numArgs < 2) {
        printUsage(argv[0]);
        return 1;
    }

    Path normalMap(argv[optionIndex    ]);
    Path outputMap(argv[optionIndex + 1]);

    if (!normalMap.exists()) {
        std::cerr << "The input normal map does not exist: " << normalMap << std::endl;
        exit(1);
    }

    // make sure we load the normal maps as linear data
    std::ifstream inputStream(normalMap, std::ios::binary);
    LinearImage normalImage = ImageDecoder::decode(inputStream, normalMap,
            ImageDecoder::ColorSpace::LINEAR);
    inputStream.close();

    if (!normalImage.isValid()) {
        std::cerr << "The input normal map is invalid: " << normalMap << std::endl;
        exit(1);
    }

    if (!isPOT(normalImage.getWidth()) || !isPOT(normalImage.getHeight()) ||
            normalImage.getWidth() != normalImage.getHeight()) {
        std::cerr << "The input normal map must have equal power-of-2 dimensions: " <<
                normalImage.getWidth() << "x" << normalImage.getHeight() << std::endl;
        exit(1);
    }

    LinearImage roughnessImage;
    std::vector<LinearImage> mipImages;
    bool hasRoughnessMap = false;

    if (!g_roughnessMap.isEmpty()) {
        Path roughnessMap(g_roughnessMap);
        if (!roughnessMap.exists()) {
            std::cerr << "The input roughness map does not exist: " << roughnessMap << std::endl;
            exit(1);
        }

        hasRoughnessMap = true;

        inputStream.open(roughnessMap, std::ios::binary);
        roughnessImage = ImageDecoder::decode(inputStream, roughnessMap,
                ImageDecoder::ColorSpace::LINEAR);
        inputStream.close();

        if (!roughnessImage.isValid()) {
            std::cerr << "The input roughness map is invalid: " << roughnessMap << std::endl;
            exit(1);
        }

        if (!isPOT(roughnessImage.getWidth()) || !isPOT(roughnessImage.getHeight()) ||
                roughnessImage.getWidth() != roughnessImage.getHeight()) {
            std::cerr << "The input roughness map must have equal power-of-2 dimensions: " <<
                    roughnessImage.getWidth() << "x" << roughnessImage.getHeight() << std::endl;
            exit(1);
        }

        if (roughnessImage.getWidth() != normalImage.getWidth() ||
                roughnessImage.getHeight() != normalImage.getHeight()) {
            std::cerr << "The input normal and roughness maps must have the same dimensions" << std::endl;
            exit(1);
        }
    }

    if (!g_formatSpecified) {
        g_format = ImageEncoder::chooseFormat(outputMap, g_linearOutput);
    } else {
        if (g_format == ImageEncoder::Format::PNG && g_linearOutput) {
            g_format = ImageEncoder::Format::PNG_LINEAR;
        }
    }

    const size_t width = hasRoughnessMap ? roughnessImage.getWidth() : normalImage.getWidth();
    const size_t height = hasRoughnessMap ? roughnessImage.getHeight() : normalImage.getHeight();
    const size_t mipLevels = size_t(std::log2f(width)) + 1;

    bool exportGrayscale = false;
    switch (g_format) {
        case ImageEncoder::Format::DDS:
        case ImageEncoder::Format::DDS_LINEAR:
        case ImageEncoder::Format::PNG:
        case ImageEncoder::Format::PNG_LINEAR:
            exportGrayscale = true;
            break;
        default:
            break;
    }

    if (hasRoughnessMap) {
        mipImages.push_back(std::move(roughnessImage));
        LinearImage* prevMip = &mipImages.at(0);

        for (size_t i = 1; i <= mipLevels; i++) {
            const size_t w = width >> i;
            const size_t h = height >> i;

            LinearImage image(w, h, 3);

            for (size_t y = 0; y < h; y++) {
                auto dst = image.get<float3>(0, y);
                for (size_t x = 0; x < w; x++, dst++) {
                    float3 aa = *prevMip->get<float3>(x * 2,     y * 2);
                    float3 ba = *prevMip->get<float3>(x * 2 + 1, y * 2);
                    float3 ab = *prevMip->get<float3>(x * 2,     y * 2 + 1);
                    float3 bb = *prevMip->get<float3>(x * 2 + 1, y * 2 + 1);
                    *dst = (aa + ba + ab + bb) / 4.0f;
                }
            }

            mipImages.push_back(std::move(image));
            prevMip = &mipImages.at(i);
        }
    }

    JobSystem js;
    js.adopt();

    JobSystem::Job* parent = js.createJob();
    for (size_t i = 0; i < mipLevels; i++) {
        JobSystem::Job* mip = jobs::createJob(js, parent,
                [&normalImage, &mipImages, outputMap, i, width, height, exportGrayscale, hasRoughnessMap]() {
            const size_t w = width >> i;
            const size_t h = height >> i;

            LinearImage image(w, h, 3);

            if (i == 0) {
                if (hasRoughnessMap) {
                    if (mipImages.at(0).getWidth() == image.getWidth()) {
                        const size_t size = image.getWidth() * image.getHeight() * 12;
                        memcpy(image.getPixelRef(), mipImages.at(0).getPixelRef(), size);
                    } else {
                        prefilter<true>(normalImage, mipImages.at(0), 0, image);
                    }
                } else {
                    std::fill_n(image.get<float3>(), w * h, float3(g_roughness));
                }
            } else {
                if (hasRoughnessMap) {
                    prefilter<false>(normalImage, mipImages.at(i), i, image);
                } else {
                    prefilter(normalImage, i, image);
                }
            }

            const std::string ext = outputMap.getExtension();
            const std::string name = outputMap.getNameWithoutExtension();
            Path out = Path(outputMap.getParent()).concat(
                    name + "_" + std::to_string(i) + "." + ext); // NOLINT

            std::ofstream outputStream(out, std::ios::binary | std::ios::trunc);
            if (!outputStream.good()) {
                std::cerr << "The output file cannot be opened: " << out << std::endl;
                return;
            }
            if (exportGrayscale) {
                image = extractChannel(image, 0);
            }
            ImageEncoder::encode(outputStream, g_format, image, g_compression, out.getPath());
            outputStream.close();
            if (!outputStream.good()) {
                std::cerr << "An error occurred while writing the output file: " << out <<
                        std::endl;
            }
        });
        js.run(mip);
    }
    js.runAndWait(parent);
}
<commit_msg>Fix / simplify roughness-prefilter.<commit_after>/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <algorithm>
#include <cstring>
#include <fstream>
#include <iostream>
#include <thread>

#include <math/vec3.h>

#include <image/ImageOps.h>
#include <image/ImageSampler.h>

#include <imageio/ImageDecoder.h>
#include <imageio/ImageEncoder.h>

#include <utils/JobSystem.h>
#include <utils/Path.h>

#include <getopt/getopt.h>

using namespace math;
using namespace image;
using namespace utils;

static ImageEncoder::Format g_format = ImageEncoder::Format::PNG_LINEAR;
static bool g_formatSpecified = false;
static bool g_linearOutput = false;
static std::string g_compression;

static Path g_roughnessMap;
static float g_roughness = 0.0;

static void printUsage(const char* name) {
    std::string execName(Path(name).getName());
    std::string usage(
            "ROUGHNESSPREFILTER generates pre-filtered roughness maps from normal maps\n"
            "to help mitigate specular aliasing.\n"
            "Usage:\n"
            "    ROUGHNESSPREFILTER [options] <normal-map> <output-roughness-map>\n"
            "\n"
            "Output note:\n"
            "    One file will be generated per mip-level. The size of the first level will be\n"
            "    the greater of the input normal map and input roughness map.\n"
            "\n"
            "Supported input formats:\n"
            "    PNG, 8 and 16 bits\n"
            "    Radiance (.hdr)\n"
            "    Photoshop (.psd), 16 and 32 bits\n"
            "    OpenEXR (.exr)\n"
            "\n"
            "Options:\n"
            "   --help, -h\n"
            "       print this message\n\n"
            "   --license\n"
            "       Print copyright and license information\n\n"
            "   --roughness=[0..1], -r [0..1]\n"
            "       desired constant roughness, ignored if --roughness-map is specified\n\n"
            "   --roughness-map=<input-roughness-map>, -m <input-roughness-map>\n"
            "       input roughness map\n\n"
            "   --format=[exr|hdr|psd|png|dds], -f [exr|hdr|psd|png|dds]\n"
            "       specify output file format, inferred from file name if omitted\n\n"
            "   --compression=COMPRESSION, -c COMPRESSION\n"
            "       format specific compression:\n"
            "           PNG: Ignored\n"
            "           Radiance: Ignored\n"
            "           Photoshop: 16 (default), 32\n"
            "           OpenEXR: RAW, RLE, ZIPS, ZIP, PIZ (default)\n"
            "           DDS: 8, 16 (default), 32\n\n"
            "   --linear, -l\n"
            "       force linear output when the PNG format is selected\n\n"
    );

    const std::string from("ROUGHNESSPREFILTER");
    for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
        usage.replace(pos, from.length(), execName);
    }
    printf("%s", usage.c_str());
}

static void license() {
    std::cout <<
    #include "licenses/licenses.inc"
    ;
}

static int handleArguments(int argc, char* argv[]) {
    static constexpr const char* OPTSTR = "hr:c:f:m:l";
    static const struct option OPTIONS[] = {
            { "help",                 no_argument, nullptr, 'h' },
            { "license",              no_argument, nullptr, 's' },
            { "format",         required_argument, nullptr, 'f' },
            { "compression",    required_argument, nullptr, 'c' },
            { "roughness",      required_argument, nullptr, 'r' },
            { "roughness-map",  required_argument, nullptr, 'm' },
            { "linear",               no_argument, nullptr, 'l' },
            { nullptr, 0, nullptr, 0 }  // termination of the option list
    };

    int opt;
    int optionIndex = 0;

    while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &optionIndex)) >= 0) {
        std::string arg(optarg ? optarg : "");
        switch (opt) {
            default:
            case 'h':
                printUsage(argv[0]);
                exit(0);
                // break;
            case 's':
                license();
                exit(0);
                // break;
            case 'f':
                if (arg == "png") {
                    g_format = ImageEncoder::Format::PNG;
                    g_formatSpecified = true;
                }
                if (arg == "hdr") {
                    g_format = ImageEncoder::Format::HDR;
                    g_formatSpecified = true;
                }
                if (arg == "exr") {
                    g_format = ImageEncoder::Format::EXR;
                    g_formatSpecified = true;
                }
                if (arg == "psd") {
                    g_format = ImageEncoder::Format::PSD;
                    g_formatSpecified = true;
                }
                if (arg == "dds") {
                    g_format = ImageEncoder::Format::DDS_LINEAR;
                    g_formatSpecified = true;
                }
                break;
            case 'c':
                g_compression = arg;
                break;
            case 'r':
                try {
                    g_roughness = std::stof(arg);
                } catch (std::invalid_argument& e) {
                    std::cerr << "The specified roughness must be [0..1]: " << arg << std::endl;
                    exit(1);
                } catch (std::out_of_range& e) {
                    std::cerr << "The specified roughness must be [0..1]: " << arg << std::endl;
                    exit(1);
                }
                break;
            case 'm':
                g_roughnessMap = arg;
                break;
            case 'l':
                g_linearOutput = true;
                break;
        }
    }

    return optind;
}

inline bool isPOT(size_t x) {
    return !(x & (x - 1));
}

float solveVMF(const float2& pos, const size_t sampleCount, const float roughness,
        const LinearImage& normal) {

    float3 averageNormal(0.0f);
    float2 topLeft(-float(sampleCount) / 2.0f + 0.5f);

    for (size_t y = 0; y < sampleCount; y++) {
        for (size_t x = 0; x < sampleCount; x++) {
            float2 offset(topLeft + float2(x, y));
            float2 samplePos(floor(pos + offset) + 0.5f);
            float3 sampleNormal = *normal.get<float3>(size_t(samplePos.x), size_t(samplePos.y));
            sampleNormal = sampleNormal * 2.0f - 1.0f;

            averageNormal += normalize(sampleNormal);
        }
    }

    averageNormal /= (sampleCount * sampleCount);

    float r = length(averageNormal);
    float kappa = 10000.0f;

    if (r < 1.0f) {
        kappa = (3.0f * r - r * r * r) / (1.0f - r * r);
    }

    // See: Karis, 2018, "Normal map filtering using vMF (part 3)"
    // The original formulation in Neubelt et al. 2013,
    // "Crafting a Next-Gen Material Pipeline for The Order: 1886" contains an error
    // and defines alpha' = sqrt(alpha^2 + 1 / kappa)
    return std::sqrt(roughness * roughness + (2.0f / kappa));
}

void prefilter(const LinearImage& normal, const size_t mipLevel, LinearImage& output) {
    const size_t width = output.getWidth();
    const size_t height = output.getHeight();
    const size_t sampleCount = 1u << mipLevel;

    for (size_t y = 0; y < height; y++) {
        auto outputRow = output.get<float>(0, y);
        for (size_t x = 0; x < width; x++, outputRow++) {
            const float2 uv = (float2(x, y) + 0.5f) / float2(width, height);
            const float2 pos = uv * normal.getWidth();
            const float roughness = g_roughness;
            *outputRow = solveVMF(pos, sampleCount, roughness, normal);
        }
    }
}

template<bool FIRST_MIP>
void prefilter(const LinearImage& normal, const LinearImage& roughness, const size_t mipLevel,
        LinearImage& output) {
    const size_t width = output.getWidth();
    const size_t height = output.getHeight();
    const size_t sampleCount = 1u << mipLevel;

    for (size_t y = 0; y < height; y++) {
        auto outputRow = output.get<float>(0, y);
        for (size_t x = 0; x < width; x++, outputRow++) {
            auto data = roughness.get<float>(x, y);
            if (FIRST_MIP) {
                *outputRow = *data;
            } else {
                const float2 uv = (float2(x, y) + 0.5f) / float2(width, height);
                const float2 pos = uv * normal.getWidth();
                *outputRow = solveVMF(pos, sampleCount, *data, normal);
            }
        }
    }
}

int main(int argc, char* argv[]) {
    int optionIndex = handleArguments(argc, argv);

    int numArgs = argc - optionIndex;
    if (numArgs < 2) {
        printUsage(argv[0]);
        return 1;
    }

    Path normalMap(argv[optionIndex    ]);
    Path outputMap(argv[optionIndex + 1]);

    if (!normalMap.exists()) {
        std::cerr << "The input normal map does not exist: " << normalMap << std::endl;
        exit(1);
    }

    // make sure we load the normal maps as linear data
    std::ifstream inputStream(normalMap, std::ios::binary);
    LinearImage normalImage = ImageDecoder::decode(inputStream, normalMap,
            ImageDecoder::ColorSpace::LINEAR);
    inputStream.close();

    if (!normalImage.isValid()) {
        std::cerr << "The input normal map is invalid: " << normalMap << std::endl;
        exit(1);
    }

    if (!isPOT(normalImage.getWidth()) || !isPOT(normalImage.getHeight()) ||
            normalImage.getWidth() != normalImage.getHeight()) {
        std::cerr << "The input normal map must have equal power-of-2 dimensions: " <<
                normalImage.getWidth() << "x" << normalImage.getHeight() << std::endl;
        exit(1);
    }

    LinearImage roughnessImage;
    std::vector<LinearImage> mipImages;
    bool hasRoughnessMap = false;

    if (!g_roughnessMap.isEmpty()) {
        Path roughnessMap(g_roughnessMap);
        if (!roughnessMap.exists()) {
            std::cerr << "The input roughness map does not exist: " << roughnessMap << std::endl;
            exit(1);
        }

        hasRoughnessMap = true;

        inputStream.open(roughnessMap, std::ios::binary);
        roughnessImage = ImageDecoder::decode(inputStream, roughnessMap,
                ImageDecoder::ColorSpace::LINEAR);
        inputStream.close();

        if (!roughnessImage.isValid()) {
            std::cerr << "The input roughness map is invalid: " << roughnessMap << std::endl;
            exit(1);
        }

        if (!isPOT(roughnessImage.getWidth()) || !isPOT(roughnessImage.getHeight()) ||
                roughnessImage.getWidth() != roughnessImage.getHeight()) {
            std::cerr << "The input roughness map must have equal power-of-2 dimensions: " <<
                    roughnessImage.getWidth() << "x" << roughnessImage.getHeight() << std::endl;
            exit(1);
        }

        if (roughnessImage.getWidth() != normalImage.getWidth() ||
                roughnessImage.getHeight() != normalImage.getHeight()) {
            std::cerr << "The input normal and roughness maps must have the same dimensions" << std::endl;
            exit(1);
        }

        roughnessImage = extractChannel(roughnessImage, 0);
    }

    if (!g_formatSpecified) {
        g_format = ImageEncoder::chooseFormat(outputMap, g_linearOutput);
    } else {
        if (g_format == ImageEncoder::Format::PNG && g_linearOutput) {
            g_format = ImageEncoder::Format::PNG_LINEAR;
        }
    }

    const size_t width = hasRoughnessMap ? roughnessImage.getWidth() : normalImage.getWidth();
    const size_t height = hasRoughnessMap ? roughnessImage.getHeight() : normalImage.getHeight();
    const size_t mipLevels = size_t(std::log2f(width)) + 1;

    if (hasRoughnessMap) {
        mipImages.resize(mipLevels);
        mipImages[0] = roughnessImage;
        image::generateMipmaps(roughnessImage, image::Filter::BOX, &mipImages[1], mipLevels - 1);
    }

    JobSystem js;
    js.adopt();

    JobSystem::Job* parent = js.createJob();
    for (size_t i = 0; i < mipLevels; i++) {
        JobSystem::Job* mip = jobs::createJob(js, parent,
                [&normalImage, &mipImages, outputMap, i, width, height, hasRoughnessMap]() {
            const size_t w = width >> i;
            const size_t h = height >> i;

            LinearImage image(w, h, 1);

            if (i == 0) {
                if (hasRoughnessMap) {
                    if (mipImages.at(0).getWidth() == image.getWidth()) {
                        const size_t size = image.getWidth() * image.getHeight() * sizeof(float);
                        memcpy(image.getPixelRef(), mipImages.at(0).getPixelRef(), size);
                    } else {
                        prefilter<true>(normalImage, mipImages.at(0), 0, image);
                    }
                } else {
                    std::fill_n(image.get<float>(), w * h, g_roughness);
                }
            } else {
                if (hasRoughnessMap) {
                    prefilter<false>(normalImage, mipImages.at(i), i, image);
                } else {
                    prefilter(normalImage, i, image);
                }
            }

            const std::string ext = outputMap.getExtension();
            const std::string name = outputMap.getNameWithoutExtension();
            Path out = Path(outputMap.getParent()).concat(
                    name + "_" + std::to_string(i) + "." + ext); // NOLINT

            std::ofstream outputStream(out, std::ios::binary | std::ios::trunc);
            if (!outputStream.good()) {
                std::cerr << "The output file cannot be opened: " << out << std::endl;
                return;
            }
            ImageEncoder::encode(outputStream, g_format, image, g_compression, out.getPath());
            outputStream.close();
            if (!outputStream.good()) {
                std::cerr << "An error occurred while writing the output file: " << out <<
                        std::endl;
            }
        });
        js.run(mip);
    }
    js.runAndWait(parent);
}
<|endoftext|>


<commit_before>/*
    This file is part of KXForms.

    Copyright (c) 2006 Cornelius Schumacher <schumacher@kde.org>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "formcreator.h"

#include "xmlbuilder.h"

#include <QDebug>

using namespace KXForms;

FormCreator::FormCreator()
{
}

QString FormCreator::create( const Schema::Document &schemaDocument )
{
  qDebug() << "---FormCreator::create()";

  mDocument = schemaDocument;

  mCollapsedForms.clear();

  XmlBuilder xml( "kxforms" );

  createForm( &xml, schemaDocument.startElement() );

  foreach ( Schema::Element element, schemaDocument.usedElements() ) {
    if ( element.identifier() != schemaDocument.startElement().identifier() ) {
      createForm( &xml, element );
    }
  }

  qDebug() << "---FormCreator::create() done";

  return xml.print();
}

void FormCreator::createForm( XmlBuilder *xml, const Schema::Element &element )
{
  if ( mCollapsedForms.contains( element.name() ) ) return;

  qDebug() << "ELEMENT" << element.name();
  XmlBuilder *form = xml->tag( "form" )->attribute( "ref", element.name() );

  form->tag( "xf:label", humanizeString( element.name() ) );

  parseAttributes( element, form );

  parseElement( element, form );
}

void FormCreator::parseAttributes( const Schema::Element &element, XmlBuilder *xml  )
{
  foreach( Schema::Relation r, element.attributeRelations() ) {
    Schema::Attribute a = mDocument.attribute( r );

    qDebug() << "  ATTRIBUTE: " << a.identifier();

    if ( a.type() == Schema::Attribute::String ) {
      XmlBuilder *input = xml->tag( "xf:input" );
      input->attribute( "ref", a.ref() );
      createLabel( input, a );
    } else if ( a.type() == Schema::Attribute::Enumeration ) {
      XmlBuilder *select1 = xml->tag( "xf:select1" );
      select1->attribute( "ref", a.ref() );
      createLabel( select1, a );
      foreach( QString value, a.enumerationValues() ) {
        XmlBuilder *item = select1->tag( "xf:item" );
        QString itemLabel;
        Hint hint = mHints.hint( element.identifier() + '/' + a.ref() );
        if ( hint.isValid() ) itemLabel = hint.enumValue( value );
        if ( itemLabel.isEmpty() ) itemLabel = humanizeString( value );
        item->tag( "xf:label", itemLabel );
        item->tag( "xf:value", value );
      }
    } else {
      qDebug() << "Unsupported type: " << a.type();
    }
  }
}

void FormCreator::parseElement( const Schema::Element &element, XmlBuilder *xml )
{
  if ( element.type() == Schema::Node::String ) {
    XmlBuilder *textArea = xml->tag( "xf:textarea" );
    textArea->attribute( "ref", "." );
    createLabel( textArea, element );
  } else if ( element.type() == Schema::Node::NormalizedString ||
              element.type() == Schema::Node::Token ) {
    XmlBuilder *input = xml->tag( "xf:input" );
    input->attribute( "ref", "." );
    createLabel( input, element );
  } else if ( element.type() == Schema::Node::ComplexType ) {
    parseComplexType( element, xml, true );
  } else {
    qDebug() << "Unsupported type: " << element.type();
  }
}

void FormCreator::parseComplexType( const Schema::Element &element, XmlBuilder *xml, bool topLevel )
{
  QString currentChoice;

  XmlBuilder *section;

  bool choiceOnly = true;
  foreach( Schema::Relation r, element.elementRelations() ) {
    if( r.choice().isEmpty() )
      choiceOnly = false;
  }

  if( !topLevel &&
      !element.mixed() &&
      !choiceOnly &&
      element.elementRelations().size() > 1 ) {
    section = xml->tag( "kxf:section" );
    createLabel( section, element );
  } /*else if( !topLevel && element.type() ==  ) {
    section = xml->tag( "kxf:section" );
    createLabel( section, element );
  } */else {
    section = xml;
  }

  XmlBuilder *list = 0;
  XmlBuilder *choice = 0;
  foreach( Schema::Relation r, element.elementRelations() ) {
    qDebug() << "  CHILD ELEMENT" << r.target();
    qDebug() << "    CHOICE" << r.choice();
    if ( r.isList() ) {
      bool isMixedList = r.choice().contains( "+" );
      if ( !list || r.choice().isEmpty() || currentChoice != r.choice() ) {
        list = section->tag( "list" );
        QString label;
        if ( isMixedList ) {
          label = "Item";
        } else {
          label = getLabel( element.identifier() + '[' + r.target() + ']' );
          if ( label.isEmpty() ) {
            label = humanizeString( r.target(), true );
          }
        }
        list->tag( "xf:label", label );
      }
      XmlBuilder *item = list->tag( "itemclass" );
      item->attribute( "ref", r.target() );
      QString itemLabel;
      itemLabel = getLabel( element.identifier() + '/' + r.target() );

      Schema::Element itemElement = mDocument.element( r );

      if ( itemLabel.isEmpty() ) {
        // Try to guess a suitable item label.
        foreach( Schema::Relation r2, itemElement.attributeRelations() ) {
          if ( r2.target() == "name" ) {
            if ( isMixedList ) {
              itemLabel = humanizeString( itemElement.name() ) + ": ";
            }
            itemLabel += "<arg ref=\"@name\"/>";
           break;
          }
        }
      }

      if ( itemLabel.isEmpty() ) {
        if ( itemElement.type() == Schema::Node::String ) {
          itemLabel += "<arg ref=\".\" truncate=\"40\"/>";
      } else if ( itemElement.type() == Schema::Node::NormalizedString ||
                 itemElement.type() == Schema::Node::Token ) {
        itemLabel += "<arg ref=\".\"/>";
        }
      }

      if ( itemLabel.isEmpty() ) itemLabel = humanizeString( r.target() );
      item->tag( "itemlabel", itemLabel );

      currentChoice = r.choice();
    } else if( !r.choice().isEmpty() ) {
      if( !choice ) {
        choice = section->tag( "xf:select1" );
        choice->tag( "xf:label",  getLabel( element.ref(), element.name() ) );
      }
      Schema::Element choiceElement = mDocument.element( r );
      XmlBuilder *item = choice->tag( "xf:item" );
      QString value = choiceElement.name();
      QString itemLabel = getLabel( choiceElement.ref(), choiceElement.name() );

      item->tag( "xf:label", itemLabel );
      item->tag( "xf:value", value );
    } else{
      Schema::Element textElement = mDocument.element( r.target() );
      if( textElement.type() == Schema::Node::ComplexType && !textElement.mixed() ) {
        parseComplexType( textElement, section, false );
      } else {
        XmlBuilder *textInput = 0;
        if ( textElement.type() == Schema::Node::NormalizedString ) {
          textInput = section->tag( "xf:input" );
        } else {
          textInput = section->tag( "xf:textarea" );
        }
        textInput->attribute( "ref", textElement.name() );
        createLabel( textInput, textElement );
        mCollapsedForms.append( r.target() );
      }
    }
  }
  if( element.elementRelations().size() == 0 ) {
    XmlBuilder *textInput = 0;
    textInput = section->tag( "xf:textarea" );
    textInput->attribute( "ref", element.name() );
    createLabel( textInput, element );
  }
}

QString FormCreator::humanizeString( const QString &str, bool pluralize )
{
  if ( str.isEmpty() ) return str;
  QString result = str[0].toUpper() + str.mid( 1 );
  if ( pluralize ) {
    if ( result.endsWith( "y" ) ) {
      result = result.left( str.length() - 1 ) + "ies";
    } else {
      result += 's';
    }
  }

  return result;
}

void FormCreator::setHints( const Hints &hints )
{
  mHints = hints;
}

void FormCreator::mergeHints( const Hints &hints )
{
  foreach( Hint h, hints.hints() ) {
    mHints.insertHint( h );
  }
}

void FormCreator::createLabel( XmlBuilder *parent, const Schema::Node &node )
{
  parent->tag( "xf:label", getLabel( node.identifier(), node.name() ) );
}

QString FormCreator::getLabel( const QString &ref, const QString &fallback,
  bool pluralize )
{
//  qDebug() << "GETLABEL: " << ref;

  QString label;

  Hint hint = mHints.hint( ref );

  if ( hint.isValid() ) label = hint.label();

  if ( label.isEmpty() ) label = humanizeString( fallback, pluralize );

  return label;
}

Hints FormCreator::hints() const
{
  return mHints;
}
<commit_msg>Add missing reference in choice elements.<commit_after>/*
    This file is part of KXForms.

    Copyright (c) 2006 Cornelius Schumacher <schumacher@kde.org>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "formcreator.h"

#include "xmlbuilder.h"

#include <QDebug>

using namespace KXForms;

FormCreator::FormCreator()
{
}

QString FormCreator::create( const Schema::Document &schemaDocument )
{
  qDebug() << "---FormCreator::create()";

  mDocument = schemaDocument;

  mCollapsedForms.clear();

  XmlBuilder xml( "kxforms" );

  createForm( &xml, schemaDocument.startElement() );

  foreach ( Schema::Element element, schemaDocument.usedElements() ) {
    if ( element.identifier() != schemaDocument.startElement().identifier() ) {
      createForm( &xml, element );
    }
  }

  qDebug() << "---FormCreator::create() done";

  return xml.print();
}

void FormCreator::createForm( XmlBuilder *xml, const Schema::Element &element )
{
  if ( mCollapsedForms.contains( element.name() ) ) return;

  qDebug() << "ELEMENT" << element.name();
  XmlBuilder *form = xml->tag( "form" )->attribute( "ref", element.name() );

  form->tag( "xf:label", humanizeString( element.name() ) );

  parseAttributes( element, form );

  parseElement( element, form );
}

void FormCreator::parseAttributes( const Schema::Element &element, XmlBuilder *xml  )
{
  foreach( Schema::Relation r, element.attributeRelations() ) {
    Schema::Attribute a = mDocument.attribute( r );

    qDebug() << "  ATTRIBUTE: " << a.identifier();

    if ( a.type() == Schema::Attribute::String ) {
      XmlBuilder *input = xml->tag( "xf:input" );
      input->attribute( "ref", a.ref() );
      createLabel( input, a );
    } else if ( a.type() == Schema::Attribute::Enumeration ) {
      XmlBuilder *select1 = xml->tag( "xf:select1" );
      select1->attribute( "ref", a.ref() );
      createLabel( select1, a );
      foreach( QString value, a.enumerationValues() ) {
        XmlBuilder *item = select1->tag( "xf:item" );
        QString itemLabel;
        Hint hint = mHints.hint( element.identifier() + '/' + a.ref() );
        if ( hint.isValid() ) itemLabel = hint.enumValue( value );
        if ( itemLabel.isEmpty() ) itemLabel = humanizeString( value );
        item->tag( "xf:label", itemLabel );
        item->tag( "xf:value", value );
      }
    } else {
      qDebug() << "Unsupported type: " << a.type();
    }
  }
}

void FormCreator::parseElement( const Schema::Element &element, XmlBuilder *xml )
{
  if ( element.type() == Schema::Node::String ) {
    XmlBuilder *textArea = xml->tag( "xf:textarea" );
    textArea->attribute( "ref", "." );
    createLabel( textArea, element );
  } else if ( element.type() == Schema::Node::NormalizedString ||
              element.type() == Schema::Node::Token ) {
    XmlBuilder *input = xml->tag( "xf:input" );
    input->attribute( "ref", "." );
    createLabel( input, element );
  } else if ( element.type() == Schema::Node::ComplexType ) {
    parseComplexType( element, xml, true );
  } else {
    qDebug() << "Unsupported type: " << element.type();
  }
}

void FormCreator::parseComplexType( const Schema::Element &element, XmlBuilder *xml, bool topLevel )
{
  QString currentChoice;

  XmlBuilder *section;

  bool choiceOnly = true;
  foreach( Schema::Relation r, element.elementRelations() ) {
    if( r.choice().isEmpty() )
      choiceOnly = false;
  }

  if( !topLevel &&
      !element.mixed() &&
      !choiceOnly &&
      element.elementRelations().size() > 1 ) {
    section = xml->tag( "kxf:section" );
    createLabel( section, element );
  } /*else if( !topLevel && element.type() ==  ) {
    section = xml->tag( "kxf:section" );
    createLabel( section, element );
  } */else {
    section = xml;
  }

  XmlBuilder *list = 0;
  XmlBuilder *choice = 0;
  foreach( Schema::Relation r, element.elementRelations() ) {
    qDebug() << "  CHILD ELEMENT" << r.target();
    qDebug() << "    CHOICE" << r.choice();
    if ( r.isList() ) {
      bool isMixedList = r.choice().contains( "+" );
      if ( !list || r.choice().isEmpty() || currentChoice != r.choice() ) {
        list = section->tag( "list" );
        QString label;
        if ( isMixedList ) {
          label = "Item";
        } else {
          label = getLabel( element.identifier() + '[' + r.target() + ']' );
          if ( label.isEmpty() ) {
            label = humanizeString( r.target(), true );
          }
        }
        list->tag( "xf:label", label );
      }
      XmlBuilder *item = list->tag( "itemclass" );
      item->attribute( "ref", r.target() );
      QString itemLabel;
      itemLabel = getLabel( element.identifier() + '/' + r.target() );

      Schema::Element itemElement = mDocument.element( r );

      if ( itemLabel.isEmpty() ) {
        // Try to guess a suitable item label.
        foreach( Schema::Relation r2, itemElement.attributeRelations() ) {
          if ( r2.target() == "name" ) {
            if ( isMixedList ) {
              itemLabel = humanizeString( itemElement.name() ) + ": ";
            }
            itemLabel += "<arg ref=\"@name\"/>";
           break;
          }
        }
      }

      if ( itemLabel.isEmpty() ) {
        if ( itemElement.type() == Schema::Node::String ) {
          itemLabel += "<arg ref=\".\" truncate=\"40\"/>";
      } else if ( itemElement.type() == Schema::Node::NormalizedString ||
                 itemElement.type() == Schema::Node::Token ) {
        itemLabel += "<arg ref=\".\"/>";
        }
      }

      if ( itemLabel.isEmpty() ) itemLabel = humanizeString( r.target() );
      item->tag( "itemlabel", itemLabel );

      currentChoice = r.choice();
    } else if( !r.choice().isEmpty() ) {
      if( !choice ) {
        choice = section->tag( "xf:select1" );
        choice->tag( "xf:label",  getLabel( element.ref(), element.name() ) );
        choice->attribute( "ref", element.ref() );
      }
      Schema::Element choiceElement = mDocument.element( r );
      XmlBuilder *item = choice->tag( "xf:item" );
      QString value = choiceElement.name();
      QString itemLabel = getLabel( choiceElement.ref(), choiceElement.name() );

      item->tag( "xf:label", itemLabel );
      item->tag( "xf:value", value );
    } else{
      Schema::Element textElement = mDocument.element( r.target() );
      if( textElement.type() == Schema::Node::ComplexType && !textElement.mixed() ) {
        parseComplexType( textElement, section, false );
      } else {
        XmlBuilder *textInput = 0;
        if ( textElement.type() == Schema::Node::NormalizedString ) {
          textInput = section->tag( "xf:input" );
        } else {
          textInput = section->tag( "xf:textarea" );
        }
        textInput->attribute( "ref", textElement.name() );
        createLabel( textInput, textElement );
        mCollapsedForms.append( r.target() );
      }
    }
  }
  if( element.elementRelations().size() == 0 ) {
    XmlBuilder *textInput = 0;
    textInput = section->tag( "xf:textarea" );
    textInput->attribute( "ref", element.name() );
    createLabel( textInput, element );
  }
}

QString FormCreator::humanizeString( const QString &str, bool pluralize )
{
  if ( str.isEmpty() ) return str;
  QString result = str[0].toUpper() + str.mid( 1 );
  if ( pluralize ) {
    if ( result.endsWith( "y" ) ) {
      result = result.left( str.length() - 1 ) + "ies";
    } else {
      result += 's';
    }
  }

  return result;
}

void FormCreator::setHints( const Hints &hints )
{
  mHints = hints;
}

void FormCreator::mergeHints( const Hints &hints )
{
  foreach( Hint h, hints.hints() ) {
    mHints.insertHint( h );
  }
}

void FormCreator::createLabel( XmlBuilder *parent, const Schema::Node &node )
{
  parent->tag( "xf:label", getLabel( node.identifier(), node.name() ) );
}

QString FormCreator::getLabel( const QString &ref, const QString &fallback,
  bool pluralize )
{
//  qDebug() << "GETLABEL: " << ref;

  QString label;

  Hint hint = mHints.hint( ref );

  if ( hint.isValid() ) label = hint.label();

  if ( label.isEmpty() ) label = humanizeString( fallback, pluralize );

  return label;
}

Hints FormCreator::hints() const
{
  return mHints;
}
<|endoftext|>


<commit_before>#include "twsClient.h"
#include "twsUtil.h"
#include "debug.h"

// from global installed ibtws
#include "ibtws/Contract.h"
#include "ibtws/Order.h"
#include "ibtws/OrderState.h"
#include "ibtws/Execution.h"
#include "ibtws/TwsSocketClientErrors.h"
#include "ibtws/EWrapper.h"
#include "ibtws/EPosixClientSocket.h"

#include <unistd.h>
#include <errno.h>
#include <string.h>

// debug verbosity 0...n
#define tws_debug_level 1
#define TWS_DEBUG( _level, _fmt, _msg... )        \
	if( tws_debug_level >= _level ) {    \
		DEBUG_PRINTF("tws debug: " _fmt, ## _msg); \
	}

TWSClient::TWSClient( IB::EWrapper *ew ) :
	myEWrapper(ew),
	ePosixClient(NULL)
{
}

TWSClient::~TWSClient()
{
	if( ePosixClient != NULL ) {
		delete ePosixClient;
	}
}

bool TWSClient::isConnected() const
{
	return ( (ePosixClient != NULL) && ePosixClient->isConnected() );
}

void TWSClient::connectTWS( const std::string &host, int port, int clientId )
{
	DEBUG_PRINTF("connect: %s:%d, clientId: %d", host.c_str(), port, clientId);

	if( isConnected() ) {
		myEWrapper->error( IB::NO_VALID_ID, IB::ALREADY_CONNECTED.code(),
			IB::ALREADY_CONNECTED.msg() );
		return;
	} else if( ePosixClient != NULL ) {
		delete ePosixClient;
	}

	ePosixClient = new IB::EPosixClientSocket(myEWrapper);

	ePosixClient->eConnect( host.c_str(), port, clientId );
}

void TWSClient::disconnectTWS()
{
	DEBUG_PRINTF("disconnect TWS");

	if ( !isConnected()) {
		return;
	}

	ePosixClient->eDisconnect();
	myEWrapper->connectionClosed();
	DEBUG_PRINTF("We are disconnected");
}

void TWSClient::selectStuff( int msec )
{
	struct timeval tval;
	tval.tv_usec = msec * 1000;
	tval.tv_sec = 0;

	fd_set readSet, writeSet, errorSet;

	FD_ZERO( &readSet);
	errorSet = writeSet = readSet;

	int fd = -1;
	if( isConnected() ) {
		// if not connected then all sets are zero and select will just timeout
		fd = ePosixClient->fd();
		assert( fd >= 0 );

		FD_SET( fd, &readSet);
		if( !ePosixClient->isOutBufferEmpty()) {
			FD_SET( fd, &writeSet);
		}
		FD_CLR( fd, &errorSet);
	}
	int ret = select( fd + 1,
		&readSet, &writeSet, &errorSet, &tval );
	/////  blocking  ///////////////////////////////////////

	if( ret == 0) {
		TWS_DEBUG( 5 , "Select timeouted." );
		return;
	} else if( ret < 0) {
		TWS_DEBUG( 1 , "Select failed with failed with errno: %s.",
			strerror(errno) );
		disconnectTWS();
		return;
	}

	if( FD_ISSET( fd, &errorSet)) {
		TWS_DEBUG( 1 ,"Error on socket." );
		ePosixClient->onError(); // might disconnect us
		if( !isConnected() ) {
			return;
		}
	}

	if( FD_ISSET( fd, &writeSet)) {
		TWS_DEBUG( 1 ,"Socket is ready for writing." );
		ePosixClient->onSend(); // might disconnect us on socket errors
		if( !isConnected() ) {
			return;
		}
	}

	if( FD_ISSET( fd, &readSet)) {
		TWS_DEBUG( 6 ,"Socket is ready for reading." );
		ePosixClient->onReceive(); // might disconnect us on socket errors
	}
}

int TWSClient::serverVersion()
{
	return ePosixClient->serverVersion();
}

std::string TWSClient::TwsConnectionTime()
{
	return ePosixClient->TwsConnectionTime();
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////

void TWSClient::reqMktData(int tickerId, const IB::Contract &contract,
	const  std::string &genericTickList, bool snapshot)
{
	DEBUG_PRINTF( "REQ_MKT_DATA %d "
		"'%s' '%s' '%s' '%s' '%s' '%g' '%s' '%s' '%s' %d",
		tickerId, contract.symbol.c_str(), contract.exchange.c_str(),
		contract.secType.c_str(), contract.expiry.c_str(),
		contract.right.c_str(), contract.strike, contract.currency.c_str(),
		contract.localSymbol.c_str(), genericTickList.c_str(), snapshot );

	ePosixClient->reqMktData( tickerId, contract, genericTickList, snapshot );
}

void TWSClient::cancelMktData ( int tickerId )
{
	DEBUG_PRINTF("CANCEL_MKT_DATA %d", tickerId);

	ePosixClient->cancelMktData( tickerId );
}

void TWSClient::placeOrder ( int id, const IB::Contract &contract,
	const IB::Order &order )
{
	DEBUG_PRINTF("PLACE_ORDER %d '%s' %ld '%s' %g '%s'",
		id, order.orderType.c_str(), order.totalQuantity, order.action.c_str(),
		order.lmtPrice, contract.symbol.c_str() );

	ePosixClient->placeOrder( id, contract, order );
}

void TWSClient::cancelOrder ( int id )
{
	DEBUG_PRINTF("CANCEL_ORDER %d", id);

	ePosixClient->cancelOrder( id );
}

void TWSClient::reqOpenOrders()
{
	DEBUG_PRINTF("REQ_OPEN_ORDERS");

	ePosixClient->reqOpenOrders();
}

void TWSClient::reqAllOpenOrders()
{
	DEBUG_PRINTF("REQ_ALL_OPEN_ORDERS");
	ePosixClient->reqAllOpenOrders();
}

void TWSClient::reqAutoOpenOrders( bool bAutoBind )
{
	DEBUG_PRINTF("REQ_AUTO_OPEN_ORDERS %d", bAutoBind);
	ePosixClient->reqAutoOpenOrders( bAutoBind );
}

void TWSClient::reqAccountUpdates( bool subscribe, const std::string &acctCode )
{
	DEBUG_PRINTF("REQ_ACCOUNT_DATA %d '%s'", subscribe, acctCode.c_str() );

	ePosixClient->reqAccountUpdates( subscribe, acctCode );
}

void TWSClient::reqExecutions(int reqId, const IB::ExecutionFilter& filter)
{
	DEBUG_PRINTF("REQ_EXECUTIONS %d", reqId);

	ePosixClient->reqExecutions(reqId, filter);
}

void TWSClient::reqIds( int numIds)
{
	DEBUG_PRINTF("REQ_IDS %d", numIds);

	ePosixClient->reqIds( numIds );
}

void TWSClient::reqContractDetails( int reqId, const IB::Contract &contract )
{
	DEBUG_PRINTF("REQ_CONTRACT_DATA %d '%s' '%s' '%s'",
		reqId, contract.symbol.c_str(), contract.secType.c_str(),
		contract.exchange.c_str() );

	ePosixClient->reqContractDetails( reqId, contract );
}

void TWSClient::setServerLogLevel( int logLevel )
{
	DEBUG_PRINTF("SET_SERVER_LOGLEVEL %d", logLevel);

	ePosixClient->setServerLogLevel( logLevel );
}

void TWSClient::reqHistoricalData ( int tickerId, const IB::Contract &contract,
	const std::string &endDateTime, const std::string &durationStr,
	const std::string &barSizeSetting, const std::string &whatToShow,
	int useRTH, int formatDate )
{
	DEBUG_PRINTF("REQ_HISTORICAL_DATA %d "
		"'%s' '%s' '%s' '%s' '%s' '%s' '%s' %d %d",
		tickerId, contract.symbol.c_str(), contract.secType.c_str(),
		contract.exchange.c_str(), endDateTime.c_str(), durationStr.c_str(),
		barSizeSetting.c_str(), whatToShow.c_str(), useRTH, formatDate );

	ePosixClient->reqHistoricalData( tickerId, contract, endDateTime,
		durationStr, barSizeSetting, whatToShow, useRTH, formatDate );
}

void TWSClient::reqCurrentTime()
{
	DEBUG_PRINTF("REQ_CURRENT_TIME");

	ePosixClient->reqCurrentTime();
}

<commit_msg>TWSClient, don't select errorSet<commit_after>#include "twsClient.h"
#include "twsUtil.h"
#include "debug.h"

// from global installed ibtws
#include "ibtws/Contract.h"
#include "ibtws/Order.h"
#include "ibtws/OrderState.h"
#include "ibtws/Execution.h"
#include "ibtws/TwsSocketClientErrors.h"
#include "ibtws/EWrapper.h"
#include "ibtws/EPosixClientSocket.h"

#include <unistd.h>
#include <errno.h>
#include <string.h>

// debug verbosity 0...n
#define tws_debug_level 1
#define TWS_DEBUG( _level, _fmt, _msg... )        \
	if( tws_debug_level >= _level ) {    \
		DEBUG_PRINTF("tws debug: " _fmt, ## _msg); \
	}

TWSClient::TWSClient( IB::EWrapper *ew ) :
	myEWrapper(ew),
	ePosixClient(NULL)
{
}

TWSClient::~TWSClient()
{
	if( ePosixClient != NULL ) {
		delete ePosixClient;
	}
}

bool TWSClient::isConnected() const
{
	return ( (ePosixClient != NULL) && ePosixClient->isConnected() );
}

void TWSClient::connectTWS( const std::string &host, int port, int clientId )
{
	DEBUG_PRINTF("connect: %s:%d, clientId: %d", host.c_str(), port, clientId);

	if( isConnected() ) {
		myEWrapper->error( IB::NO_VALID_ID, IB::ALREADY_CONNECTED.code(),
			IB::ALREADY_CONNECTED.msg() );
		return;
	} else if( ePosixClient != NULL ) {
		delete ePosixClient;
	}

	ePosixClient = new IB::EPosixClientSocket(myEWrapper);

	ePosixClient->eConnect( host.c_str(), port, clientId );
}

void TWSClient::disconnectTWS()
{
	DEBUG_PRINTF("disconnect TWS");

	if ( !isConnected()) {
		return;
	}

	ePosixClient->eDisconnect();
	myEWrapper->connectionClosed();
	DEBUG_PRINTF("We are disconnected");
}

void TWSClient::selectStuff( int msec )
{
	struct timeval tval;
	tval.tv_usec = msec * 1000;
	tval.tv_sec = 0;

	fd_set readSet, writeSet;

	FD_ZERO( &readSet);
	writeSet = readSet;

	int fd = -1;
	if( isConnected() ) {
		// if not connected then all sets are zero and select will just timeout
		fd = ePosixClient->fd();
		assert( fd >= 0 );

		FD_SET( fd, &readSet);
		if( !ePosixClient->isOutBufferEmpty()) {
			FD_SET( fd, &writeSet);
		}
	}
	int ret = select( fd + 1,
		&readSet, &writeSet, NULL, &tval );
	/////  blocking  ///////////////////////////////////////

	if( ret == 0) {
		TWS_DEBUG( 5 , "Select timeouted." );
		return;
	} else if( ret < 0) {
		TWS_DEBUG( 1 , "Select failed with failed with errno: %s.",
			strerror(errno) );
		disconnectTWS();
		return;
	}

	if( FD_ISSET( fd, &writeSet)) {
		TWS_DEBUG( 1 ,"Socket is ready for writing." );
		ePosixClient->onSend(); // might disconnect us on socket errors
		if( !isConnected() ) {
			return;
		}
	}

	if( FD_ISSET( fd, &readSet)) {
		TWS_DEBUG( 6 ,"Socket is ready for reading." );
		ePosixClient->onReceive(); // might disconnect us on socket errors
	}
}

int TWSClient::serverVersion()
{
	return ePosixClient->serverVersion();
}

std::string TWSClient::TwsConnectionTime()
{
	return ePosixClient->TwsConnectionTime();
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////

void TWSClient::reqMktData(int tickerId, const IB::Contract &contract,
	const  std::string &genericTickList, bool snapshot)
{
	DEBUG_PRINTF( "REQ_MKT_DATA %d "
		"'%s' '%s' '%s' '%s' '%s' '%g' '%s' '%s' '%s' %d",
		tickerId, contract.symbol.c_str(), contract.exchange.c_str(),
		contract.secType.c_str(), contract.expiry.c_str(),
		contract.right.c_str(), contract.strike, contract.currency.c_str(),
		contract.localSymbol.c_str(), genericTickList.c_str(), snapshot );

	ePosixClient->reqMktData( tickerId, contract, genericTickList, snapshot );
}

void TWSClient::cancelMktData ( int tickerId )
{
	DEBUG_PRINTF("CANCEL_MKT_DATA %d", tickerId);

	ePosixClient->cancelMktData( tickerId );
}

void TWSClient::placeOrder ( int id, const IB::Contract &contract,
	const IB::Order &order )
{
	DEBUG_PRINTF("PLACE_ORDER %d '%s' %ld '%s' %g '%s'",
		id, order.orderType.c_str(), order.totalQuantity, order.action.c_str(),
		order.lmtPrice, contract.symbol.c_str() );

	ePosixClient->placeOrder( id, contract, order );
}

void TWSClient::cancelOrder ( int id )
{
	DEBUG_PRINTF("CANCEL_ORDER %d", id);

	ePosixClient->cancelOrder( id );
}

void TWSClient::reqOpenOrders()
{
	DEBUG_PRINTF("REQ_OPEN_ORDERS");

	ePosixClient->reqOpenOrders();
}

void TWSClient::reqAllOpenOrders()
{
	DEBUG_PRINTF("REQ_ALL_OPEN_ORDERS");
	ePosixClient->reqAllOpenOrders();
}

void TWSClient::reqAutoOpenOrders( bool bAutoBind )
{
	DEBUG_PRINTF("REQ_AUTO_OPEN_ORDERS %d", bAutoBind);
	ePosixClient->reqAutoOpenOrders( bAutoBind );
}

void TWSClient::reqAccountUpdates( bool subscribe, const std::string &acctCode )
{
	DEBUG_PRINTF("REQ_ACCOUNT_DATA %d '%s'", subscribe, acctCode.c_str() );

	ePosixClient->reqAccountUpdates( subscribe, acctCode );
}

void TWSClient::reqExecutions(int reqId, const IB::ExecutionFilter& filter)
{
	DEBUG_PRINTF("REQ_EXECUTIONS %d", reqId);

	ePosixClient->reqExecutions(reqId, filter);
}

void TWSClient::reqIds( int numIds)
{
	DEBUG_PRINTF("REQ_IDS %d", numIds);

	ePosixClient->reqIds( numIds );
}

void TWSClient::reqContractDetails( int reqId, const IB::Contract &contract )
{
	DEBUG_PRINTF("REQ_CONTRACT_DATA %d '%s' '%s' '%s'",
		reqId, contract.symbol.c_str(), contract.secType.c_str(),
		contract.exchange.c_str() );

	ePosixClient->reqContractDetails( reqId, contract );
}

void TWSClient::setServerLogLevel( int logLevel )
{
	DEBUG_PRINTF("SET_SERVER_LOGLEVEL %d", logLevel);

	ePosixClient->setServerLogLevel( logLevel );
}

void TWSClient::reqHistoricalData ( int tickerId, const IB::Contract &contract,
	const std::string &endDateTime, const std::string &durationStr,
	const std::string &barSizeSetting, const std::string &whatToShow,
	int useRTH, int formatDate )
{
	DEBUG_PRINTF("REQ_HISTORICAL_DATA %d "
		"'%s' '%s' '%s' '%s' '%s' '%s' '%s' %d %d",
		tickerId, contract.symbol.c_str(), contract.secType.c_str(),
		contract.exchange.c_str(), endDateTime.c_str(), durationStr.c_str(),
		barSizeSetting.c_str(), whatToShow.c_str(), useRTH, formatDate );

	ePosixClient->reqHistoricalData( tickerId, contract, endDateTime,
		durationStr, barSizeSetting, whatToShow, useRTH, formatDate );
}

void TWSClient::reqCurrentTime()
{
	DEBUG_PRINTF("REQ_CURRENT_TIME");

	ePosixClient->reqCurrentTime();
}

<|endoftext|>


<commit_before>#include <spdlog/spdlog.h>
#include <src/pugixml.hpp>
#include <nanogui/layout.h>
#include <nanogui/opengl.h>
#include "glshadersource.hpp"
#include "glshaderobject.hpp"
#include "clickablelabel.hpp"
#include "directpopup.hpp"
#include "sink.hpp"
#include "source.hpp"
#include "graphnodelink.hpp"
#include "graphnode.hpp"
#include "graph.hpp"

NAMESPACE_BEGIN(QCE);

GraphNode::GraphNode(Widget *parent, Graph *parentGraph, const std::string &title) :
    Window(parent, title),
    mParentGraph(parentGraph),
    mAnchorPos(Eigen::Vector2i::Zero()),
    mAnchorHeight(30),
    mShaderObject(nullptr)
{
    mSize = Eigen::Vector2i(100, 80);

    mPopup = new DirectPopup(this, this);
    mPopup->setId("graphNodePopup");
    mPopup->setSize(Eigen::Vector2i(10, 10));
    mPopup->setLayout(new nanogui::GroupLayout());
    mPopup->setVisible(false);

    ClickableLabel *removeButton  = new ClickableLabel(mPopup, "Remove");
    removeButton->setId("removeGraphNodeButton");
    removeButton->setCallback([this](const Eigen::Vector2i &p) {
        spdlog::get("qde")->debug(
            "GraphNode '{}': Removing",
            mTitle
        );
        mShaderSource->decTimesUsed();
        // TODO: Check if this really is the proper way to delete an
        // object. What happens with the instance here?
        Graph *parentGraph = dynamic_cast<Graph *>(mParent);
        parentGraph->removeChild(this);
        mPopup->setVisible(false);
    });
}

void GraphNode::draw(NVGcontext* ctx)
{
    refreshRelativePlacement();
    Window::draw(ctx);
}

void GraphNode::addSink(Sink* sink)
{
    mInputs.push_back(sink);
    sink->incRef();
}

void GraphNode::removeSink(const int index)
{
    Sink *sink = mInputs[index];
    mInputs.erase(mInputs.begin() + index);
    sink->decRef();
}

void GraphNode::removeSink(const Sink *sink)
{
    mInputs.erase(std::remove(
        mInputs.begin(),
        mInputs.end(),
        sink
    ), mInputs.end());
    sink->decRef();
}

void GraphNode::addSource(Source *source)
{
    mOutputs.push_back(source);
    source->incRef();
}

void GraphNode::removeSource(const int index)
{
    Source *source = mOutputs[index];
    mOutputs.erase(mOutputs.begin() + index);
    source->decRef();
}

void GraphNode::removeSource(const Source *source)
{
    mOutputs.erase(std::remove(
        mOutputs.begin(),
        mOutputs.end(),
        source
    ), mOutputs.end());
    source->decRef();
}

bool GraphNode::mouseButtonEvent(const Eigen::Vector2i &p, int button, bool down, int modifiers)
{
    Window::mouseButtonEvent(p, button, down, modifiers);

    if (button == GLFW_MOUSE_BUTTON_1 && mEnabled && down) {
        Graph *parentGraph = dynamic_cast<Graph *>(mParent);
        parentGraph->nodeSelectedEvent(this);

        return true;
    }

    if (button == GLFW_MOUSE_BUTTON_2 && mEnabled && down) {
        int offsetX = p.x() - mPos.x();
        int offsetY = p.y() - mPos.y();
        Eigen::Vector2i position(offsetX, offsetY);
        mPopup->setAnchorPos(position);
        mPopup->setVisible(!mPopup->visible());

        return true;
    }

    return false;
}

bool GraphNode::sinkConnectedEvent(Connector *sink)
{
    // May be overridden by child classes.
    return true;
}

// May be overridden by child classes.
std::string GraphNode::calculateOutput()
{
    fmt::MemoryWriter output;

    for (auto input : mInputs) {
        if (input->link()) {
            GraphNodeLink *link = input->link();
            // spdlog::get("qde")->debug("Node '{}': Input has link: '{}'", mId, link->id());

            if (link->source()) {
                GraphNode *node = dynamic_cast<GraphNode *>(link->source()->parent());
                output << node->calculateOutput();
                spdlog::get("qde")->debug("Node '{}': Link has source: '{}'", mId, node->id());
            }
        }
    }

    return output.str();
}

NAMESPACE_END(QCE);<commit_msg>Log when a mouse button event happens<commit_after>#include <spdlog/spdlog.h>
#include <src/pugixml.hpp>
#include <nanogui/layout.h>
#include <nanogui/opengl.h>
#include "glshadersource.hpp"
#include "glshaderobject.hpp"
#include "clickablelabel.hpp"
#include "directpopup.hpp"
#include "sink.hpp"
#include "source.hpp"
#include "graphnodelink.hpp"
#include "graphnode.hpp"
#include "graph.hpp"

NAMESPACE_BEGIN(QCE);

GraphNode::GraphNode(Widget *parent, Graph *parentGraph, const std::string &title) :
    Window(parent, title),
    mParentGraph(parentGraph),
    mAnchorPos(Eigen::Vector2i::Zero()),
    mAnchorHeight(30),
    mShaderObject(nullptr)
{
    mSize = Eigen::Vector2i(100, 80);

    mPopup = new DirectPopup(this, this);
    mPopup->setId("graphNodePopup");
    mPopup->setSize(Eigen::Vector2i(10, 10));
    mPopup->setLayout(new nanogui::GroupLayout());
    mPopup->setVisible(false);

    ClickableLabel *removeButton  = new ClickableLabel(mPopup, "Remove");
    removeButton->setId("removeGraphNodeButton");
    removeButton->setCallback([this](const Eigen::Vector2i &p) {
        spdlog::get("qde")->debug(
            "GraphNode '{}': Removing",
            mTitle
        );
        mShaderSource->decTimesUsed();
        // TODO: Check if this really is the proper way to delete an
        // object. What happens with the instance here?
        Graph *parentGraph = dynamic_cast<Graph *>(mParent);
        parentGraph->removeChild(this);
        mPopup->setVisible(false);
    });
}

void GraphNode::draw(NVGcontext* ctx)
{
    refreshRelativePlacement();
    Window::draw(ctx);
}

void GraphNode::addSink(Sink* sink)
{
    mInputs.push_back(sink);
    sink->incRef();
}

void GraphNode::removeSink(const int index)
{
    Sink *sink = mInputs[index];
    mInputs.erase(mInputs.begin() + index);
    sink->decRef();
}

void GraphNode::removeSink(const Sink *sink)
{
    mInputs.erase(std::remove(
        mInputs.begin(),
        mInputs.end(),
        sink
    ), mInputs.end());
    sink->decRef();
}

void GraphNode::addSource(Source *source)
{
    mOutputs.push_back(source);
    source->incRef();
}

void GraphNode::removeSource(const int index)
{
    Source *source = mOutputs[index];
    mOutputs.erase(mOutputs.begin() + index);
    source->decRef();
}

void GraphNode::removeSource(const Source *source)
{
    mOutputs.erase(std::remove(
        mOutputs.begin(),
        mOutputs.end(),
        source
    ), mOutputs.end());
    source->decRef();
}

bool GraphNode::mouseButtonEvent(const Eigen::Vector2i &p, int button, bool down, int modifiers)
{
    spdlog::get("qde")->debug(
        "GraphNode '{}': Received mouse button event at ({},{}): {}, {}",
        mTitle, p.x(), p.y(), button, down
    );

    Window::mouseButtonEvent(p, button, down, modifiers);

    if (button == GLFW_MOUSE_BUTTON_1 && mEnabled && down) {
        Graph *parentGraph = dynamic_cast<Graph *>(mParent);
        parentGraph->nodeSelectedEvent(this);

        return true;
    }

    if (button == GLFW_MOUSE_BUTTON_2 && mEnabled && down) {
        int offsetX = p.x() - mPos.x();
        int offsetY = p.y() - mPos.y();
        Eigen::Vector2i position(offsetX, offsetY);
        mPopup->setAnchorPos(position);
        mPopup->setVisible(!mPopup->visible());

        return true;
    }

    return false;
}

bool GraphNode::sinkConnectedEvent(Connector *sink)
{
    // May be overridden by child classes.
    return true;
}

// May be overridden by child classes.
std::string GraphNode::calculateOutput()
{
    fmt::MemoryWriter output;

    for (auto input : mInputs) {
        if (input->link()) {
            GraphNodeLink *link = input->link();
            // spdlog::get("qde")->debug("Node '{}': Input has link: '{}'", mId, link->id());

            if (link->source()) {
                GraphNode *node = dynamic_cast<GraphNode *>(link->source()->parent());
                output << node->calculateOutput();
                spdlog::get("qde")->debug("Node '{}': Link has source: '{}'", mId, node->id());
            }
        }
    }

    return output.str();
}

NAMESPACE_END(QCE);
<|endoftext|>


<commit_before>#include "buffer.hh"
#include "assert.hh"
#include "editor.hh"
#include "selectors.hh"

using namespace Kakoune;

void test_buffer()
{
    Buffer buffer("test", Buffer::Type::Scratch, "allo ?\nmais que fais la police\n hein ?\n youpi\n");
    assert(buffer.line_count() == 4);

    BufferIterator i = buffer.begin();
    assert(*i == 'a');
    i += 6;
    assert(buffer.line_and_column_at(i) == BufferCoord{0 COMMA 6});
    i += 1;
    assert(buffer.line_and_column_at(i) == BufferCoord{1 COMMA 0});
    --i;
    assert(buffer.line_and_column_at(i) == BufferCoord{0 COMMA 6});
    ++i;
    assert(buffer.line_and_column_at(i) == BufferCoord{1 COMMA 0});
    buffer.modify(Modification::make_insert(i, "tchou kanaky\n"));
    assert(buffer.line_count() == 5);

    BufferIterator begin = buffer.iterator_at({ 4, 1 });
    BufferIterator end = buffer.iterator_at({ 4, 5 }) + 1;
    String str = buffer.string(begin, end);
    assert(str == "youpi");
}

void test_editor()
{
    Buffer buffer("test", Buffer::Type::Scratch, "test\n\nyoupi\n");
    Editor editor(buffer);

    using namespace std::placeholders;

    editor.select(select_whole_buffer);
    editor.multi_select(std::bind(select_all_matches, _1, "\n\\h*"));
    for (auto& sel : editor.selections())
    {
        assert(*sel.begin() == '\n');
        editor.buffer().modify(Modification::make_erase(sel.begin(), sel.end()));
    }
}

void test_string()
{
   assert(int_to_str(124)  == "124");
   assert(int_to_str(-129) == "-129");
   assert(int_to_str(0)    == "0");

   assert(String("youpi ") + "matin" == "youpi matin");

   std::vector<String> splited = split("youpi:matin::tchou", ':');
   assert(splited[0] == "youpi");
   assert(splited[1] == "matin");
   assert(splited[2] == "");
   assert(splited[3] == "tchou");
}

void run_unit_tests()
{
    test_string();
    test_buffer();
    test_editor();
}
<commit_msg>Correct multi select unit test<commit_after>#include "buffer.hh"
#include "assert.hh"
#include "editor.hh"
#include "selectors.hh"

using namespace Kakoune;

void test_buffer()
{
    Buffer buffer("test", Buffer::Type::Scratch, "allo ?\nmais que fais la police\n hein ?\n youpi\n");
    assert(buffer.line_count() == 4);

    BufferIterator i = buffer.begin();
    assert(*i == 'a');
    i += 6;
    assert(buffer.line_and_column_at(i) == BufferCoord{0 COMMA 6});
    i += 1;
    assert(buffer.line_and_column_at(i) == BufferCoord{1 COMMA 0});
    --i;
    assert(buffer.line_and_column_at(i) == BufferCoord{0 COMMA 6});
    ++i;
    assert(buffer.line_and_column_at(i) == BufferCoord{1 COMMA 0});
    buffer.modify(Modification::make_insert(i, "tchou kanaky\n"));
    assert(buffer.line_count() == 5);

    BufferIterator begin = buffer.iterator_at({ 4, 1 });
    BufferIterator end = buffer.iterator_at({ 4, 5 }) + 1;
    String str = buffer.string(begin, end);
    assert(str == "youpi");
}

void test_editor()
{
    Buffer buffer("test", Buffer::Type::Scratch, "test\n\nyoupi\n");
    Editor editor(buffer);

    using namespace std::placeholders;

    editor.select(select_whole_buffer);
    editor.multi_select(std::bind(select_all_matches, _1, "\\n\\h*"));
    for (auto& sel : editor.selections())
    {
        assert(*sel.begin() == '\n');
        editor.buffer().modify(Modification::make_erase(sel.begin(), sel.end()));
    }
}

void test_string()
{
   assert(int_to_str(124)  == "124");
   assert(int_to_str(-129) == "-129");
   assert(int_to_str(0)    == "0");

   assert(String("youpi ") + "matin" == "youpi matin");

   std::vector<String> splited = split("youpi:matin::tchou", ':');
   assert(splited[0] == "youpi");
   assert(splited[1] == "matin");
   assert(splited[2] == "");
   assert(splited[3] == "tchou");
}

void run_unit_tests()
{
    test_string();
    test_buffer();
    test_editor();
}
<|endoftext|>