<commit_before>// Copyright (c) 2011 Cloudera, Inc. All rights reserved.

#include <boost/thread/thread.hpp>
#include <protocol/TBinaryProtocol.h>
#include <transport/TServerSocket.h>
#include <transport/TTransportUtils.h>
#include <gtest/gtest.h>

#include "common/logging.h"
#include "codegen/llvm-codegen.h"
#include "runtime/row-batch.h"
#include "runtime/data-stream-mgr.h"
#include "runtime/data-stream-sender.h"
#include "runtime/data-stream-recvr.h"
#include "runtime/descriptors.h"
#include "testutil/in-process-query-executor.h"
#include "testutil/test-exec-env.h"
#include "util/cpu-info.h"
#include "util/disk-info.h"
#include "util/debug-util.h"
#include "util/thrift-server.h"
#include "gen-cpp/ImpalaInternalService.h"
#include "gen-cpp/Types_types.h"
#include "gen-cpp/Descriptors_types.h"

using namespace std;
using namespace tr1;
using namespace boost;

using namespace apache::thrift;
using namespace apache::thrift::protocol;

DECLARE_int32(port);

namespace impala {

class ImpalaTestBackend : public ImpalaInternalServiceIf {
 public:
  ImpalaTestBackend(DataStreamMgr* stream_mgr): mgr_(stream_mgr) {}
  virtual ~ImpalaTestBackend() {}

  virtual void ExecPlanFragment(
      TExecPlanFragmentResult& return_val, const TExecPlanFragmentParams& params) {}

  virtual void ReportExecStatus(
      TReportExecStatusResult& return_val, const TReportExecStatusParams& params) {}

  virtual void CancelPlanFragment(
      TCancelPlanFragmentResult& return_val, const TCancelPlanFragmentParams& params) {}

  virtual void TransmitData(
      TTransmitDataResult& return_val, const TTransmitDataParams& params) {
    if (!params.eos) {
      mgr_->AddData(params.dest_fragment_id, params.dest_node_id, params.row_batch)
          .SetTStatus(&return_val);
    } else {
      mgr_->CloseSender(params.dest_fragment_id, params.dest_node_id)
          .SetTStatus(&return_val);
    }
  }

 private:
  DataStreamMgr* mgr_;
};

class DataStreamTest : public testing::Test {
 protected:
  DataStreamTest(): next_val_(0) {}

  virtual void SetUp() {
    fragment_id_.lo = 0;
    fragment_id_.hi = 0;
    stream_mgr_ = new DataStreamMgr();
    sink_.destNodeId = DEST_NODE_ID;
    dest_.push_back(THostPort());
    dest_.back().ipaddress = "127.0.0.1";
    // Need a unique port since backend servers are never stopped
    dest_.back().port = FLAGS_port++;
    CreateRowDesc();
    CreateRowBatch();
    StartBackend();
  }

  static const PlanNodeId DEST_NODE_ID = 1;
  static const int BATCH_CAPACITY = 100;  // rows
  static const int PER_ROW_DATA = 8;
  static const int TOTAL_DATA_SIZE = 8 * 1024;
  static const int NUM_BATCHES = TOTAL_DATA_SIZE / BATCH_CAPACITY / PER_ROW_DATA;

  ObjectPool obj_pool_;
  DescriptorTbl* desc_tbl_;
  const RowDescriptor* row_desc_;
  TUniqueId fragment_id_;
  string stmt_;

  // RowBatch generation
  scoped_ptr<RowBatch> batch_;
  int next_val_;
  int64_t* tuple_mem_;

  // receiving node
  DataStreamMgr* stream_mgr_;
  DataStreamRecvr* stream_recvr_;
  thread recvr_thread_;
  Status recvr_status_;
  ThriftServer* server_;

  // sending node(s)
  TDataStreamSink sink_;
  vector<THostPort> dest_;

  struct SenderInfo {
    thread* thread_handle;
    Status status;
    int num_bytes_sent;

    SenderInfo(): thread_handle(NULL), num_bytes_sent(0) {}
  };

  vector<SenderInfo> sender_info_;

  // RowDescriptor to mimic "select bigint_col from alltypesagg", except the slot
  // isn't nullable
  void CreateRowDesc() {
    // create DescriptorTbl
    TTupleDescriptor tuple_desc;
    tuple_desc.__set_id(0);
    tuple_desc.__set_byteSize(8);
    tuple_desc.__set_numNullBytes(0);
    TDescriptorTable thrift_desc_tbl;
    thrift_desc_tbl.tupleDescriptors.push_back(tuple_desc);
    TSlotDescriptor slot_desc;
    slot_desc.__set_id(0);
    slot_desc.__set_parent(0);
    slot_desc.__set_slotType(TPrimitiveType::BIGINT);
    slot_desc.__set_columnPos(0);
    slot_desc.__set_byteOffset(0);
    slot_desc.__set_nullIndicatorByte(-1);
    slot_desc.__set_nullIndicatorBit(-1);
    slot_desc.__set_slotIdx(0);
    slot_desc.__set_isMaterialized(true);
    thrift_desc_tbl.slotDescriptors.push_back(slot_desc);
    EXPECT_TRUE(DescriptorTbl::Create(&obj_pool_, thrift_desc_tbl, &desc_tbl_).ok());

    vector<TTupleId> row_tids;
    row_tids.push_back(0);
    vector<bool> nullable_tuples;
    nullable_tuples.push_back(false);
    row_desc_ = obj_pool_.Add(new RowDescriptor(*desc_tbl_, row_tids, nullable_tuples));
  }

  // Create batch_, but don't fill it with data yet. Assumes we created row_desc_.
  RowBatch* CreateRowBatch() {
    RowBatch* batch = new RowBatch(*row_desc_, BATCH_CAPACITY);
    int64_t* tuple_mem = reinterpret_cast<int64_t*>(
        batch->tuple_data_pool()->Allocate(BATCH_CAPACITY * 8));
    bzero(tuple_mem, BATCH_CAPACITY * 8);
    for (int i = 0; i < BATCH_CAPACITY; ++i) {
      int idx = batch->AddRow();
      TupleRow* row = batch->GetRow(idx);
      row->SetTuple(0, reinterpret_cast<Tuple*>(&tuple_mem[i]));
      batch->CommitLastRow();
    }
    return batch;
  }

 void GetNextBatch(RowBatch* batch, int* next_val) {
    for (int i = 0; i < BATCH_CAPACITY; ++i) {
      TupleRow* row = batch->GetRow(i);
      int64_t* val = reinterpret_cast<int64_t*>(row->GetTuple(0)->GetSlot(0));
      *val = (*next_val)++;
    }
  }

  // Start receiver (expecting given number of senders) in separate thread.
  void StartReceiver(int num_senders, int buffer_size) {
    stream_recvr_ =
        stream_mgr_->CreateRecvr(*row_desc_, fragment_id_, DEST_NODE_ID, num_senders,
                                 buffer_size);
    recvr_thread_ = thread(&DataStreamTest::ReadStream, this, num_senders);
  }

  void JoinReceiver() {
    recvr_thread_.join();
  }

  // Deplete stream and print batches
  void ReadStream(int num_senders) {
    RowBatch* batch;
    VLOG_QUERY <<  "start reading";
    bool is_cancelled;
    multiset<int64_t> data_values;
    while ((batch = stream_recvr_->GetBatch(&is_cancelled)) != NULL && !is_cancelled) {
      VLOG_QUERY << "read batch #rows=" << (batch != NULL ? batch->num_rows() : 0);
      for (int i = 0; i < batch->num_rows(); ++i) {
        TupleRow* row = batch->GetRow(i);
        data_values.insert(*static_cast<int64_t*>(row->GetTuple(0)->GetSlot(0)));
      }
      usleep(100000);  // slow down receiver to exercise buffering logic
    }
    if (is_cancelled) VLOG_QUERY << "reader is cancelled";
    recvr_status_ = (is_cancelled ? Status::CANCELLED : Status::OK);

    if (!is_cancelled) {
      // check contents of batches
      int64_t expected_val;
      EXPECT_EQ(data_values.size(), NUM_BATCHES * BATCH_CAPACITY * num_senders);
      int j = 0;
      for (multiset<int64_t>::iterator i = data_values.begin(); i != data_values.end();
           ++i, ++j) {
        expected_val = j / num_senders;
        EXPECT_EQ(expected_val, *i);
      }
    }

    VLOG_QUERY << "done reading";
  }

  // Start backend in separate thread.
  void StartBackend() {
    shared_ptr<ImpalaTestBackend> handler(new ImpalaTestBackend(stream_mgr_));
    shared_ptr<TProcessor> processor(new ImpalaInternalServiceProcessor(handler));

    server_ = new ThriftServer("DataStreamTest backend", processor, dest_.back().port);
    server_->Start();
  }

  void StopBackend() {
    delete server_;
  }

  void StartSender(int channel_buffer_size = 1024) {
    int num_senders = sender_info_.size();
    sender_info_.push_back(SenderInfo());
    SenderInfo& info = sender_info_.back();
    info.thread_handle =
        new thread(&DataStreamTest::Sender, this, num_senders, channel_buffer_size);
  }

  void JoinSenders() {
    for (int i = 0; i < sender_info_.size(); ++i) {
      sender_info_[i].thread_handle->join();
    }
  }

  void Sender(int sender_num, int channel_buffer_size) {
    VLOG_QUERY << "create sender " << sender_num;
    DataStreamSender sender(
        *row_desc_, fragment_id_, sink_, dest_, channel_buffer_size);
    EXPECT_TRUE(sender.Init(NULL).ok());
    scoped_ptr<RowBatch> batch(CreateRowBatch());
    SenderInfo& info = sender_info_[sender_num];
    int next_val = 0;
    for (int i = 0; i < NUM_BATCHES; ++i) {
      GetNextBatch(batch.get(), &next_val);
      VLOG_QUERY << "sender " << sender_num << ": #rows=" << batch->num_rows();
      info.status = sender.Send(NULL, batch.get());
      if (!info.status.ok()) break;
    }
    VLOG_QUERY << "closing sender" << sender_num;
    info.status = sender.Close(NULL);
    info.num_bytes_sent = sender.GetNumDataBytesSent();
  }
};

TEST_F(DataStreamTest, SingleSenderSmallBuffer) {
  VLOG_QUERY << "start receiver\n";
  StartReceiver(1, 1024);
  VLOG_QUERY << "start sender\n";
  StartSender();
  VLOG_QUERY << "join senders\n";
  JoinSenders();
  EXPECT_TRUE(sender_info_[0].status.ok());
  EXPECT_GT(sender_info_[0].num_bytes_sent, 0);
  VLOG_QUERY << "join receiver\n";
  JoinReceiver();
  VLOG_QUERY << "stop backend\n";
  StopBackend();
}

TEST_F(DataStreamTest, SingleSenderLargeBuffer) {
  VLOG_QUERY << "start receiver\n";
  StartReceiver(1, 1024 * 1024);
  VLOG_QUERY << "start sender\n";
  StartSender();
  VLOG_QUERY << "join senders\n";
  JoinSenders();
  EXPECT_TRUE(sender_info_[0].status.ok());
  EXPECT_GT(sender_info_[0].num_bytes_sent, 0);
  VLOG_QUERY << "join receiver\n";
  JoinReceiver();
  VLOG_QUERY << "stop backend\n";
  StopBackend();
}

TEST_F(DataStreamTest, MultipleSendersSmallBuffer) {
  VLOG_QUERY << "start receiver\n";
  StartReceiver(4, 4 * 1024);
  VLOG_QUERY << "start senders\n";
  StartSender();
  StartSender();
  StartSender();
  StartSender();
  VLOG_QUERY << "join senders\n";
  JoinSenders();
  EXPECT_TRUE(sender_info_[0].status.ok());
  EXPECT_GT(sender_info_[0].num_bytes_sent, 0);
  EXPECT_TRUE(sender_info_[1].status.ok());
  EXPECT_GT(sender_info_[1].num_bytes_sent, 0);
  EXPECT_TRUE(sender_info_[2].status.ok());
  EXPECT_GT(sender_info_[2].num_bytes_sent, 0);
  EXPECT_TRUE(sender_info_[3].status.ok());
  EXPECT_GT(sender_info_[3].num_bytes_sent, 0);
  VLOG_QUERY << "join receiver\n";
  JoinReceiver();
  VLOG_QUERY << "stop backend\n";
  StopBackend();
}

TEST_F(DataStreamTest, MultipleSendersLargeBuffer) {
  VLOG_QUERY << "start receiver\n";
  StartReceiver(4, 4 * 1024 * 1024);
  VLOG_QUERY << "start senders\n";
  StartSender();
  StartSender();
  StartSender();
  StartSender();
  VLOG_QUERY << "join senders\n";
  JoinSenders();
  EXPECT_TRUE(sender_info_[0].status.ok());
  EXPECT_GT(sender_info_[0].num_bytes_sent, 0);
  EXPECT_TRUE(sender_info_[1].status.ok());
  EXPECT_GT(sender_info_[1].num_bytes_sent, 0);
  EXPECT_TRUE(sender_info_[2].status.ok());
  EXPECT_GT(sender_info_[2].num_bytes_sent, 0);
  EXPECT_TRUE(sender_info_[3].status.ok());
  EXPECT_GT(sender_info_[3].num_bytes_sent, 0);
  VLOG_QUERY << "join receiver\n";
  JoinReceiver();
  VLOG_QUERY << "stop backend\n";
  StopBackend();
}

TEST_F(DataStreamTest, UnknownSenderSmallResult) {
  // starting a sender w/o a corresponding receiver should result in an error
  // on the sending side
  // case 1: entire query result fits in single buffer, close() returns error status
  StartSender(TOTAL_DATA_SIZE + 1024);
  JoinSenders();
  EXPECT_FALSE(sender_info_[0].status.ok());
  EXPECT_EQ(sender_info_[0].num_bytes_sent, 0);
}

TEST_F(DataStreamTest, UnknownSenderLargeResult) {
  // case 2: query result requires multiple buffers, send() returns error status
  StartSender();
  JoinSenders();
  EXPECT_FALSE(sender_info_[0].status.ok());
  EXPECT_EQ(sender_info_[0].num_bytes_sent, 0);
}

TEST_F(DataStreamTest, Cancel) {
  StartReceiver(1, 1024);
  stream_mgr_->Cancel(fragment_id_);
  JoinReceiver();
  EXPECT_TRUE(recvr_status_.IsCancelled());
}

// TODO: more tests:
// - TEST_F(DataStreamTest, SingleSenderMultipleReceivers)
// - TEST_F(DataStreamTest, MultipleSendersMultipleReceivers)
// - test case for transmission error in last batch
// - receivers getting created concurrently

}

int main(int argc, char **argv) {
  google::InitGoogleLogging(argv[0]);
  ::testing::InitGoogleTest(&argc, argv);
  impala::CpuInfo::Init();
  impala::DiskInfo::Init();
  impala::LlvmCodeGen::InitializeLlvm();
  return RUN_ALL_TESTS();
}
<commit_msg>Fix data-stream-test bug<commit_after>// Copyright (c) 2011 Cloudera, Inc. All rights reserved.

#include <boost/thread/thread.hpp>
#include <protocol/TBinaryProtocol.h>
#include <transport/TServerSocket.h>
#include <transport/TTransportUtils.h>
#include <gtest/gtest.h>

#include "common/logging.h"
#include "codegen/llvm-codegen.h"
#include "runtime/row-batch.h"
#include "runtime/data-stream-mgr.h"
#include "runtime/data-stream-sender.h"
#include "runtime/data-stream-recvr.h"
#include "runtime/descriptors.h"
#include "testutil/in-process-query-executor.h"
#include "testutil/test-exec-env.h"
#include "util/cpu-info.h"
#include "util/disk-info.h"
#include "util/debug-util.h"
#include "util/thrift-server.h"
#include "gen-cpp/ImpalaInternalService.h"
#include "gen-cpp/Types_types.h"
#include "gen-cpp/Descriptors_types.h"

using namespace std;
using namespace tr1;
using namespace boost;

using namespace apache::thrift;
using namespace apache::thrift::protocol;

DECLARE_int32(port);

namespace impala {

class ImpalaTestBackend : public ImpalaInternalServiceIf {
 public:
  ImpalaTestBackend(DataStreamMgr* stream_mgr): mgr_(stream_mgr) {}
  virtual ~ImpalaTestBackend() {}

  virtual void ExecPlanFragment(
      TExecPlanFragmentResult& return_val, const TExecPlanFragmentParams& params) {}

  virtual void ReportExecStatus(
      TReportExecStatusResult& return_val, const TReportExecStatusParams& params) {}

  virtual void CancelPlanFragment(
      TCancelPlanFragmentResult& return_val, const TCancelPlanFragmentParams& params) {}

  virtual void TransmitData(
      TTransmitDataResult& return_val, const TTransmitDataParams& params) {
    if (!params.eos) {
      mgr_->AddData(params.dest_fragment_id, params.dest_node_id, params.row_batch)
          .SetTStatus(&return_val);
    } else {
      mgr_->CloseSender(params.dest_fragment_id, params.dest_node_id)
          .SetTStatus(&return_val);
    }
  }

 private:
  DataStreamMgr* mgr_;
};

class DataStreamTest : public testing::Test {
 protected:
  DataStreamTest(): next_val_(0) {}

  virtual void SetUp() {
    fragment_id_.lo = 0;
    fragment_id_.hi = 0;
    stream_mgr_ = new DataStreamMgr();
    sink_.destNodeId = DEST_NODE_ID;
    dest_.push_back(THostPort());
    dest_.back().ipaddress = "127.0.0.1";
    // Need a unique port since backend servers are never stopped
    dest_.back().port = FLAGS_port++;
    // Ensure that individual sender info addresses don't change
    sender_info_.reserve(MAX_SENDERS);
    CreateRowDesc();
    CreateRowBatch();
    StartBackend();
  }

  // We reserve contiguous memory for senders in SetUp. If a test uses more
  // senders, a DCHECK will fail and you should increase this value.
  static const int MAX_SENDERS = 16;
  static const PlanNodeId DEST_NODE_ID = 1;
  static const int BATCH_CAPACITY = 100;  // rows
  static const int PER_ROW_DATA = 8;
  static const int TOTAL_DATA_SIZE = 8 * 1024;
  static const int NUM_BATCHES = TOTAL_DATA_SIZE / BATCH_CAPACITY / PER_ROW_DATA;

  ObjectPool obj_pool_;
  DescriptorTbl* desc_tbl_;
  const RowDescriptor* row_desc_;
  TUniqueId fragment_id_;
  string stmt_;

  // RowBatch generation
  scoped_ptr<RowBatch> batch_;
  int next_val_;
  int64_t* tuple_mem_;

  // receiving node
  DataStreamMgr* stream_mgr_;
  DataStreamRecvr* stream_recvr_;
  thread recvr_thread_;
  Status recvr_status_;
  ThriftServer* server_;

  // sending node(s)
  TDataStreamSink sink_;
  vector<THostPort> dest_;

  struct SenderInfo {
    thread* thread_handle;
    Status status;
    int num_bytes_sent;

    SenderInfo(): thread_handle(NULL), num_bytes_sent(0) {}
  };

  vector<SenderInfo> sender_info_;

  // RowDescriptor to mimic "select bigint_col from alltypesagg", except the slot
  // isn't nullable
  void CreateRowDesc() {
    // create DescriptorTbl
    TTupleDescriptor tuple_desc;
    tuple_desc.__set_id(0);
    tuple_desc.__set_byteSize(8);
    tuple_desc.__set_numNullBytes(0);
    TDescriptorTable thrift_desc_tbl;
    thrift_desc_tbl.tupleDescriptors.push_back(tuple_desc);
    TSlotDescriptor slot_desc;
    slot_desc.__set_id(0);
    slot_desc.__set_parent(0);
    slot_desc.__set_slotType(TPrimitiveType::BIGINT);
    slot_desc.__set_columnPos(0);
    slot_desc.__set_byteOffset(0);
    slot_desc.__set_nullIndicatorByte(-1);
    slot_desc.__set_nullIndicatorBit(-1);
    slot_desc.__set_slotIdx(0);
    slot_desc.__set_isMaterialized(true);
    thrift_desc_tbl.slotDescriptors.push_back(slot_desc);
    EXPECT_TRUE(DescriptorTbl::Create(&obj_pool_, thrift_desc_tbl, &desc_tbl_).ok());

    vector<TTupleId> row_tids;
    row_tids.push_back(0);
    vector<bool> nullable_tuples;
    nullable_tuples.push_back(false);
    row_desc_ = obj_pool_.Add(new RowDescriptor(*desc_tbl_, row_tids, nullable_tuples));
  }

  // Create batch_, but don't fill it with data yet. Assumes we created row_desc_.
  RowBatch* CreateRowBatch() {
    RowBatch* batch = new RowBatch(*row_desc_, BATCH_CAPACITY);
    int64_t* tuple_mem = reinterpret_cast<int64_t*>(
        batch->tuple_data_pool()->Allocate(BATCH_CAPACITY * 8));
    bzero(tuple_mem, BATCH_CAPACITY * 8);
    for (int i = 0; i < BATCH_CAPACITY; ++i) {
      int idx = batch->AddRow();
      TupleRow* row = batch->GetRow(idx);
      row->SetTuple(0, reinterpret_cast<Tuple*>(&tuple_mem[i]));
      batch->CommitLastRow();
    }
    return batch;
  }

 void GetNextBatch(RowBatch* batch, int* next_val) {
    for (int i = 0; i < BATCH_CAPACITY; ++i) {
      TupleRow* row = batch->GetRow(i);
      int64_t* val = reinterpret_cast<int64_t*>(row->GetTuple(0)->GetSlot(0));
      *val = (*next_val)++;
    }
  }

  // Start receiver (expecting given number of senders) in separate thread.
  void StartReceiver(int num_senders, int buffer_size) {
    stream_recvr_ =
        stream_mgr_->CreateRecvr(*row_desc_, fragment_id_, DEST_NODE_ID, num_senders,
                                 buffer_size);
    recvr_thread_ = thread(&DataStreamTest::ReadStream, this, num_senders);
  }

  void JoinReceiver() {
    recvr_thread_.join();
  }

  // Deplete stream and print batches
  void ReadStream(int num_senders) {
    RowBatch* batch;
    VLOG_QUERY <<  "start reading";
    bool is_cancelled;
    multiset<int64_t> data_values;
    while ((batch = stream_recvr_->GetBatch(&is_cancelled)) != NULL && !is_cancelled) {
      VLOG_QUERY << "read batch #rows=" << (batch != NULL ? batch->num_rows() : 0);
      for (int i = 0; i < batch->num_rows(); ++i) {
        TupleRow* row = batch->GetRow(i);
        data_values.insert(*static_cast<int64_t*>(row->GetTuple(0)->GetSlot(0)));
      }
      usleep(100000);  // slow down receiver to exercise buffering logic
    }
    if (is_cancelled) VLOG_QUERY << "reader is cancelled";
    recvr_status_ = (is_cancelled ? Status::CANCELLED : Status::OK);

    if (!is_cancelled) {
      // check contents of batches
      int64_t expected_val;
      EXPECT_EQ(data_values.size(), NUM_BATCHES * BATCH_CAPACITY * num_senders);
      int j = 0;
      for (multiset<int64_t>::iterator i = data_values.begin(); i != data_values.end();
           ++i, ++j) {
        expected_val = j / num_senders;
        EXPECT_EQ(expected_val, *i);
      }
    }

    VLOG_QUERY << "done reading";
  }

  // Start backend in separate thread.
  void StartBackend() {
    shared_ptr<ImpalaTestBackend> handler(new ImpalaTestBackend(stream_mgr_));
    shared_ptr<TProcessor> processor(new ImpalaInternalServiceProcessor(handler));

    server_ = new ThriftServer("DataStreamTest backend", processor, dest_.back().port);
    server_->Start();
  }

  void StopBackend() {
    delete server_;
  }

  void StartSender(int channel_buffer_size = 1024) {
    int num_senders = sender_info_.size();
    DCHECK_LT(num_senders, MAX_SENDERS);
    sender_info_.push_back(SenderInfo());
    SenderInfo& info = sender_info_.back();
    info.thread_handle =
        new thread(&DataStreamTest::Sender, this, num_senders, channel_buffer_size);
  }

  void JoinSenders() {
    for (int i = 0; i < sender_info_.size(); ++i) {
      sender_info_[i].thread_handle->join();
    }
  }

  void Sender(int sender_num, int channel_buffer_size) {
    VLOG_QUERY << "create sender " << sender_num;
    DataStreamSender sender(
        *row_desc_, fragment_id_, sink_, dest_, channel_buffer_size);
    EXPECT_TRUE(sender.Init(NULL).ok());
    scoped_ptr<RowBatch> batch(CreateRowBatch());
    SenderInfo& info = sender_info_[sender_num];
    int next_val = 0;
    for (int i = 0; i < NUM_BATCHES; ++i) {
      GetNextBatch(batch.get(), &next_val);
      VLOG_QUERY << "sender " << sender_num << ": #rows=" << batch->num_rows();
      info.status = sender.Send(NULL, batch.get());
      if (!info.status.ok()) break;
    }
    VLOG_QUERY << "closing sender" << sender_num;
    info.status = sender.Close(NULL);
    info.num_bytes_sent = sender.GetNumDataBytesSent();
  }
};

TEST_F(DataStreamTest, SingleSenderSmallBuffer) {
  VLOG_QUERY << "start receiver\n";
  StartReceiver(1, 1024);
  VLOG_QUERY << "start sender\n";
  StartSender();
  VLOG_QUERY << "join senders\n";
  JoinSenders();
  EXPECT_TRUE(sender_info_[0].status.ok());
  EXPECT_GT(sender_info_[0].num_bytes_sent, 0);
  VLOG_QUERY << "join receiver\n";
  JoinReceiver();
  VLOG_QUERY << "stop backend\n";
  StopBackend();
}

TEST_F(DataStreamTest, SingleSenderLargeBuffer) {
  VLOG_QUERY << "start receiver\n";
  StartReceiver(1, 1024 * 1024);
  VLOG_QUERY << "start sender\n";
  StartSender();
  VLOG_QUERY << "join senders\n";
  JoinSenders();
  EXPECT_TRUE(sender_info_[0].status.ok());
  EXPECT_GT(sender_info_[0].num_bytes_sent, 0);
  VLOG_QUERY << "join receiver\n";
  JoinReceiver();
  VLOG_QUERY << "stop backend\n";
  StopBackend();
}

TEST_F(DataStreamTest, MultipleSendersSmallBuffer) {
  VLOG_QUERY << "start receiver\n";
  StartReceiver(4, 4 * 1024);
  VLOG_QUERY << "start senders\n";
  StartSender();
  StartSender();
  StartSender();
  StartSender();
  VLOG_QUERY << "join senders\n";
  JoinSenders();
  EXPECT_TRUE(sender_info_[0].status.ok());
  EXPECT_GT(sender_info_[0].num_bytes_sent, 0);
  EXPECT_TRUE(sender_info_[1].status.ok());
  EXPECT_GT(sender_info_[1].num_bytes_sent, 0);
  EXPECT_TRUE(sender_info_[2].status.ok());
  EXPECT_GT(sender_info_[2].num_bytes_sent, 0);
  EXPECT_TRUE(sender_info_[3].status.ok());
  EXPECT_GT(sender_info_[3].num_bytes_sent, 0);
  VLOG_QUERY << "join receiver\n";
  JoinReceiver();
  VLOG_QUERY << "stop backend\n";
  StopBackend();
}

TEST_F(DataStreamTest, MultipleSendersLargeBuffer) {
  VLOG_QUERY << "start receiver\n";
  StartReceiver(4, 4 * 1024 * 1024);
  VLOG_QUERY << "start senders\n";
  StartSender();
  StartSender();
  StartSender();
  StartSender();
  VLOG_QUERY << "join senders\n";
  JoinSenders();
  EXPECT_TRUE(sender_info_[0].status.ok());
  EXPECT_GT(sender_info_[0].num_bytes_sent, 0);
  EXPECT_TRUE(sender_info_[1].status.ok());
  EXPECT_GT(sender_info_[1].num_bytes_sent, 0);
  EXPECT_TRUE(sender_info_[2].status.ok());
  EXPECT_GT(sender_info_[2].num_bytes_sent, 0);
  EXPECT_TRUE(sender_info_[3].status.ok());
  EXPECT_GT(sender_info_[3].num_bytes_sent, 0);
  VLOG_QUERY << "join receiver\n";
  JoinReceiver();
  VLOG_QUERY << "stop backend\n";
  StopBackend();
}

TEST_F(DataStreamTest, UnknownSenderSmallResult) {
  // starting a sender w/o a corresponding receiver should result in an error
  // on the sending side
  // case 1: entire query result fits in single buffer, close() returns error status
  StartSender(TOTAL_DATA_SIZE + 1024);
  JoinSenders();
  EXPECT_FALSE(sender_info_[0].status.ok());
  EXPECT_EQ(sender_info_[0].num_bytes_sent, 0);
}

TEST_F(DataStreamTest, UnknownSenderLargeResult) {
  // case 2: query result requires multiple buffers, send() returns error status
  StartSender();
  JoinSenders();
  EXPECT_FALSE(sender_info_[0].status.ok());
  EXPECT_EQ(sender_info_[0].num_bytes_sent, 0);
}

TEST_F(DataStreamTest, Cancel) {
  StartReceiver(1, 1024);
  stream_mgr_->Cancel(fragment_id_);
  JoinReceiver();
  EXPECT_TRUE(recvr_status_.IsCancelled());
}

// TODO: more tests:
// - TEST_F(DataStreamTest, SingleSenderMultipleReceivers)
// - TEST_F(DataStreamTest, MultipleSendersMultipleReceivers)
// - test case for transmission error in last batch
// - receivers getting created concurrently

}

int main(int argc, char **argv) {
  google::InitGoogleLogging(argv[0]);
  ::testing::InitGoogleTest(&argc, argv);
  impala::CpuInfo::Init();
  impala::DiskInfo::Init();
  impala::LlvmCodeGen::InitializeLlvm();
  return RUN_ALL_TESTS();
}
<|endoftext|>


<commit_before>#include "context.h"

#include <QAbstractEventDispatcher>
#include <QDebug>

#warning todo this needs to be a singleton as multiple contexts dont make sense and it makes it eaier to use from qml

static void sink_cb(pa_context *context, const pa_sink_info *info, int eol, void *data)
{
    Q_ASSERT(data);
    ((Context *)data)->sinkCallback(context, info, eol);
}

static void sink_input_callback(pa_context *context, const pa_sink_input_info *info, int eol, void *data)
{
    Q_ASSERT(data);
    ((Context *)data)->sinkInputCallback(context, info, eol);
}

static void client_cb(pa_context *context, const pa_client_info *info, int eol, void *data)
{
    Q_ASSERT(data);
    ((Context *)data)->clientCallback(context, info, eol);
}

static void context_state_callback(pa_context *context, void *data)
{
    Q_ASSERT(data);
    ((Context *)data)->contextStateCallback(context);
}

#warning fix varnames
static void subscribe_cb(pa_context *context, pa_subscription_event_type_t type, uint32_t index, void *data)
{
    Q_ASSERT(data);
    ((Context *)data)->subscribeCallback(context, type, index);
}

// --------------------------

Context::Context(QObject *parent)
    : QObject(parent)
    , m_context(nullptr)
    , m_mainloop(nullptr)
{
    // We require a glib event loop
    if (!QByteArray(QAbstractEventDispatcher::instance()->metaObject()->className()).contains("EventDispatcherGlib")) {
        qDebug() << "Disabling PulseAudio integration for lack of GLib event loop";
    }

    connectToDaemon();
}

Context::~Context()
{
    pa_context_unref(m_context);
    pa_glib_mainloop_free(m_mainloop);
}

void Context::subscribeCallback(pa_context *context, pa_subscription_event_type_t type, uint32_t index)
{
#warning fixme
//    Q_ASSERT(c == s_context);

    switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
#warning fixme
        case PA_SUBSCRIPTION_EVENT_SINK:
            if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
                m_sinks.take(index)->deleteLater();
                emit sinkRemoved(index);
            } else {
                pa_operation *o;
                if (!(o = pa_context_get_sink_info_by_index(context, index, sink_cb, this))) {
                    qWarning() << "pa_context_get_sink_info_by_index() failed";
                    return;
                }
                pa_operation_unref(o);
            }
            break;

//        case PA_SUBSCRIPTION_EVENT_SOURCE:
//            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
//                if (s_mixers.contains(KMIXPA_CAPTURE))
//                    s_mixers[KMIXPA_CAPTURE]->removeWidget(index);
//            } else {
//                pa_operation *o;
//                if (!(o = pa_context_get_source_info_by_index(c, index, source_cb, NULL))) {
//                    qWarning() << "pa_context_get_source_info_by_index() failed";
//                    return;
//                }
//                pa_operation_unref(o);
//            }
//            break;

        case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
            if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
                int listIndex = -1;
                QList<quint32> list = m_sinkInputs.keys();
                for (int i = 0; i < list.size(); ++i) {
                    if (list.at(i) == index) {
                        listIndex = i;
                        break;
                    }
                }
                Q_ASSERT(listIndex >= 0);
                m_sinkInputs.take(index)->deleteLater();
                emit sinkInputRemoved(listIndex);
            } else {
                qDebug() << "sub sink input info";
                pa_operation *o;
                if (!(o = pa_context_get_sink_input_info(context, index, sink_input_callback, this))) {
                    qWarning() << "pa_context_get_sink_input_info() failed";
                    return;
                }
                pa_operation_unref(o);
            }
            break;

//        case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
//            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
//                if (s_mixers.contains(KMIXPA_APP_CAPTURE))
//                    s_mixers[KMIXPA_APP_CAPTURE]->removeWidget(index);
//            } else {
//                pa_operation *o;
//                if (!(o = pa_context_get_source_output_info(c, index, source_output_cb, NULL))) {
//                    qWarning() << "pa_context_get_sink_input_info() failed";
//                    return;
//                }
//                pa_operation_unref(o);
//            }
//            break;

        case PA_SUBSCRIPTION_EVENT_CLIENT:
            if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
                m_clients.take(index)->deleteLater();
                emit clientsChanged();
            } else {
                pa_operation *o;
                if (!(o = pa_context_get_client_info(context, index, client_cb, this))) {
                    qWarning() << "pa_context_get_client_info() failed";
                    return;
                }
                pa_operation_unref(o);
            }
            break;

    }
    qDebug() << "return !!!!!";
}

void Context::contextStateCallback(pa_context *c)
{
    qDebug() << "state callback";
    pa_context_state_t state = pa_context_get_state(c);
    if (state == PA_CONTEXT_READY) {
        qDebug() << "ready";
        // Attempt to load things up
        pa_operation *o = nullptr;

        // 1. Register for the stream changes (except during probe)
        if (m_context == c) {
#warning fixme
            pa_context_set_subscribe_callback(c, subscribe_cb, this);

            if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t)
                                           (PA_SUBSCRIPTION_MASK_SINK|
                                            PA_SUBSCRIPTION_MASK_SOURCE|
                                            PA_SUBSCRIPTION_MASK_CLIENT|
                                            PA_SUBSCRIPTION_MASK_SINK_INPUT|
                                            PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT), NULL, NULL))) {
                qWarning() << "pa_context_subscribe() failed";
                return;
            }
            pa_operation_unref(o);
        }

#warning fixme
        if (!(o = pa_context_get_sink_info_list(c, sink_cb, this))) {
            qWarning() << "pa_context_get_sink_info_list() failed";
            return;
        }
        pa_operation_unref(o);

//        if (!(o = pa_context_get_source_info_list(c, source_cb, NULL))) {
//            qWarning() << "pa_context_get_source_info_list() failed";
//            return;
//        }
//        pa_operation_unref(o);
//        s_outstandingRequests++;

        if (!(o = pa_context_get_client_info_list(c, client_cb, this))) {
            qWarning() << "pa_context_client_info_list() failed";
            return;
        }
        pa_operation_unref(o);
//        s_outstandingRequests++;

        if (!(o = pa_context_get_sink_input_info_list(c, sink_input_callback, this))) {
            qWarning() << "pa_context_get_sink_input_info_list() failed";
            return;
        }
        pa_operation_unref(o);
//        s_outstandingRequests++;

//        if (!(o = pa_context_get_source_output_info_list(c, source_output_cb, NULL))) {
//            qWarning() << "pa_context_get_source_output_info_list() failed";
//            return;
//        }
//        pa_operation_unref(o);
//        s_outstandingRequests++;

        /* These calls are not always supported */
//        if ((o = pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, NULL))) {
//            pa_operation_unref(o);
//            s_outstandingRequests++;

//            pa_ext_stream_restore_set_subscribe_cb(c, ext_stream_restore_subscribe_cb, NULL);

//            if ((o = pa_ext_stream_restore_subscribe(c, 1, NULL, NULL)))
//                pa_operation_unref(o);
//        } else {
//            qWarning() << "Failed to initialize stream_restore extension: " << pa_strerror(pa_context_errno(m_context));
//        }
    } else if (!PA_CONTEXT_IS_GOOD(state)) {
        qDebug() << "context kaput";
        // If this is our probe phase, exit our context immediately
        if (m_context != c) {
            pa_context_disconnect(c);
        } else {
            // If we're not probing, it means we've been disconnected from our
            // glib context
            pa_context_unref(m_context);
            m_context = nullptr;

#warning fixme
            // This one is not handled above.
//            clients.clear();
        }
    }
}

#warning data changed should possibly only be emitted once eol is >0 to avoid pointless redraws

void Context::sinkCallback(pa_context *context, const pa_sink_info *info, int eol)
{
    if (eol < 0) {
        if (pa_context_errno(context) == PA_ERR_NOENTITY) {
            return;
        }
        return;
    }

    if (eol > 0) {
        return;
    }

    Q_ASSERT(info);

    bool isNew = false;

    Sink *obj = m_sinks.value(info->index, nullptr);
    if (!obj) {
        obj = new Sink;
        isNew = true;
    }
    obj->setInfo(info);
    m_sinks.insert(info->index, obj);

#warning this is bullshit and removal by this method is even more shit
    int listIndex = -1;
    QList<quint32> list = m_sinks.keys();
    for (int i = 0; i < list.size(); ++i) {
        if (list.at(i) == info->index) {
            listIndex = i;
            break;
        }
    }
    Q_ASSERT(listIndex >= 0);

    if (isNew) {
        emit sinkAdded(listIndex);
    } else {
        emit sinkUpdated(listIndex);
    }
}

void Context::clientCallback(pa_context *context, const pa_client_info *info, int eol)
{
    Q_ASSERT(context);

    if (eol < 0) {
        if (pa_context_errno(context) == PA_ERR_NOENTITY) {
            return;
        }
        return;
    }

    if (eol > 0) {
        return;
    }

    Q_ASSERT(info);

    Client *obj = m_clients.value(info->index, nullptr);
    if (!obj)
        obj = new Client;
    obj->setInfo(info);
    m_clients.insert(info->index, obj);

    emit clientsChanged();
}

void Context::sinkInputCallback(pa_context *context, const pa_sink_input_info *info, int eol)
{
    qDebug() << "sink_input_cb";
    Q_ASSERT(context);

    if (eol < 0) {
        if (pa_context_errno(context) == PA_ERR_NOENTITY) {
            return;
        }
        return;
    }

    if (eol > 0) {
        return;
    }

    Q_ASSERT(info);

    bool isNew = false;

    SinkInput *obj = m_sinkInputs.value(info->index, nullptr);
    if (!obj) {
        obj = new SinkInput;
        isNew = true;
    }
    obj->setInfo(info);
    m_sinkInputs.insert(info->index, obj);

    int listIndex = -1;
    QList<quint32> list = m_sinkInputs.keys();
    for (int i = 0; i < list.size(); ++i) {
        if (list.at(i) == info->index) {
            listIndex = i;
            break;
        }
    }
    Q_ASSERT(listIndex >= 0);

    qDebug() << Q_FUNC_INFO << isNew << listIndex;
    if (isNew) {
        emit sinkInputAdded(listIndex);
    } else {
        emit sinkInputUpdated(listIndex);
    }
}

void Context::setSinkVolume(quint32 index, quint32 volume)
{
    qDebug() << Q_FUNC_INFO << index << volume;
    pa_operation *o;
    Sink *sink = m_sinks.value(index, nullptr);
    if (!sink)
        return;
    pa_cvolume newVolume = sink->volume();
    for (int i = 0; i < newVolume.channels; ++i) {
        newVolume.values[i] = volume;
    }
    if (!(o = pa_context_set_sink_volume_by_index(m_context, index, &newVolume, NULL, NULL))) {
        qWarning() <<  "pa_context_set_sink_volume_by_index() failed";
        return;
    }
    pa_operation_unref(o);
}

void Context::setSinkPort(quint32 portIndex)
{
    pa_operation *o;
    if (!(o = pa_context_set_sink_port_by_index(m_context, portIndex, nullptr, nullptr, nullptr))) {
        qWarning() << "pa_context_set_sink_port_by_index failed";
        return;
    }
}

void Context::setSinkInputVolume(quint32 index, quint32 volume)
{
    qDebug() << Q_FUNC_INFO << index << volume;
    pa_operation *o;
    SinkInput *obj = m_sinkInputs.value(index, nullptr);
    if (!obj)
        return;
    pa_cvolume newVolume = obj->volume();
    for (int i = 0; i < newVolume.channels; ++i) {
        newVolume.values[i] = volume;
    }
    if (!(o = pa_context_set_sink_input_volume(m_context, index, &newVolume, NULL, NULL))) {
        qWarning() <<  "pa_context_set_sink_volume_by_index() failed";
        return;
    }
    pa_operation_unref(o);
}

void Context::connectToDaemon()
{
    Q_ASSERT(m_context == nullptr);
    Q_ASSERT(m_mainloop == nullptr);

    qDebug() <<  "Attempting connection to PulseAudio sound daemon";
    m_mainloop = pa_glib_mainloop_new(nullptr);
    pa_mainloop_api *api = pa_glib_mainloop_get_api(m_mainloop);
    Q_ASSERT(api);

    m_context = pa_context_new(api, "QPulse");
    Q_ASSERT(m_context);

    if (pa_context_connect(m_context, NULL, PA_CONTEXT_NOFAIL, 0) < 0) {
        pa_context_unref(m_context);
        m_context = nullptr;
        return;
    }
    pa_context_set_state_callback(m_context, &context_state_callback, this);
}
<commit_msg>do not connect the context when there is no glib event loop<commit_after>#include "context.h"

#include <QAbstractEventDispatcher>
#include <QDebug>

#warning todo this needs to be a singleton as multiple contexts dont make sense and it makes it eaier to use from qml

static void sink_cb(pa_context *context, const pa_sink_info *info, int eol, void *data)
{
    Q_ASSERT(data);
    ((Context *)data)->sinkCallback(context, info, eol);
}

static void sink_input_callback(pa_context *context, const pa_sink_input_info *info, int eol, void *data)
{
    Q_ASSERT(data);
    ((Context *)data)->sinkInputCallback(context, info, eol);
}

static void client_cb(pa_context *context, const pa_client_info *info, int eol, void *data)
{
    Q_ASSERT(data);
    ((Context *)data)->clientCallback(context, info, eol);
}

static void context_state_callback(pa_context *context, void *data)
{
    Q_ASSERT(data);
    ((Context *)data)->contextStateCallback(context);
}

#warning fix varnames
static void subscribe_cb(pa_context *context, pa_subscription_event_type_t type, uint32_t index, void *data)
{
    Q_ASSERT(data);
    ((Context *)data)->subscribeCallback(context, type, index);
}

// --------------------------

Context::Context(QObject *parent)
    : QObject(parent)
    , m_context(nullptr)
    , m_mainloop(nullptr)
{
    // We require a glib event loop
    if (!QByteArray(QAbstractEventDispatcher::instance()->metaObject()->className()).contains("EventDispatcherGlib")) {
        qDebug() << "Disabling PulseAudio integration for lack of GLib event loop";
        return;
    }

    connectToDaemon();
}

Context::~Context()
{
    pa_context_unref(m_context);
    pa_glib_mainloop_free(m_mainloop);
}

void Context::subscribeCallback(pa_context *context, pa_subscription_event_type_t type, uint32_t index)
{
#warning fixme
//    Q_ASSERT(c == s_context);

    switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
#warning fixme
        case PA_SUBSCRIPTION_EVENT_SINK:
            if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
                m_sinks.take(index)->deleteLater();
                emit sinkRemoved(index);
            } else {
                pa_operation *o;
                if (!(o = pa_context_get_sink_info_by_index(context, index, sink_cb, this))) {
                    qWarning() << "pa_context_get_sink_info_by_index() failed";
                    return;
                }
                pa_operation_unref(o);
            }
            break;

//        case PA_SUBSCRIPTION_EVENT_SOURCE:
//            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
//                if (s_mixers.contains(KMIXPA_CAPTURE))
//                    s_mixers[KMIXPA_CAPTURE]->removeWidget(index);
//            } else {
//                pa_operation *o;
//                if (!(o = pa_context_get_source_info_by_index(c, index, source_cb, NULL))) {
//                    qWarning() << "pa_context_get_source_info_by_index() failed";
//                    return;
//                }
//                pa_operation_unref(o);
//            }
//            break;

        case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
            if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
                int listIndex = -1;
                QList<quint32> list = m_sinkInputs.keys();
                for (int i = 0; i < list.size(); ++i) {
                    if (list.at(i) == index) {
                        listIndex = i;
                        break;
                    }
                }
                Q_ASSERT(listIndex >= 0);
                m_sinkInputs.take(index)->deleteLater();
                emit sinkInputRemoved(listIndex);
            } else {
                qDebug() << "sub sink input info";
                pa_operation *o;
                if (!(o = pa_context_get_sink_input_info(context, index, sink_input_callback, this))) {
                    qWarning() << "pa_context_get_sink_input_info() failed";
                    return;
                }
                pa_operation_unref(o);
            }
            break;

//        case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
//            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
//                if (s_mixers.contains(KMIXPA_APP_CAPTURE))
//                    s_mixers[KMIXPA_APP_CAPTURE]->removeWidget(index);
//            } else {
//                pa_operation *o;
//                if (!(o = pa_context_get_source_output_info(c, index, source_output_cb, NULL))) {
//                    qWarning() << "pa_context_get_sink_input_info() failed";
//                    return;
//                }
//                pa_operation_unref(o);
//            }
//            break;

        case PA_SUBSCRIPTION_EVENT_CLIENT:
            if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
                m_clients.take(index)->deleteLater();
                emit clientsChanged();
            } else {
                pa_operation *o;
                if (!(o = pa_context_get_client_info(context, index, client_cb, this))) {
                    qWarning() << "pa_context_get_client_info() failed";
                    return;
                }
                pa_operation_unref(o);
            }
            break;

    }
    qDebug() << "return !!!!!";
}

void Context::contextStateCallback(pa_context *c)
{
    qDebug() << "state callback";
    pa_context_state_t state = pa_context_get_state(c);
    if (state == PA_CONTEXT_READY) {
        qDebug() << "ready";
        // Attempt to load things up
        pa_operation *o = nullptr;

        // 1. Register for the stream changes (except during probe)
        if (m_context == c) {
#warning fixme
            pa_context_set_subscribe_callback(c, subscribe_cb, this);

            if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t)
                                           (PA_SUBSCRIPTION_MASK_SINK|
                                            PA_SUBSCRIPTION_MASK_SOURCE|
                                            PA_SUBSCRIPTION_MASK_CLIENT|
                                            PA_SUBSCRIPTION_MASK_SINK_INPUT|
                                            PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT), NULL, NULL))) {
                qWarning() << "pa_context_subscribe() failed";
                return;
            }
            pa_operation_unref(o);
        }

#warning fixme
        if (!(o = pa_context_get_sink_info_list(c, sink_cb, this))) {
            qWarning() << "pa_context_get_sink_info_list() failed";
            return;
        }
        pa_operation_unref(o);

//        if (!(o = pa_context_get_source_info_list(c, source_cb, NULL))) {
//            qWarning() << "pa_context_get_source_info_list() failed";
//            return;
//        }
//        pa_operation_unref(o);
//        s_outstandingRequests++;

        if (!(o = pa_context_get_client_info_list(c, client_cb, this))) {
            qWarning() << "pa_context_client_info_list() failed";
            return;
        }
        pa_operation_unref(o);
//        s_outstandingRequests++;

        if (!(o = pa_context_get_sink_input_info_list(c, sink_input_callback, this))) {
            qWarning() << "pa_context_get_sink_input_info_list() failed";
            return;
        }
        pa_operation_unref(o);
//        s_outstandingRequests++;

//        if (!(o = pa_context_get_source_output_info_list(c, source_output_cb, NULL))) {
//            qWarning() << "pa_context_get_source_output_info_list() failed";
//            return;
//        }
//        pa_operation_unref(o);
//        s_outstandingRequests++;

        /* These calls are not always supported */
//        if ((o = pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, NULL))) {
//            pa_operation_unref(o);
//            s_outstandingRequests++;

//            pa_ext_stream_restore_set_subscribe_cb(c, ext_stream_restore_subscribe_cb, NULL);

//            if ((o = pa_ext_stream_restore_subscribe(c, 1, NULL, NULL)))
//                pa_operation_unref(o);
//        } else {
//            qWarning() << "Failed to initialize stream_restore extension: " << pa_strerror(pa_context_errno(m_context));
//        }
    } else if (!PA_CONTEXT_IS_GOOD(state)) {
        qDebug() << "context kaput";
        // If this is our probe phase, exit our context immediately
        if (m_context != c) {
            pa_context_disconnect(c);
        } else {
            // If we're not probing, it means we've been disconnected from our
            // glib context
            pa_context_unref(m_context);
            m_context = nullptr;

#warning fixme
            // This one is not handled above.
//            clients.clear();
        }
    }
}

#warning data changed should possibly only be emitted once eol is >0 to avoid pointless redraws

void Context::sinkCallback(pa_context *context, const pa_sink_info *info, int eol)
{
    if (eol < 0) {
        if (pa_context_errno(context) == PA_ERR_NOENTITY) {
            return;
        }
        return;
    }

    if (eol > 0) {
        return;
    }

    Q_ASSERT(info);

    bool isNew = false;

    Sink *obj = m_sinks.value(info->index, nullptr);
    if (!obj) {
        obj = new Sink;
        isNew = true;
    }
    obj->setInfo(info);
    m_sinks.insert(info->index, obj);

#warning this is bullshit and removal by this method is even more shit
    int listIndex = -1;
    QList<quint32> list = m_sinks.keys();
    for (int i = 0; i < list.size(); ++i) {
        if (list.at(i) == info->index) {
            listIndex = i;
            break;
        }
    }
    Q_ASSERT(listIndex >= 0);

    if (isNew) {
        emit sinkAdded(listIndex);
    } else {
        emit sinkUpdated(listIndex);
    }
}

void Context::clientCallback(pa_context *context, const pa_client_info *info, int eol)
{
    Q_ASSERT(context);

    if (eol < 0) {
        if (pa_context_errno(context) == PA_ERR_NOENTITY) {
            return;
        }
        return;
    }

    if (eol > 0) {
        return;
    }

    Q_ASSERT(info);

    Client *obj = m_clients.value(info->index, nullptr);
    if (!obj)
        obj = new Client;
    obj->setInfo(info);
    m_clients.insert(info->index, obj);

    emit clientsChanged();
}

void Context::sinkInputCallback(pa_context *context, const pa_sink_input_info *info, int eol)
{
    qDebug() << "sink_input_cb";
    Q_ASSERT(context);

    if (eol < 0) {
        if (pa_context_errno(context) == PA_ERR_NOENTITY) {
            return;
        }
        return;
    }

    if (eol > 0) {
        return;
    }

    Q_ASSERT(info);

    bool isNew = false;

    SinkInput *obj = m_sinkInputs.value(info->index, nullptr);
    if (!obj) {
        obj = new SinkInput;
        isNew = true;
    }
    obj->setInfo(info);
    m_sinkInputs.insert(info->index, obj);

    int listIndex = -1;
    QList<quint32> list = m_sinkInputs.keys();
    for (int i = 0; i < list.size(); ++i) {
        if (list.at(i) == info->index) {
            listIndex = i;
            break;
        }
    }
    Q_ASSERT(listIndex >= 0);

    qDebug() << Q_FUNC_INFO << isNew << listIndex;
    if (isNew) {
        emit sinkInputAdded(listIndex);
    } else {
        emit sinkInputUpdated(listIndex);
    }
}

void Context::setSinkVolume(quint32 index, quint32 volume)
{
    qDebug() << Q_FUNC_INFO << index << volume;
    pa_operation *o;
    Sink *sink = m_sinks.value(index, nullptr);
    if (!sink)
        return;
    pa_cvolume newVolume = sink->volume();
    for (int i = 0; i < newVolume.channels; ++i) {
        newVolume.values[i] = volume;
    }
    if (!(o = pa_context_set_sink_volume_by_index(m_context, index, &newVolume, NULL, NULL))) {
        qWarning() <<  "pa_context_set_sink_volume_by_index() failed";
        return;
    }
    pa_operation_unref(o);
}

void Context::setSinkPort(quint32 portIndex)
{
    pa_operation *o;
    if (!(o = pa_context_set_sink_port_by_index(m_context, portIndex, nullptr, nullptr, nullptr))) {
        qWarning() << "pa_context_set_sink_port_by_index failed";
        return;
    }
}

void Context::setSinkInputVolume(quint32 index, quint32 volume)
{
    qDebug() << Q_FUNC_INFO << index << volume;
    pa_operation *o;
    SinkInput *obj = m_sinkInputs.value(index, nullptr);
    if (!obj)
        return;
    pa_cvolume newVolume = obj->volume();
    for (int i = 0; i < newVolume.channels; ++i) {
        newVolume.values[i] = volume;
    }
    if (!(o = pa_context_set_sink_input_volume(m_context, index, &newVolume, NULL, NULL))) {
        qWarning() <<  "pa_context_set_sink_volume_by_index() failed";
        return;
    }
    pa_operation_unref(o);
}

void Context::connectToDaemon()
{
    Q_ASSERT(m_context == nullptr);
    Q_ASSERT(m_mainloop == nullptr);

    qDebug() <<  "Attempting connection to PulseAudio sound daemon";
    m_mainloop = pa_glib_mainloop_new(nullptr);
    pa_mainloop_api *api = pa_glib_mainloop_get_api(m_mainloop);
    Q_ASSERT(api);

    m_context = pa_context_new(api, "QPulse");
    Q_ASSERT(m_context);

    if (pa_context_connect(m_context, NULL, PA_CONTEXT_NOFAIL, 0) < 0) {
        pa_context_unref(m_context);
        m_context = nullptr;
        return;
    }
    pa_context_set_state_callback(m_context, &context_state_callback, this);
}
<|endoftext|>


<commit_before>//===-- DynamicLoaderLinux.h ------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

// C Includes
// C++ Includes
// Other libraries and framework includes
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/Log.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Target.h"
#include "lldb/Target/Thread.h"
#include "lldb/Target/ThreadPlanRunToAddress.h"

#include "AuxVector.h"
#include "DynamicLoaderLinuxDYLD.h"

using namespace lldb;
using namespace lldb_private;

void
DynamicLoaderLinuxDYLD::Initialize()
{
    PluginManager::RegisterPlugin(GetPluginNameStatic(),
                                  GetPluginDescriptionStatic(),
                                  CreateInstance);
}

void
DynamicLoaderLinuxDYLD::Terminate()
{
}

const char *
DynamicLoaderLinuxDYLD::GetPluginName()
{
    return "DynamicLoaderLinuxDYLD";
}

const char *
DynamicLoaderLinuxDYLD::GetShortPluginName()
{
    return "linux-dyld";
}

const char *
DynamicLoaderLinuxDYLD::GetPluginNameStatic()
{
    return "dynamic-loader.linux-dyld";
}

const char *
DynamicLoaderLinuxDYLD::GetPluginDescriptionStatic()
{
    return "Dynamic loader plug-in that watches for shared library "
           "loads/unloads in Linux processes.";
}

void
DynamicLoaderLinuxDYLD::GetPluginCommandHelp(const char *command, Stream *strm)
{
}

uint32_t
DynamicLoaderLinuxDYLD::GetPluginVersion()
{
    return 1;
}

DynamicLoader *
DynamicLoaderLinuxDYLD::CreateInstance(Process *process, bool force)
{
    bool create = force;
    if (!create)
    {
        const llvm::Triple &triple_ref = process->GetTarget().GetArchitecture().GetTriple();
        if (triple_ref.getOS() == llvm::Triple::Linux)
            create = true;
    }

    if (create)
        return new DynamicLoaderLinuxDYLD (process);
    return NULL;
}

DynamicLoaderLinuxDYLD::DynamicLoaderLinuxDYLD(Process *process)
    : DynamicLoader(process),
      m_rendezvous(process),
      m_load_offset(LLDB_INVALID_ADDRESS),
      m_entry_point(LLDB_INVALID_ADDRESS),
      m_auxv(NULL)
{
}

DynamicLoaderLinuxDYLD::~DynamicLoaderLinuxDYLD()
{
}

void
DynamicLoaderLinuxDYLD::DidAttach()
{
    ModuleSP executable;
    addr_t load_offset;

    m_auxv.reset(new AuxVector(m_process));

    executable = m_process->GetTarget().GetExecutableModule();
    load_offset = ComputeLoadOffset();

    if (!executable.empty() && load_offset != LLDB_INVALID_ADDRESS)
    {
        ModuleList module_list;
        module_list.Append(executable);
        UpdateLoadedSections(executable, load_offset);
        m_process->GetTarget().ModulesDidLoad(module_list);
    }
}

void
DynamicLoaderLinuxDYLD::DidLaunch()
{
    ModuleSP executable;
    addr_t load_offset;

    m_auxv.reset(new AuxVector(m_process));

    executable = m_process->GetTarget().GetExecutableModule();
    load_offset = ComputeLoadOffset();

    if (!executable.empty() && load_offset != LLDB_INVALID_ADDRESS)
    {
        ModuleList module_list;
        module_list.Append(executable);
        UpdateLoadedSections(executable, load_offset);
        ProbeEntry();
        m_process->GetTarget().ModulesDidLoad(module_list);
    }
}

Error
DynamicLoaderLinuxDYLD::ExecutePluginCommand(Args &command, Stream *strm)
{
    return Error();
}

Log *
DynamicLoaderLinuxDYLD::EnablePluginLogging(Stream *strm, Args &command)
{
    return NULL;
}

Error
DynamicLoaderLinuxDYLD::CanLoadImage()
{
    return Error();
}

void
DynamicLoaderLinuxDYLD::UpdateLoadedSections(ModuleSP module, addr_t base_addr)
{
    ObjectFile *obj_file = module->GetObjectFile();
    SectionList *sections = obj_file->GetSectionList();
    SectionLoadList &load_list = m_process->GetTarget().GetSectionLoadList();
    const size_t num_sections = sections->GetSize();

    for (unsigned i = 0; i < num_sections; ++i)
    {
        Section *section = sections->GetSectionAtIndex(i).get();
        lldb::addr_t new_load_addr = section->GetFileAddress() + base_addr;
        lldb::addr_t old_load_addr = load_list.GetSectionLoadAddress(section);

        // If the file address of the section is zero then this is not an
        // allocatable/loadable section (property of ELF sh_addr).  Skip it.
        if (new_load_addr == base_addr)
            continue;

        if (old_load_addr == LLDB_INVALID_ADDRESS ||
            old_load_addr != new_load_addr)
            load_list.SetSectionLoadAddress(section, new_load_addr);
    }
}

void
DynamicLoaderLinuxDYLD::ProbeEntry()
{
    Breakpoint *entry_break;
    addr_t entry;

    if ((entry = GetEntryPoint()) == LLDB_INVALID_ADDRESS)
        return;

    entry_break = m_process->GetTarget().CreateBreakpoint(entry, true).get();
    entry_break->SetCallback(EntryBreakpointHit, this, true);
}

// The runtime linker has run and initialized the rendezvous structure once the
// process has hit its entry point.  When we hit the corresponding breakpoint we
// interrogate the rendezvous structure to get the load addresses of all
// dependent modules for the process.  Similarly, we can discover the runtime
// linker function and setup a breakpoint to notify us of any dynamically loaded
// modules (via dlopen).
bool
DynamicLoaderLinuxDYLD::EntryBreakpointHit(void *baton,
                                           StoppointCallbackContext *context,
                                           user_id_t break_id,
                                           user_id_t break_loc_id)
{
    DynamicLoaderLinuxDYLD* dyld_instance;

    dyld_instance = static_cast<DynamicLoaderLinuxDYLD*>(baton);
    dyld_instance->LoadAllCurrentModules();
    dyld_instance->SetRendezvousBreakpoint();
    return false; // Continue running.
}

void
DynamicLoaderLinuxDYLD::SetRendezvousBreakpoint()
{
    Breakpoint *dyld_break;
    addr_t break_addr;

    break_addr = m_rendezvous.GetBreakAddress();
    dyld_break = m_process->GetTarget().CreateBreakpoint(break_addr, true).get();
    dyld_break->SetCallback(RendezvousBreakpointHit, this, true);
}

bool
DynamicLoaderLinuxDYLD::RendezvousBreakpointHit(void *baton,
                                                StoppointCallbackContext *context,
                                                user_id_t break_id,
                                                user_id_t break_loc_id)
{
    DynamicLoaderLinuxDYLD* dyld_instance;

    dyld_instance = static_cast<DynamicLoaderLinuxDYLD*>(baton);
    dyld_instance->RefreshModules();

    // Return true to stop the target, false to just let the target run.
    return dyld_instance->GetStopWhenImagesChange();
}

void
DynamicLoaderLinuxDYLD::RefreshModules()
{
    if (!m_rendezvous.Resolve())
        return;

    DYLDRendezvous::iterator I;
    DYLDRendezvous::iterator E;

    ModuleList &loaded_modules = m_process->GetTarget().GetImages();

    if (m_rendezvous.ModulesDidLoad())
    {
        ModuleList new_modules;

        E = m_rendezvous.loaded_end();
        for (I = m_rendezvous.loaded_begin(); I != E; ++I)
        {
            FileSpec file(I->path.c_str(), true);
            ModuleSP module_sp = LoadModuleAtAddress(file, I->base_addr);
            if (!module_sp.empty())
                new_modules.Append(module_sp);
        }
        m_process->GetTarget().ModulesDidLoad(new_modules);
    }

    if (m_rendezvous.ModulesDidUnload())
    {
        ModuleList old_modules;

        E = m_rendezvous.unloaded_end();
        for (I = m_rendezvous.unloaded_begin(); I != E; ++I)
        {
            FileSpec file(I->path.c_str(), true);
            ModuleSP module_sp = loaded_modules.FindFirstModuleForFileSpec(file);
            if (!module_sp.empty())
                old_modules.Append(module_sp);
        }
        m_process->GetTarget().ModulesDidUnload(old_modules);
    }
}

ThreadPlanSP
DynamicLoaderLinuxDYLD::GetStepThroughTrampolinePlan(Thread &thread, bool stop)
{
    LogSP log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_DYNAMIC_LOADER));
    ThreadPlanSP thread_plan_sp;

    StackFrame *frame = thread.GetStackFrameAtIndex(0).get();
    const SymbolContext &context = frame->GetSymbolContext(eSymbolContextSymbol);
    Symbol *sym = context.symbol;

    if (sym == NULL || !sym->IsTrampoline())
        return thread_plan_sp;

    const ConstString &sym_name = sym->GetMangled().GetName(Mangled::ePreferMangled);
    if (!sym_name)
        return thread_plan_sp;

    SymbolContextList target_symbols;
    Target &target = thread.GetProcess().GetTarget();
    ModuleList &images = target.GetImages();

    images.FindSymbolsWithNameAndType(sym_name, eSymbolTypeCode, target_symbols);
    size_t num_targets = target_symbols.GetSize();
    if (!num_targets)
        return thread_plan_sp;

    typedef std::vector<lldb::addr_t> AddressVector;
    AddressVector addrs;
    for (size_t i = 0; i < num_targets; ++i)
    {
        SymbolContext context;
        AddressRange range;
        if (target_symbols.GetContextAtIndex(i, context))
        {
            context.GetAddressRange(eSymbolContextEverything, range);
            lldb::addr_t addr = range.GetBaseAddress().GetLoadAddress(&target);
            if (addr != LLDB_INVALID_ADDRESS)
                addrs.push_back(addr);
        }
    }

    if (addrs.size() > 0)
    {
        AddressVector::iterator start = addrs.begin();
        AddressVector::iterator end = addrs.end();

        std::sort(start, end);
        addrs.erase(std::unique(start, end), end);
        thread_plan_sp.reset(new ThreadPlanRunToAddress(thread, addrs, stop));
    }

    return thread_plan_sp;
}

void
DynamicLoaderLinuxDYLD::LoadAllCurrentModules()
{
    DYLDRendezvous::iterator I;
    DYLDRendezvous::iterator E;
    ModuleList module_list;

    if (!m_rendezvous.Resolve())
        return;

    for (I = m_rendezvous.begin(), E = m_rendezvous.end(); I != E; ++I)
    {
        FileSpec file(I->path.c_str(), false);
        ModuleSP module_sp = LoadModuleAtAddress(file, I->base_addr);
        if (!module_sp.empty())
            module_list.Append(module_sp);
    }

    m_process->GetTarget().ModulesDidLoad(module_list);
}

ModuleSP
DynamicLoaderLinuxDYLD::LoadModuleAtAddress(const FileSpec &file, addr_t base_addr)
{
    Target &target = m_process->GetTarget();
    ModuleList &modules = target.GetImages();
    ModuleSP module_sp;

    if ((module_sp = modules.FindFirstModuleForFileSpec(file)))
    {
        UpdateLoadedSections(module_sp, base_addr);
    }
    else if ((module_sp = target.GetSharedModule(file, target.GetArchitecture())))
    {
        UpdateLoadedSections(module_sp, base_addr);
        modules.Append(module_sp);
    }

    return module_sp;
}

addr_t
DynamicLoaderLinuxDYLD::ComputeLoadOffset()
{
    addr_t virt_entry;

    if (m_load_offset != LLDB_INVALID_ADDRESS)
        return m_load_offset;

    if ((virt_entry = GetEntryPoint()) == LLDB_INVALID_ADDRESS)
        return LLDB_INVALID_ADDRESS;

    ModuleSP module = m_process->GetTarget().GetExecutableModule();
    ObjectFile *exe = module->GetObjectFile();
    Address file_entry = exe->GetEntryPointAddress();

    if (!file_entry.IsValid())
        return LLDB_INVALID_ADDRESS;

    m_load_offset = virt_entry - file_entry.GetFileAddress();
    return m_load_offset;
}

addr_t
DynamicLoaderLinuxDYLD::GetEntryPoint()
{
    if (m_entry_point != LLDB_INVALID_ADDRESS)
        return m_entry_point;

    if (m_auxv.get() == NULL)
        return LLDB_INVALID_ADDRESS;

    AuxVector::iterator I = m_auxv->FindEntry(AuxVector::AT_ENTRY);

    if (I == m_auxv->end())
        return LLDB_INVALID_ADDRESS;

    m_entry_point = static_cast<addr_t>(I->value);
    return m_entry_point;
}
<commit_msg>linux: add missing arguments to FindFirstModuleForFileSpec<commit_after>//===-- DynamicLoaderLinux.h ------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

// C Includes
// C++ Includes
// Other libraries and framework includes
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/Log.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Target.h"
#include "lldb/Target/Thread.h"
#include "lldb/Target/ThreadPlanRunToAddress.h"

#include "AuxVector.h"
#include "DynamicLoaderLinuxDYLD.h"

using namespace lldb;
using namespace lldb_private;

void
DynamicLoaderLinuxDYLD::Initialize()
{
    PluginManager::RegisterPlugin(GetPluginNameStatic(),
                                  GetPluginDescriptionStatic(),
                                  CreateInstance);
}

void
DynamicLoaderLinuxDYLD::Terminate()
{
}

const char *
DynamicLoaderLinuxDYLD::GetPluginName()
{
    return "DynamicLoaderLinuxDYLD";
}

const char *
DynamicLoaderLinuxDYLD::GetShortPluginName()
{
    return "linux-dyld";
}

const char *
DynamicLoaderLinuxDYLD::GetPluginNameStatic()
{
    return "dynamic-loader.linux-dyld";
}

const char *
DynamicLoaderLinuxDYLD::GetPluginDescriptionStatic()
{
    return "Dynamic loader plug-in that watches for shared library "
           "loads/unloads in Linux processes.";
}

void
DynamicLoaderLinuxDYLD::GetPluginCommandHelp(const char *command, Stream *strm)
{
}

uint32_t
DynamicLoaderLinuxDYLD::GetPluginVersion()
{
    return 1;
}

DynamicLoader *
DynamicLoaderLinuxDYLD::CreateInstance(Process *process, bool force)
{
    bool create = force;
    if (!create)
    {
        const llvm::Triple &triple_ref = process->GetTarget().GetArchitecture().GetTriple();
        if (triple_ref.getOS() == llvm::Triple::Linux)
            create = true;
    }

    if (create)
        return new DynamicLoaderLinuxDYLD (process);
    return NULL;
}

DynamicLoaderLinuxDYLD::DynamicLoaderLinuxDYLD(Process *process)
    : DynamicLoader(process),
      m_rendezvous(process),
      m_load_offset(LLDB_INVALID_ADDRESS),
      m_entry_point(LLDB_INVALID_ADDRESS),
      m_auxv(NULL)
{
}

DynamicLoaderLinuxDYLD::~DynamicLoaderLinuxDYLD()
{
}

void
DynamicLoaderLinuxDYLD::DidAttach()
{
    ModuleSP executable;
    addr_t load_offset;

    m_auxv.reset(new AuxVector(m_process));

    executable = m_process->GetTarget().GetExecutableModule();
    load_offset = ComputeLoadOffset();

    if (!executable.empty() && load_offset != LLDB_INVALID_ADDRESS)
    {
        ModuleList module_list;
        module_list.Append(executable);
        UpdateLoadedSections(executable, load_offset);
        m_process->GetTarget().ModulesDidLoad(module_list);
    }
}

void
DynamicLoaderLinuxDYLD::DidLaunch()
{
    ModuleSP executable;
    addr_t load_offset;

    m_auxv.reset(new AuxVector(m_process));

    executable = m_process->GetTarget().GetExecutableModule();
    load_offset = ComputeLoadOffset();

    if (!executable.empty() && load_offset != LLDB_INVALID_ADDRESS)
    {
        ModuleList module_list;
        module_list.Append(executable);
        UpdateLoadedSections(executable, load_offset);
        ProbeEntry();
        m_process->GetTarget().ModulesDidLoad(module_list);
    }
}

Error
DynamicLoaderLinuxDYLD::ExecutePluginCommand(Args &command, Stream *strm)
{
    return Error();
}

Log *
DynamicLoaderLinuxDYLD::EnablePluginLogging(Stream *strm, Args &command)
{
    return NULL;
}

Error
DynamicLoaderLinuxDYLD::CanLoadImage()
{
    return Error();
}

void
DynamicLoaderLinuxDYLD::UpdateLoadedSections(ModuleSP module, addr_t base_addr)
{
    ObjectFile *obj_file = module->GetObjectFile();
    SectionList *sections = obj_file->GetSectionList();
    SectionLoadList &load_list = m_process->GetTarget().GetSectionLoadList();
    const size_t num_sections = sections->GetSize();

    for (unsigned i = 0; i < num_sections; ++i)
    {
        Section *section = sections->GetSectionAtIndex(i).get();
        lldb::addr_t new_load_addr = section->GetFileAddress() + base_addr;
        lldb::addr_t old_load_addr = load_list.GetSectionLoadAddress(section);

        // If the file address of the section is zero then this is not an
        // allocatable/loadable section (property of ELF sh_addr).  Skip it.
        if (new_load_addr == base_addr)
            continue;

        if (old_load_addr == LLDB_INVALID_ADDRESS ||
            old_load_addr != new_load_addr)
            load_list.SetSectionLoadAddress(section, new_load_addr);
    }
}

void
DynamicLoaderLinuxDYLD::ProbeEntry()
{
    Breakpoint *entry_break;
    addr_t entry;

    if ((entry = GetEntryPoint()) == LLDB_INVALID_ADDRESS)
        return;

    entry_break = m_process->GetTarget().CreateBreakpoint(entry, true).get();
    entry_break->SetCallback(EntryBreakpointHit, this, true);
}

// The runtime linker has run and initialized the rendezvous structure once the
// process has hit its entry point.  When we hit the corresponding breakpoint we
// interrogate the rendezvous structure to get the load addresses of all
// dependent modules for the process.  Similarly, we can discover the runtime
// linker function and setup a breakpoint to notify us of any dynamically loaded
// modules (via dlopen).
bool
DynamicLoaderLinuxDYLD::EntryBreakpointHit(void *baton,
                                           StoppointCallbackContext *context,
                                           user_id_t break_id,
                                           user_id_t break_loc_id)
{
    DynamicLoaderLinuxDYLD* dyld_instance;

    dyld_instance = static_cast<DynamicLoaderLinuxDYLD*>(baton);
    dyld_instance->LoadAllCurrentModules();
    dyld_instance->SetRendezvousBreakpoint();
    return false; // Continue running.
}

void
DynamicLoaderLinuxDYLD::SetRendezvousBreakpoint()
{
    Breakpoint *dyld_break;
    addr_t break_addr;

    break_addr = m_rendezvous.GetBreakAddress();
    dyld_break = m_process->GetTarget().CreateBreakpoint(break_addr, true).get();
    dyld_break->SetCallback(RendezvousBreakpointHit, this, true);
}

bool
DynamicLoaderLinuxDYLD::RendezvousBreakpointHit(void *baton,
                                                StoppointCallbackContext *context,
                                                user_id_t break_id,
                                                user_id_t break_loc_id)
{
    DynamicLoaderLinuxDYLD* dyld_instance;

    dyld_instance = static_cast<DynamicLoaderLinuxDYLD*>(baton);
    dyld_instance->RefreshModules();

    // Return true to stop the target, false to just let the target run.
    return dyld_instance->GetStopWhenImagesChange();
}

void
DynamicLoaderLinuxDYLD::RefreshModules()
{
    if (!m_rendezvous.Resolve())
        return;

    DYLDRendezvous::iterator I;
    DYLDRendezvous::iterator E;

    ModuleList &loaded_modules = m_process->GetTarget().GetImages();

    if (m_rendezvous.ModulesDidLoad())
    {
        ModuleList new_modules;

        E = m_rendezvous.loaded_end();
        for (I = m_rendezvous.loaded_begin(); I != E; ++I)
        {
            FileSpec file(I->path.c_str(), true);
            ModuleSP module_sp = LoadModuleAtAddress(file, I->base_addr);
            if (!module_sp.empty())
                new_modules.Append(module_sp);
        }
        m_process->GetTarget().ModulesDidLoad(new_modules);
    }

    if (m_rendezvous.ModulesDidUnload())
    {
        ModuleList old_modules;

        E = m_rendezvous.unloaded_end();
        for (I = m_rendezvous.unloaded_begin(); I != E; ++I)
        {
            FileSpec file(I->path.c_str(), true);
            ModuleSP module_sp =
                loaded_modules.FindFirstModuleForFileSpec(file, NULL, NULL);
            if (!module_sp.empty())
                old_modules.Append(module_sp);
        }
        m_process->GetTarget().ModulesDidUnload(old_modules);
    }
}

ThreadPlanSP
DynamicLoaderLinuxDYLD::GetStepThroughTrampolinePlan(Thread &thread, bool stop)
{
    LogSP log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_DYNAMIC_LOADER));
    ThreadPlanSP thread_plan_sp;

    StackFrame *frame = thread.GetStackFrameAtIndex(0).get();
    const SymbolContext &context = frame->GetSymbolContext(eSymbolContextSymbol);
    Symbol *sym = context.symbol;

    if (sym == NULL || !sym->IsTrampoline())
        return thread_plan_sp;

    const ConstString &sym_name = sym->GetMangled().GetName(Mangled::ePreferMangled);
    if (!sym_name)
        return thread_plan_sp;

    SymbolContextList target_symbols;
    Target &target = thread.GetProcess().GetTarget();
    ModuleList &images = target.GetImages();

    images.FindSymbolsWithNameAndType(sym_name, eSymbolTypeCode, target_symbols);
    size_t num_targets = target_symbols.GetSize();
    if (!num_targets)
        return thread_plan_sp;

    typedef std::vector<lldb::addr_t> AddressVector;
    AddressVector addrs;
    for (size_t i = 0; i < num_targets; ++i)
    {
        SymbolContext context;
        AddressRange range;
        if (target_symbols.GetContextAtIndex(i, context))
        {
            context.GetAddressRange(eSymbolContextEverything, range);
            lldb::addr_t addr = range.GetBaseAddress().GetLoadAddress(&target);
            if (addr != LLDB_INVALID_ADDRESS)
                addrs.push_back(addr);
        }
    }

    if (addrs.size() > 0)
    {
        AddressVector::iterator start = addrs.begin();
        AddressVector::iterator end = addrs.end();

        std::sort(start, end);
        addrs.erase(std::unique(start, end), end);
        thread_plan_sp.reset(new ThreadPlanRunToAddress(thread, addrs, stop));
    }

    return thread_plan_sp;
}

void
DynamicLoaderLinuxDYLD::LoadAllCurrentModules()
{
    DYLDRendezvous::iterator I;
    DYLDRendezvous::iterator E;
    ModuleList module_list;

    if (!m_rendezvous.Resolve())
        return;

    for (I = m_rendezvous.begin(), E = m_rendezvous.end(); I != E; ++I)
    {
        FileSpec file(I->path.c_str(), false);
        ModuleSP module_sp = LoadModuleAtAddress(file, I->base_addr);
        if (!module_sp.empty())
            module_list.Append(module_sp);
    }

    m_process->GetTarget().ModulesDidLoad(module_list);
}

ModuleSP
DynamicLoaderLinuxDYLD::LoadModuleAtAddress(const FileSpec &file, addr_t base_addr)
{
    Target &target = m_process->GetTarget();
    ModuleList &modules = target.GetImages();
    ModuleSP module_sp;

    if ((module_sp = modules.FindFirstModuleForFileSpec(file, NULL, NULL)))
    {
        UpdateLoadedSections(module_sp, base_addr);
    }
    else if ((module_sp = target.GetSharedModule(file, target.GetArchitecture())))
    {
        UpdateLoadedSections(module_sp, base_addr);
        modules.Append(module_sp);
    }

    return module_sp;
}

addr_t
DynamicLoaderLinuxDYLD::ComputeLoadOffset()
{
    addr_t virt_entry;

    if (m_load_offset != LLDB_INVALID_ADDRESS)
        return m_load_offset;

    if ((virt_entry = GetEntryPoint()) == LLDB_INVALID_ADDRESS)
        return LLDB_INVALID_ADDRESS;

    ModuleSP module = m_process->GetTarget().GetExecutableModule();
    ObjectFile *exe = module->GetObjectFile();
    Address file_entry = exe->GetEntryPointAddress();

    if (!file_entry.IsValid())
        return LLDB_INVALID_ADDRESS;

    m_load_offset = virt_entry - file_entry.GetFileAddress();
    return m_load_offset;
}

addr_t
DynamicLoaderLinuxDYLD::GetEntryPoint()
{
    if (m_entry_point != LLDB_INVALID_ADDRESS)
        return m_entry_point;

    if (m_auxv.get() == NULL)
        return LLDB_INVALID_ADDRESS;

    AuxVector::iterator I = m_auxv->FindEntry(AuxVector::AT_ENTRY);

    if (I == m_auxv->end())
        return LLDB_INVALID_ADDRESS;

    m_entry_point = static_cast<addr_t>(I->value);
    return m_entry_point;
}
<|endoftext|>


<commit_before>/*
 *  SinglePlayer.cpp
 *  OpenLieroX
 *
 *  Created by Albert Zeyer on 15.01.10.
 *  code under LGPL
 *
 */

#include <boost/bind.hpp>
#include "SinglePlayer.h"
#include "Options.h"
#include "ConfigHandler.h"
#include "game/Mod.h"
#include "game/Level.h"
#include "GfxPrimitives.h"
#include "DeprecatedGUI/Menu.h"
#include "CClient.h"
#include "CServer.h"
#include "ProfileSystem.h"
#include "CWormHuman.h"
#include "OLXCommand.h"
#include "util/macros.h"
#include "game/Settings.h"
#include "game/Game.h"
#include "client/ClientConnectionRequestInfo.h"

SinglePlayerGame singlePlayerGame;

static bool gameLevelExists(const std::string& game, int levelNr) {
	std::string dir;
	if(!ReadString("games/" + game + "/game.cfg", "Level" + itoa(levelNr), "Dir", dir, "") || Trimmed(dir) == "")
		return false;
	else
		return true;
}

static bool gameExists(const std::string& game) {
	return gameLevelExists(game, 1);
}

int SinglePlayerGame::maxSelectableLevelForCurrentGame() {
	int level = 1;
	std::map<std::string,int>::iterator f = tLXOptions->localplayLevels.find(currentGame);
	if(f != tLXOptions->localplayLevels.end())
		// this is the max allowed level
		level = MAX(f->second, 1);

	// check the last existing level
	while(level > 1 && !gameLevelExists(currentGame, level))
		--level;

	return level;
}

void SinglePlayerGame::setGame(const std::string& game) {
	if(!stringcaseequal(currentGame, game)) {
		currentGame = game;
		setLevel(maxSelectableLevelForCurrentGame());
	}
}

bool SinglePlayerGame::setLevel(int levelNr) {
	currentGameValid = false;
	image = NULL;
	levelInfo = LevelInfo();
	modInfo = ModInfo();
	description = "";

	if(!gameExists(currentGame)) {
		description = "Game " + currentGame + " is invalid";
		errors << "SinglePlayerGame::setLevel: game " << currentGame << " unknown" << endl;
		return false;
	}

	currentLevel = levelNr;
	if(currentLevel <= 0) currentLevel = 1;

	if(!gameLevelExists(currentGame, currentLevel)) {
		warnings << "SinglePlayerGame::setLevel: game " << currentGame << " doesnt have level " << currentLevel << endl;
		do { currentLevel--; }
		while(currentLevel > 0 && !gameLevelExists(currentGame, currentLevel));
		if(currentLevel <= 0) {
			errors << "SinglePlayerGame::setLevel: woot" << endl;
			return false;
		}
	}

	std::string level, mod, img;
	ReadString("games/" + currentGame + "/game.cfg", "Level" + itoa(currentLevel), "Dir", level, ""); TrimSpaces(level);
	ReadString("games/" + currentGame + "/game.cfg", "Level" + itoa(currentLevel), "Mod", mod, "Classic"); TrimSpaces(mod);
	ReadString("games/" + currentGame + "/game.cfg", "Level" + itoa(currentLevel), "Image", img, ""); TrimSpaces(img);
	ReadString("games/" + currentGame + "/game.cfg", "Level" + itoa(currentLevel), "Desc", description, ""); TrimSpaces(description);

	level = "../games/" + currentGame + "/" + level; // it's relative to levels
	levelInfo = infoForLevel(level);
	if(!levelInfo.valid) {
		description = "Problem while loading level " + level + "";
		errors << "SinglePlayerGame::setLevel: cannot find map " << level << " for game " << currentGame << ":" << currentLevel << endl;
		return false;
	}
	levelInfo.path = level; // to force this uncommon filename

	modInfo = infoForMod(mod);
	if(!modInfo.valid) {
		description = "Problem while loading mod " + mod + "";
		errors << "SinglePlayerGame::setLevel: cannot find mod " << mod << " for game " << currentGame << ":" << currentLevel << endl;
		return false;
	}

	if(description == "")
		description = "Undescribed level " + itoa(currentLevel) + " of game " + currentGame + "";

	image = LoadGameImage("games/" + currentGame + "/" + img);
	if(image.get() == NULL) {
		warnings << "SinglePlayerGame::setLevel: cannot load preview image '" << img << "' for game " << currentGame << ":" << currentLevel << endl;
		image = gfxCreateSurfaceAlpha(200, 50);
		DrawCross(image.get(), 0, 0, 200, 50, Color(255,0,0));
	}

	currentGameValid = true;
	return true;
}

static bool addPlayerToClient() {
	// this is the current way to tell CClient to create a local worm later on with that profile
	// that is done in CClientNetEngine::ParseConnected or updateAddedWorms
	cClient->connectInfo = new ClientConnectionRequestInfo;
	cClient->connectInfo->worms.push_back(MainHumanProfile());
	return true;
}

struct SinglePlayerSettingsScope : FeatureSettingsLayer {
	Settings::Layers oldLayers;

	SinglePlayerSettingsScope() : FeatureSettingsLayer("Single player game settings") {
		oldLayers.swap(gameSettings.layers);
		gameSettings.layersInitStandard(false);
		gameSettings.layers.push_back(this);
	}
	~SinglePlayerSettingsScope() { oldLayers.swap(gameSettings.layers); }
};

static SmartPointer<SinglePlayerSettingsScope> singlePlayerSettings;

static void SinglePlayer_CleanupAfterGameloopEnd() {
	singlePlayerSettings = NULL;
}

bool SinglePlayerGame::startGame() {
	levelSucceeded = false;

	if(!currentGameValid) {
		errors << "SinglePlayerGame::startGame: cannot start game: current game/level is invalid" << endl;
		return false;
	}

	if(bDedicated) {
		errors << "SinglePlayerGame::startGame: cannot do that in dedicated mode" << endl;
		// actually, we could but I really dont see a reason why we would want
		return false;
	}

	if(singlePlayerSettings.get()) singlePlayerSettings = NULL; // just to be sure
	singlePlayerSettings = new SinglePlayerSettingsScope();

	if(! cClient->Initialize() )
	{
		errors << "Could not initialize client" << endl;
		return false;
	}

	if(!addPlayerToClient()) {
		errors << "SinglePlayerGame::startGame: didn't found human worm" << endl;
		return false;
	}

	if(!cServer->StartServer()) {
		errors << "Could not start server" << endl;
		return false;
	}

	// standardGameMode is the lower level game mode - can be set by custom config file
	standardGameMode = NULL;

	// don't have any wpn restrictions
	cServer->setWeaponRestFile("");

	gameSettings.overwrite[FT_Map].as<LevelInfo>()->path = levelInfo.path;
	gameSettings.overwrite[FT_Map].as<LevelInfo>()->name = levelInfo.name;
	gameSettings.overwrite[FT_Mod].as<ModInfo>()->path = modInfo.path;
	gameSettings.overwrite[FT_Mod].as<ModInfo>()->name = modInfo.name;

	gameSettings.overwrite[FT_NewNetEngine] = false;
	gameSettings.overwrite[FT_Lives] = -2;
	gameSettings.overwrite[FT_KillLimit] = -1;
	gameSettings.overwrite[FT_TimeLimit] = -1.0f;

	gameSettings.overwrite[FT_GameMode].as<GameModeInfo>()->mode = this;

	{
		std::string extraConfig;
		ReadString("games/" + currentGame + "/game.cfg", "Level" + itoa(currentLevel), "Config", extraConfig, ""); TrimSpaces(extraConfig);
		if(extraConfig != "") {
			notes << "SinglePlayerGame: config: " << extraConfig << endl;
			singlePlayerSettings->loadFromConfig("games/" + currentGame + "/" + extraConfig, false);
		}
	}

	{
		std::string extraCmdStr;
		std::vector<std::string> extraCmds;
		ReadString("games/" + currentGame + "/game.cfg", "Level" + itoa(currentLevel), "Exec", extraCmdStr, ""); TrimSpaces(extraCmdStr);
		extraCmds = explode(extraCmdStr, ";");
		foreach(c, extraCmds) {
			notes << "SinglePlayerGame: exec: " << *c << endl;
			game.prepareCallbacks.connect(boost::bind(Execute, &stdoutCLI(), *c));
		}
	}

	// this can happen if the config has overwritten it
	if(gameSettings[FT_GameMode].as<GameModeInfo>()->mode != this) {
		// we set the fallback gamemode
		standardGameMode = gameSettings[FT_GameMode].as<GameModeInfo>()->mode;
		gameSettings.overwrite[FT_GameMode].as<GameModeInfo>()->mode = this;
	}

	game.cleanupCallbacks.connect(boost::bind(&SinglePlayer_CleanupAfterGameloopEnd));

	return true;
}

void SinglePlayerGame::setLevelSucceeded() {
	if(levelSucceeded) return;

	notes << "SinglePlayerGame: level was succeeded" << endl;
	levelSucceeded = true;
	game.gameOver = true;
	tLXOptions->localplayLevels[currentGame] = MAX((int)tLXOptions->localplayLevels[currentGame], (int)currentLevel + 1);

	if(gameLevelExists(currentGame, currentLevel + 1))
		setLevel(currentLevel + 1);
}

void SinglePlayerGame::Simulate() {
	if(standardGameMode)
		standardGameMode->Simulate();

	if(levelSucceeded)
		cServer->RecheckGame();
}

bool SinglePlayerGame::CheckGameOver() {
	if(standardGameMode && standardGameMode->CheckGameOver()) {
		notes << "SinglePlayer game gameOver, winner is " << standardGameMode->Winner() << ", winner team is " << standardGameMode->WinnerTeam() << endl;
		CWorm* w = game.firstLocalHumanWorm();
		if(w && standardGameMode->Winner() == w->getID())
			setLevelSucceeded();
		else if(w && standardGameMode->isTeamGame() && standardGameMode->WinnerTeam() == w->getTeam())
			setLevelSucceeded();

		return true;
	}

	return levelSucceeded;
}

int SinglePlayerGame::Winner() {
	if(!levelSucceeded) {
		if(standardGameMode) return standardGameMode->Winner();
		return -1;
	}

	CWorm* w = game.firstLocalHumanWorm();
	if(w) return w->getID();

	return -1;
}

void SinglePlayerGame::GameOver() {
	if(standardGameMode) standardGameMode->GameOver();
}

<commit_msg>small dbg out<commit_after>/*
 *  SinglePlayer.cpp
 *  OpenLieroX
 *
 *  Created by Albert Zeyer on 15.01.10.
 *  code under LGPL
 *
 */

#include <boost/bind.hpp>
#include "SinglePlayer.h"
#include "Options.h"
#include "ConfigHandler.h"
#include "game/Mod.h"
#include "game/Level.h"
#include "GfxPrimitives.h"
#include "DeprecatedGUI/Menu.h"
#include "CClient.h"
#include "CServer.h"
#include "ProfileSystem.h"
#include "CWormHuman.h"
#include "OLXCommand.h"
#include "util/macros.h"
#include "game/Settings.h"
#include "game/Game.h"
#include "client/ClientConnectionRequestInfo.h"

SinglePlayerGame singlePlayerGame;

static bool gameLevelExists(const std::string& game, int levelNr) {
	std::string dir;
	if(!ReadString("games/" + game + "/game.cfg", "Level" + itoa(levelNr), "Dir", dir, "") || Trimmed(dir) == "")
		return false;
	else
		return true;
}

static bool gameExists(const std::string& game) {
	return gameLevelExists(game, 1);
}

int SinglePlayerGame::maxSelectableLevelForCurrentGame() {
	int level = 1;
	std::map<std::string,int>::iterator f = tLXOptions->localplayLevels.find(currentGame);
	if(f != tLXOptions->localplayLevels.end())
		// this is the max allowed level
		level = MAX(f->second, 1);

	// check the last existing level
	while(level > 1 && !gameLevelExists(currentGame, level))
		--level;

	return level;
}

void SinglePlayerGame::setGame(const std::string& game) {
	if(!stringcaseequal(currentGame, game)) {
		currentGame = game;
		setLevel(maxSelectableLevelForCurrentGame());
	}
}

bool SinglePlayerGame::setLevel(int levelNr) {
	currentGameValid = false;
	image = NULL;
	levelInfo = LevelInfo();
	modInfo = ModInfo();
	description = "";

	if(!gameExists(currentGame)) {
		description = "Game " + currentGame + " is invalid";
		errors << "SinglePlayerGame::setLevel: game " << currentGame << " unknown" << endl;
		return false;
	}

	currentLevel = levelNr;
	if(currentLevel <= 0) currentLevel = 1;

	if(!gameLevelExists(currentGame, currentLevel)) {
		warnings << "SinglePlayerGame::setLevel: game " << currentGame << " doesnt have level " << currentLevel << endl;
		do { currentLevel--; }
		while(currentLevel > 0 && !gameLevelExists(currentGame, currentLevel));
		if(currentLevel <= 0) {
			errors << "SinglePlayerGame::setLevel: woot" << endl;
			return false;
		}
	}

	std::string level, mod, img;
	ReadString("games/" + currentGame + "/game.cfg", "Level" + itoa(currentLevel), "Dir", level, ""); TrimSpaces(level);
	ReadString("games/" + currentGame + "/game.cfg", "Level" + itoa(currentLevel), "Mod", mod, "Classic"); TrimSpaces(mod);
	ReadString("games/" + currentGame + "/game.cfg", "Level" + itoa(currentLevel), "Image", img, ""); TrimSpaces(img);
	ReadString("games/" + currentGame + "/game.cfg", "Level" + itoa(currentLevel), "Desc", description, ""); TrimSpaces(description);

	level = "../games/" + currentGame + "/" + level; // it's relative to levels
	levelInfo = infoForLevel(level);
	if(!levelInfo.valid) {
		description = "Problem while loading level " + level + "";
		errors << "SinglePlayerGame::setLevel: cannot find map " << level << " for game " << currentGame << ":" << currentLevel << endl;
		return false;
	}
	levelInfo.path = level; // to force this uncommon filename

	modInfo = infoForMod(mod);
	if(!modInfo.valid) {
		description = "Problem while loading mod " + mod + "";
		errors << "SinglePlayerGame::setLevel: cannot find mod " << mod << " for game " << currentGame << ":" << currentLevel << endl;
		return false;
	}

	if(description == "")
		description = "Undescribed level " + itoa(currentLevel) + " of game " + currentGame + "";

	image = LoadGameImage("games/" + currentGame + "/" + img);
	if(image.get() == NULL) {
		warnings << "SinglePlayerGame::setLevel: cannot load preview image '" << img << "' for game " << currentGame << ":" << currentLevel << endl;
		image = gfxCreateSurfaceAlpha(200, 50);
		DrawCross(image.get(), 0, 0, 200, 50, Color(255,0,0));
	}

	currentGameValid = true;
	return true;
}

static bool addPlayerToClient() {
	// this is the current way to tell CClient to create a local worm later on with that profile
	// that is done in CClientNetEngine::ParseConnected or updateAddedWorms
	cClient->connectInfo = new ClientConnectionRequestInfo;
	cClient->connectInfo->worms.push_back(MainHumanProfile());
	return true;
}

struct SinglePlayerSettingsScope : FeatureSettingsLayer {
	Settings::Layers oldLayers;

	SinglePlayerSettingsScope() : FeatureSettingsLayer("Single player game settings") {
		oldLayers.swap(gameSettings.layers);
		gameSettings.layersInitStandard(false);
		gameSettings.layers.push_back(this);
	}
	~SinglePlayerSettingsScope() { oldLayers.swap(gameSettings.layers); }
};

static SmartPointer<SinglePlayerSettingsScope> singlePlayerSettings;

static void SinglePlayer_CleanupAfterGameloopEnd() {
	singlePlayerSettings = NULL;
}

bool SinglePlayerGame::startGame() {
	levelSucceeded = false;

	if(!currentGameValid) {
		errors << "SinglePlayerGame::startGame: cannot start game: current game/level is invalid" << endl;
		return false;
	}

	if(bDedicated) {
		errors << "SinglePlayerGame::startGame: cannot do that in dedicated mode" << endl;
		// actually, we could but I really dont see a reason why we would want
		return false;
	}

	if(singlePlayerSettings.get()) singlePlayerSettings = NULL; // just to be sure
	singlePlayerSettings = new SinglePlayerSettingsScope();

	if(! cClient->Initialize() )
	{
		errors << "Could not initialize client" << endl;
		return false;
	}

	if(!addPlayerToClient()) {
		errors << "SinglePlayerGame::startGame: didn't found human worm" << endl;
		return false;
	}

	if(!cServer->StartServer()) {
		errors << "Could not start server" << endl;
		return false;
	}

	// standardGameMode is the lower level game mode - can be set by custom config file
	standardGameMode = NULL;

	// don't have any wpn restrictions
	cServer->setWeaponRestFile("");

	gameSettings.overwrite[FT_Map].as<LevelInfo>()->path = levelInfo.path;
	gameSettings.overwrite[FT_Map].as<LevelInfo>()->name = levelInfo.name;
	gameSettings.overwrite[FT_Mod].as<ModInfo>()->path = modInfo.path;
	gameSettings.overwrite[FT_Mod].as<ModInfo>()->name = modInfo.name;

	gameSettings.overwrite[FT_NewNetEngine] = false;
	gameSettings.overwrite[FT_Lives] = -2;
	gameSettings.overwrite[FT_KillLimit] = -1;
	gameSettings.overwrite[FT_TimeLimit] = -1.0f;

	gameSettings.overwrite[FT_GameMode].as<GameModeInfo>()->mode = this;

	{
		std::string extraConfig;
		ReadString("games/" + currentGame + "/game.cfg", "Level" + itoa(currentLevel), "Config", extraConfig, ""); TrimSpaces(extraConfig);
		if(extraConfig != "") {
			notes << "SinglePlayerGame: config: " << extraConfig << endl;
			singlePlayerSettings->loadFromConfig("games/" + currentGame + "/" + extraConfig, false);
		}
	}

	{
		std::string extraCmdStr;
		std::vector<std::string> extraCmds;
		ReadString("games/" + currentGame + "/game.cfg", "Level" + itoa(currentLevel), "Exec", extraCmdStr, ""); TrimSpaces(extraCmdStr);
		extraCmds = explode(extraCmdStr, ";");
		foreach(c, extraCmds) {
			notes << "SinglePlayerGame: exec: " << *c << endl;
			game.prepareCallbacks.connect(boost::bind(Execute, &stdoutCLI(), *c));
		}
	}

	// this can happen if the config has overwritten it
	if(gameSettings[FT_GameMode].as<GameModeInfo>()->mode != this) {
		// we set the fallback gamemode
		standardGameMode = gameSettings[FT_GameMode].as<GameModeInfo>()->mode;
		gameSettings.overwrite[FT_GameMode].as<GameModeInfo>()->mode = this;
		notes << "singlePlayer: special game mode " << standardGameMode->Name() << endl;
	}

	game.cleanupCallbacks.connect(boost::bind(&SinglePlayer_CleanupAfterGameloopEnd));

	return true;
}

void SinglePlayerGame::setLevelSucceeded() {
	if(levelSucceeded) return;

	notes << "SinglePlayerGame: level was succeeded" << endl;
	levelSucceeded = true;
	game.gameOver = true;
	tLXOptions->localplayLevels[currentGame] = MAX((int)tLXOptions->localplayLevels[currentGame], (int)currentLevel + 1);

	if(gameLevelExists(currentGame, currentLevel + 1))
		setLevel(currentLevel + 1);
}

void SinglePlayerGame::Simulate() {
	if(standardGameMode)
		standardGameMode->Simulate();

	if(levelSucceeded)
		cServer->RecheckGame();
}

bool SinglePlayerGame::CheckGameOver() {
	if(standardGameMode && standardGameMode->CheckGameOver()) {
		notes << "SinglePlayer game gameOver, winner is " << standardGameMode->Winner() << ", winner team is " << standardGameMode->WinnerTeam() << endl;
		CWorm* w = game.firstLocalHumanWorm();
		if(w && standardGameMode->Winner() == w->getID())
			setLevelSucceeded();
		else if(w && standardGameMode->isTeamGame() && standardGameMode->WinnerTeam() == w->getTeam())
			setLevelSucceeded();

		return true;
	}

	return levelSucceeded;
}

int SinglePlayerGame::Winner() {
	if(!levelSucceeded) {
		if(standardGameMode) return standardGameMode->Winner();
		return -1;
	}

	CWorm* w = game.firstLocalHumanWorm();
	if(w) return w->getID();

	return -1;
}

void SinglePlayerGame::GameOver() {
	if(standardGameMode) standardGameMode->GameOver();
}

<|endoftext|>


<commit_before>/****************************************************************************
 * Copyright (c) 2012-2018 by the DataTransferKit authors                   *
 * All rights reserved.                                                     *
 *                                                                          *
 * This file is part of the DataTransferKit library. DataTransferKit is     *
 * distributed under a BSD 3-clause license. For the licensing terms see    *
 * the LICENSE file in the top-level directory.                             *
 *                                                                          *
 * SPDX-License-Identifier: BSD-3-Clause                                    *
 ****************************************************************************/

#ifndef DTK_LINEAR_BVH_DECL_HPP
#define DTK_LINEAR_BVH_DECL_HPP

#include <DTK_Box.hpp>
#include <DTK_DetailsBoundingVolumeHierarchyImpl.hpp>
#include <DTK_DetailsNode.hpp>

#include <Kokkos_View.hpp>

namespace DataTransferKit
{

template <typename DeviceType>
class BoundingVolumeHierarchy
{
  public:
    using bounding_volume_type = Box;
    using size_type = typename Kokkos::View<int *, DeviceType>::size_type;

    BoundingVolumeHierarchy() = default; // build an empty tree

    BoundingVolumeHierarchy(
        Kokkos::View<Box const *, DeviceType> bounding_boxes );

    KOKKOS_INLINE_FUNCTION
    size_type size() const { return _leaf_nodes.extent( 0 ); }

    KOKKOS_INLINE_FUNCTION
    bool empty() const { return size() == 0; }

    KOKKOS_INLINE_FUNCTION
    bounding_volume_type bounds() const
    {
        // NOTE should default constructor initialize to an invalid geometry?
        if ( empty() )
            return bounding_volume_type();
        return ( size() > 1 ? _internal_nodes : _leaf_nodes )[0].bounding_box;
    }

    template <typename Query, typename... Args>
    inline void query( Kokkos::View<Query *, DeviceType> queries,
                       Args &&... args ) const
    {
        using Tag = typename Query::Tag;
        Details::BoundingVolumeHierarchyImpl<DeviceType>::queryDispatch(
            Tag{}, *this, queries, std::forward<Args>( args )... );
    }

  private:
    friend struct Details::TreeTraversal<DeviceType>;

    Kokkos::View<Node *, DeviceType> _leaf_nodes;
    Kokkos::View<Node *, DeviceType> _internal_nodes;
};

template <typename DeviceType>
using BVH = BoundingVolumeHierarchy<DeviceType>;

} // namespace DataTransferKit

#endif
<commit_msg>Pointless s/int/Node/ in BoundingVolumeHierarchy::size_type declaration<commit_after>/****************************************************************************
 * Copyright (c) 2012-2018 by the DataTransferKit authors                   *
 * All rights reserved.                                                     *
 *                                                                          *
 * This file is part of the DataTransferKit library. DataTransferKit is     *
 * distributed under a BSD 3-clause license. For the licensing terms see    *
 * the LICENSE file in the top-level directory.                             *
 *                                                                          *
 * SPDX-License-Identifier: BSD-3-Clause                                    *
 ****************************************************************************/

#ifndef DTK_LINEAR_BVH_DECL_HPP
#define DTK_LINEAR_BVH_DECL_HPP

#include <DTK_Box.hpp>
#include <DTK_DetailsBoundingVolumeHierarchyImpl.hpp>
#include <DTK_DetailsNode.hpp>

#include <Kokkos_View.hpp>

namespace DataTransferKit
{

template <typename DeviceType>
class BoundingVolumeHierarchy
{
  public:
    using bounding_volume_type = Box;
    using size_type = typename Kokkos::View<Node *, DeviceType>::size_type;

    BoundingVolumeHierarchy() = default; // build an empty tree

    BoundingVolumeHierarchy(
        Kokkos::View<Box const *, DeviceType> bounding_boxes );

    KOKKOS_INLINE_FUNCTION
    size_type size() const { return _leaf_nodes.extent( 0 ); }

    KOKKOS_INLINE_FUNCTION
    bool empty() const { return size() == 0; }

    KOKKOS_INLINE_FUNCTION
    bounding_volume_type bounds() const
    {
        // NOTE should default constructor initialize to an invalid geometry?
        if ( empty() )
            return bounding_volume_type();
        return ( size() > 1 ? _internal_nodes : _leaf_nodes )[0].bounding_box;
    }

    template <typename Query, typename... Args>
    inline void query( Kokkos::View<Query *, DeviceType> queries,
                       Args &&... args ) const
    {
        using Tag = typename Query::Tag;
        Details::BoundingVolumeHierarchyImpl<DeviceType>::queryDispatch(
            Tag{}, *this, queries, std::forward<Args>( args )... );
    }

  private:
    friend struct Details::TreeTraversal<DeviceType>;

    Kokkos::View<Node *, DeviceType> _leaf_nodes;
    Kokkos::View<Node *, DeviceType> _internal_nodes;
};

template <typename DeviceType>
using BVH = BoundingVolumeHierarchy<DeviceType>;

} // namespace DataTransferKit

#endif
<|endoftext|>