<commit_before>/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

#include "tensorflow/lite/delegates/gpu/gl/api.h"

#include <algorithm>
#include <cstdint>
#include <deque>
#include <mutex>  // NOLINT
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/common/util.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_call.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_gl31.h"
#include "tensorflow/lite/delegates/gpu/gl/request_gpu_info.h"
#include "tensorflow/lite/delegates/gpu/gl/runtime.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"

#ifndef TFLITE_GPU_BINARY_RELEASE
#include "tensorflow/lite/delegates/gpu/gl/serialization.h"
#endif  // TFLITE_GPU_BINARY_RELEASE

namespace tflite {
namespace gpu {
namespace gl {
namespace {

using ObjectsSizes = std::unordered_map<ValueId, size_t>;

enum class InferenceContextState {
  NOT_STARTED,
  IN_PROGRESS,
};

class InferenceContextImpl : public InferenceContext {
 public:
  explicit InferenceContextImpl(std::unique_ptr<Runtime> runtime)
      : runtime_(std::move(runtime)) {}

  absl::Status Execute() final {
    std::lock_guard<std::mutex> lock(guard_);
    if (state_ != InferenceContextState::NOT_STARTED) {
      return absl::FailedPreconditionError("InferenceContext is not reset");
    }
    state_ = InferenceContextState::IN_PROGRESS;
    return runtime_->Execute();
  }

  absl::Status Reset() final {
    std::lock_guard<std::mutex> lock(guard_);
    // TODO(akulik): should Reset not return Status?
    state_ = InferenceContextState::NOT_STARTED;
    return absl::OkStatus();
  }

  RuntimeStats stats() const final { return runtime_->stats(); }

 private:
  std::unique_ptr<Runtime> runtime_;

  mutable std::mutex guard_;
  InferenceContextState state_ = InferenceContextState::NOT_STARTED;
};

class InferenceContextWithBatchImpl : public InferenceContext {
 public:
  InferenceContextWithBatchImpl(const ObjectsSizes& sizes,
                                const ObjectManager* objects,
                                std::unique_ptr<ObjectManager> refs,
                                std::unique_ptr<Runtime> runtime)
      : sizes_(sizes),
        objects_(objects),
        refs_(std::move(refs)),
        runtime_(std::move(runtime)) {}

  absl::Status Execute() final {
    std::lock_guard<std::mutex> lock(guard_);
    if (state_ != InferenceContextState::NOT_STARTED) {
      return absl::FailedPreconditionError("InferenceContext is not reset");
    }
    state_ = InferenceContextState::IN_PROGRESS;

    // Calculate expected number of batches and check that all external objects
    // match that number.
    int num_batches = 0;
    for (const auto& s : sizes_) {
      const ValueId id = s.first;
      const size_t byte_size = s.second;

      auto buffer = objects_->FindBuffer(id);
      if (!buffer) continue;

      if (buffer->bytes_size() % byte_size) {
        return absl::InvalidArgumentError(absl::StrCat(
            "Object ", id, " does not match expected byte size: ", byte_size));
      }

      const size_t b = buffer->bytes_size() / byte_size;
      if (num_batches == 0) {
        num_batches = b;
      } else if (num_batches != b) {
        return absl::InvalidArgumentError(absl::StrCat(
            "Object ", id, " size does not match expected batch size: ", b,
            " vs ", num_batches));
      }
    }

    for (size_t b = 0; b < num_batches; ++b) {
      // slice external objects by batch.
      for (const auto& s : sizes_) {
        const ValueId id = s.first;
        const size_t byte_size = s.second;
        auto buffer = objects_->FindBuffer(id);
        if (buffer) {
          auto ref = refs_->FindBuffer(id);
          if (!ref) {
            return absl::InvalidArgumentError(
                absl::StrCat("Reference to ", id, " is not found"));
          }
          RETURN_IF_ERROR(buffer->MakeView(b * byte_size, byte_size, ref));
        }
      }
      RETURN_IF_ERROR(runtime_->Execute());
    }
    return absl::OkStatus();
  }

  absl::Status Reset() final {
    std::lock_guard<std::mutex> lock(guard_);
    state_ = InferenceContextState::NOT_STARTED;
    // TODO(akulik): should Reset not return Status?
    return absl::OkStatus();
  }

  RuntimeStats stats() const final { return runtime_->stats(); }

 private:
  const ObjectsSizes sizes_;
  const ObjectManager* objects_;

  // view over external objects provided by a user.
  std::unique_ptr<ObjectManager> refs_;
  std::unique_ptr<Runtime> runtime_;

  mutable std::mutex guard_;
  InferenceContextState state_ = InferenceContextState::NOT_STARTED;
};

struct ProgramParameters {
  // A list of uniform parameters to be set.
  std::vector<Variable> parameters;

  // A list of objects to bind to opengl program.
  std::vector<Object> objects;

  uint3 workgroup_size;
  uint3 num_workgroups;

  size_t shader_idx;
};

std::string GetShaderHeader(uint3 localsize) {
  return absl::StrCat("#version 310 es\nlayout(local_size_x = ", localsize.x,
                      ", local_size_y = ", localsize.y,
                      ", local_size_z = ", localsize.z, ") in;\n");
}

class CompiledModelImpl
#ifndef TFLITE_GPU_BINARY_RELEASE
    : public CompiledModel,
      public DeserializationHandler {
#else
    : public CompiledModel {
#endif  // TFLITE_GPU_BINARY_RELEASE
 public:
  explicit CompiledModelImpl(const GpuInfo& gpu_info) : gpu_info_(gpu_info) {}

  // Called while compiling shaders from scratch
  absl::Status Add(const WorkgroupsCalculator& workgroup_calculator,
                   ShaderCode code) {
    // Calculate workgroup size.
    uint3 workgroup_size = workgroup_calculator.Calculate(code);
    uint3 num_workgroups = IntegralDivideRoundUp(code.workload, workgroup_size);

    for (const auto& object : code.objects) {
      if (IsRef(object)) {
        object_sizes_[GetRef(object)] = ByteSizeOf(object);
      }
    }

    // Store full shader and compile it if necessary.
    size_t shader_idx;
    RETURN_IF_ERROR(
        AddFullShader(code.source_code, workgroup_size, &shader_idx));
    programs_.push_back({
        std::move(code.parameters),
        std::move(code.objects),
        workgroup_size,
        num_workgroups,
        shader_idx,
    });
    return absl::OkStatus();
  }

  // Store full shader and compile it if necessary.
  // Returns full_shader_index
  absl::Status AddFullShader(const std::string& partial_shader,
                             const uint3& workgroup_size, size_t* size) {
    std::string shader_src = GetShaderHeader(workgroup_size) + partial_shader;
    auto it = shader_to_index_.find(shader_src);
    if (it == shader_to_index_.end()) {
      GlShader shader;
      RETURN_IF_ERROR(
          GlShader::CompileShader(GL_COMPUTE_SHADER, shader_src, &shader));
      shaders_.push_back(std::move(shader));
      shader_to_index_.insert({shader_src, shader_to_index_.size()});
      *size = shader_to_index_.size() - 1;
    } else {
      *size = it->second;
    }
    return absl::OkStatus();
  }

  absl::Status NewRun(
      const RuntimeOptions& options, const ObjectManager* objects,
      CommandQueue* command_queue,
      std::unique_ptr<InferenceContext>* inference_context) const final {
    std::unique_ptr<ObjectManager> refs;
    if (dynamic_batch_) {
      // Runtime is using objects from refs that will point to provided objects.
      // At this point just create 0 batch slice references.
      refs = absl::make_unique<ObjectManager>();
      for (const auto& s : object_sizes_) {
        auto buffer = objects->FindBuffer(s.first);
        if (!buffer) continue;
        GlBuffer ref;
        RETURN_IF_ERROR(buffer->MakeView(0, s.second, &ref));
        RETURN_IF_ERROR(refs->RegisterBuffer(s.first, std::move(ref)));
      }
    }
    auto runtime = absl::make_unique<Runtime>(options, gpu_info_, command_queue,
                                              refs ? refs.get() : objects);
    for (auto& c : programs_) {
      RETURN_IF_ERROR(runtime->AddProgram(shaders_[c.shader_idx], c.parameters,
                                          c.objects, c.num_workgroups));
    }
    RETURN_IF_ERROR(runtime->PrepareForExecution());
    if (dynamic_batch_) {
      *inference_context = absl::make_unique<InferenceContextWithBatchImpl>(
          object_sizes_, objects, std::move(refs), std::move(runtime));
    } else {
      *inference_context =
          absl::make_unique<InferenceContextImpl>(std::move(runtime));
    }
    return absl::OkStatus();
  }

#ifndef TFLITE_GPU_BINARY_RELEASE
  // Called on deserialization
  absl::Status OnProgram(const std::vector<Variable>& parameters,
                         const std::vector<Object>& objects,
                         const uint3& workgroup_size,
                         const uint3& num_workgroups,
                         size_t partial_shader_index) final {
    for (auto& object : objects) {
      if (IsRef(object)) {
        object_sizes_[GetRef(object)] = ByteSizeOf(object);
      }
    }

    size_t shader_idx;
    RETURN_IF_ERROR(AddFullShader(partial_shaders_[partial_shader_index],
                                  workgroup_size, &shader_idx));
    programs_.push_back({
        parameters,
        objects,
        workgroup_size,
        num_workgroups,
        shader_idx,
    });
    return absl::OkStatus();
  }

  absl::Status Serialize(
      std::vector<uint8_t>* serialized_compiled_model) const final {
    SerializedCompiledModelBuilder builder;

    // sort shaders first. They need to be serialized in order.
    std::vector<std::string> full_shaders(shaders_.size());
    for (const auto& shader : shader_to_index_) {
      full_shaders[shader.second] = shader.first;
    }

    std::unordered_map<std::string, size_t> partial_shader_to_index;
    std::vector<std::string> partial_shaders;
    for (const auto& program : programs_) {
      // Remove a header from a shader.
      std::string shader_without_header = full_shaders[program.shader_idx];
      shader_without_header.erase(0, shader_without_header.find("in;") + 3);

      // Insert shader into partial shaders array.
      auto it = partial_shader_to_index.find(shader_without_header);
      size_t shader_idx;
      if (it == partial_shader_to_index.end()) {
        shader_idx = partial_shaders.size();
        partial_shaders.push_back(shader_without_header);
        builder.AddShader(shader_without_header);
        partial_shader_to_index.insert({shader_without_header, shader_idx});
      } else {
        shader_idx = it->second;
      }
      builder.AddProgram(program.parameters, program.objects,
                         program.workgroup_size, program.num_workgroups,
                         shader_idx);
    }
    CompiledModelOptions options;
    options.dynamic_batch = dynamic_batch_;
    auto data = builder.Finalize(options);
    serialized_compiled_model->insert(serialized_compiled_model->end(),
                                      data.begin(), data.end());
    return absl::OkStatus();
  }

  absl::Status OnShader(absl::Span<const char> shader_src) final {
    std::string source(shader_src.data(), shader_src.size());
    partial_shaders_.push_back(source);
    return absl::OkStatus();
  }

  void OnOptions(const CompiledModelOptions& options) final {
    dynamic_batch_ = options.dynamic_batch;
  }
#endif  // TFLITE_GPU_BINARY_RELEASE

  CompilerStats stats() const final { return stats_; }

  void set_dynamic_batch(bool dynamic_batch) { dynamic_batch_ = dynamic_batch; }

 private:
  const GpuInfo gpu_info_;
  bool dynamic_batch_ = false;

  std::vector<std::string> partial_shaders_;
  std::vector<GlShader> shaders_;

  // Shaders are serialized in order of their indices.
  std::unordered_map<std::string, size_t> shader_to_index_;
  std::deque<ProgramParameters> programs_;
  std::unordered_map<ValueId, size_t> object_sizes_;
  CompilerStats stats_;
};
}  // namespace

absl::Status Compile(const CompilationOptions& options,
                     const GraphFloat32& model,
                     const std::unordered_set<int>& tflite_graph_io,
                     const NodeShader& node_shader,
                     const WorkgroupsCalculator& workgroup_calculator,
                     std::unique_ptr<CompiledModel>* compiled_model) {
  if (!IsBatchMatchesForAllValues(model)) {
    return absl::InvalidArgumentError(
        "Only identical batch dimension is supported");
  }
  GpuInfo gpu_info;
  RETURN_IF_ERROR(RequestGpuInfo(&gpu_info));
  if (!IsOpenGl31OrAbove(gpu_info)) {
    return absl::InternalError(
        "OpenGL ES 3.1 or above is required to use OpenGL inference.");
  }
  auto compiled_model_impl = absl::make_unique<CompiledModelImpl>(gpu_info);
  compiled_model_impl->set_dynamic_batch(options.dynamic_batch);
  auto compiler = NewCompiler(&node_shader, &gpu_info, options);
  RETURN_IF_ERROR(compiler->Compile(
      model, tflite_graph_io, [&](ShaderCode code) -> absl::Status {
        return compiled_model_impl->Add(workgroup_calculator, std::move(code));
      }));
  *compiled_model = std::move(compiled_model_impl);
  return absl::OkStatus();
}

#ifndef TFLITE_GPU_BINARY_RELEASE
absl::Status ReadSerializedModel(
    const std::vector<uint8_t>& serialized_model,
    std::unique_ptr<CompiledModel>* compiled_model) {
  GpuInfo gpu_info;
  RETURN_IF_ERROR(RequestGpuInfo(&gpu_info));
  if (!IsOpenGl31OrAbove(gpu_info)) {
    return absl::InternalError(
        "OpenGL ES 3.1 or above is required to use OpenGL inference.");
  }
  auto compiled_model_impl = absl::make_unique<CompiledModelImpl>(gpu_info);
  RETURN_IF_ERROR(DeserializeCompiledModel(
      absl::MakeConstSpan(serialized_model), compiled_model_impl.get()));
  *compiled_model = std::move(compiled_model_impl);
  return absl::OkStatus();
}
#endif  // TFLITE_GPU_BINARY_RELEASE

}  // namespace gl
}  // namespace gpu
}  // namespace tflite
<commit_msg>Rename tmp variable c to program to avoid name collision.<commit_after>/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

#include "tensorflow/lite/delegates/gpu/gl/api.h"

#include <algorithm>
#include <cstdint>
#include <deque>
#include <mutex>  // NOLINT
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/common/util.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_call.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_gl31.h"
#include "tensorflow/lite/delegates/gpu/gl/request_gpu_info.h"
#include "tensorflow/lite/delegates/gpu/gl/runtime.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"

#ifndef TFLITE_GPU_BINARY_RELEASE
#include "tensorflow/lite/delegates/gpu/gl/serialization.h"
#endif  // TFLITE_GPU_BINARY_RELEASE

namespace tflite {
namespace gpu {
namespace gl {
namespace {

using ObjectsSizes = std::unordered_map<ValueId, size_t>;

enum class InferenceContextState {
  NOT_STARTED,
  IN_PROGRESS,
};

class InferenceContextImpl : public InferenceContext {
 public:
  explicit InferenceContextImpl(std::unique_ptr<Runtime> runtime)
      : runtime_(std::move(runtime)) {}

  absl::Status Execute() final {
    std::lock_guard<std::mutex> lock(guard_);
    if (state_ != InferenceContextState::NOT_STARTED) {
      return absl::FailedPreconditionError("InferenceContext is not reset");
    }
    state_ = InferenceContextState::IN_PROGRESS;
    return runtime_->Execute();
  }

  absl::Status Reset() final {
    std::lock_guard<std::mutex> lock(guard_);
    // TODO(akulik): should Reset not return Status?
    state_ = InferenceContextState::NOT_STARTED;
    return absl::OkStatus();
  }

  RuntimeStats stats() const final { return runtime_->stats(); }

 private:
  std::unique_ptr<Runtime> runtime_;

  mutable std::mutex guard_;
  InferenceContextState state_ = InferenceContextState::NOT_STARTED;
};

class InferenceContextWithBatchImpl : public InferenceContext {
 public:
  InferenceContextWithBatchImpl(const ObjectsSizes& sizes,
                                const ObjectManager* objects,
                                std::unique_ptr<ObjectManager> refs,
                                std::unique_ptr<Runtime> runtime)
      : sizes_(sizes),
        objects_(objects),
        refs_(std::move(refs)),
        runtime_(std::move(runtime)) {}

  absl::Status Execute() final {
    std::lock_guard<std::mutex> lock(guard_);
    if (state_ != InferenceContextState::NOT_STARTED) {
      return absl::FailedPreconditionError("InferenceContext is not reset");
    }
    state_ = InferenceContextState::IN_PROGRESS;

    // Calculate expected number of batches and check that all external objects
    // match that number.
    int num_batches = 0;
    for (const auto& s : sizes_) {
      const ValueId id = s.first;
      const size_t byte_size = s.second;

      auto buffer = objects_->FindBuffer(id);
      if (!buffer) continue;

      if (buffer->bytes_size() % byte_size) {
        return absl::InvalidArgumentError(absl::StrCat(
            "Object ", id, " does not match expected byte size: ", byte_size));
      }

      const size_t b = buffer->bytes_size() / byte_size;
      if (num_batches == 0) {
        num_batches = b;
      } else if (num_batches != b) {
        return absl::InvalidArgumentError(absl::StrCat(
            "Object ", id, " size does not match expected batch size: ", b,
            " vs ", num_batches));
      }
    }

    for (size_t b = 0; b < num_batches; ++b) {
      // slice external objects by batch.
      for (const auto& s : sizes_) {
        const ValueId id = s.first;
        const size_t byte_size = s.second;
        auto buffer = objects_->FindBuffer(id);
        if (buffer) {
          auto ref = refs_->FindBuffer(id);
          if (!ref) {
            return absl::InvalidArgumentError(
                absl::StrCat("Reference to ", id, " is not found"));
          }
          RETURN_IF_ERROR(buffer->MakeView(b * byte_size, byte_size, ref));
        }
      }
      RETURN_IF_ERROR(runtime_->Execute());
    }
    return absl::OkStatus();
  }

  absl::Status Reset() final {
    std::lock_guard<std::mutex> lock(guard_);
    state_ = InferenceContextState::NOT_STARTED;
    // TODO(akulik): should Reset not return Status?
    return absl::OkStatus();
  }

  RuntimeStats stats() const final { return runtime_->stats(); }

 private:
  const ObjectsSizes sizes_;
  const ObjectManager* objects_;

  // view over external objects provided by a user.
  std::unique_ptr<ObjectManager> refs_;
  std::unique_ptr<Runtime> runtime_;

  mutable std::mutex guard_;
  InferenceContextState state_ = InferenceContextState::NOT_STARTED;
};

struct ProgramParameters {
  // A list of uniform parameters to be set.
  std::vector<Variable> parameters;

  // A list of objects to bind to opengl program.
  std::vector<Object> objects;

  uint3 workgroup_size;
  uint3 num_workgroups;

  size_t shader_idx;
};

std::string GetShaderHeader(uint3 localsize) {
  return absl::StrCat("#version 310 es\nlayout(local_size_x = ", localsize.x,
                      ", local_size_y = ", localsize.y,
                      ", local_size_z = ", localsize.z, ") in;\n");
}

class CompiledModelImpl
#ifndef TFLITE_GPU_BINARY_RELEASE
    : public CompiledModel,
      public DeserializationHandler {
#else
    : public CompiledModel {
#endif  // TFLITE_GPU_BINARY_RELEASE
 public:
  explicit CompiledModelImpl(const GpuInfo& gpu_info) : gpu_info_(gpu_info) {}

  // Called while compiling shaders from scratch
  absl::Status Add(const WorkgroupsCalculator& workgroup_calculator,
                   ShaderCode code) {
    // Calculate workgroup size.
    uint3 workgroup_size = workgroup_calculator.Calculate(code);
    uint3 num_workgroups = IntegralDivideRoundUp(code.workload, workgroup_size);

    for (const auto& object : code.objects) {
      if (IsRef(object)) {
        object_sizes_[GetRef(object)] = ByteSizeOf(object);
      }
    }

    // Store full shader and compile it if necessary.
    size_t shader_idx;
    RETURN_IF_ERROR(
        AddFullShader(code.source_code, workgroup_size, &shader_idx));
    programs_.push_back({
        std::move(code.parameters),
        std::move(code.objects),
        workgroup_size,
        num_workgroups,
        shader_idx,
    });
    return absl::OkStatus();
  }

  // Store full shader and compile it if necessary.
  // Returns full_shader_index
  absl::Status AddFullShader(const std::string& partial_shader,
                             const uint3& workgroup_size, size_t* size) {
    std::string shader_src = GetShaderHeader(workgroup_size) + partial_shader;
    auto it = shader_to_index_.find(shader_src);
    if (it == shader_to_index_.end()) {
      GlShader shader;
      RETURN_IF_ERROR(
          GlShader::CompileShader(GL_COMPUTE_SHADER, shader_src, &shader));
      shaders_.push_back(std::move(shader));
      shader_to_index_.insert({shader_src, shader_to_index_.size()});
      *size = shader_to_index_.size() - 1;
    } else {
      *size = it->second;
    }
    return absl::OkStatus();
  }

  absl::Status NewRun(
      const RuntimeOptions& options, const ObjectManager* objects,
      CommandQueue* command_queue,
      std::unique_ptr<InferenceContext>* inference_context) const final {
    std::unique_ptr<ObjectManager> refs;
    if (dynamic_batch_) {
      // Runtime is using objects from refs that will point to provided objects.
      // At this point just create 0 batch slice references.
      refs = absl::make_unique<ObjectManager>();
      for (const auto& s : object_sizes_) {
        auto buffer = objects->FindBuffer(s.first);
        if (!buffer) continue;
        GlBuffer ref;
        RETURN_IF_ERROR(buffer->MakeView(0, s.second, &ref));
        RETURN_IF_ERROR(refs->RegisterBuffer(s.first, std::move(ref)));
      }
    }
    auto runtime = absl::make_unique<Runtime>(options, gpu_info_, command_queue,
                                              refs ? refs.get() : objects);
    for (auto& program : programs_) {
      RETURN_IF_ERROR(runtime->AddProgram(shaders_[program.shader_idx],
                                          program.parameters, program.objects,
                                          program.num_workgroups));
    }
    RETURN_IF_ERROR(runtime->PrepareForExecution());
    if (dynamic_batch_) {
      *inference_context = absl::make_unique<InferenceContextWithBatchImpl>(
          object_sizes_, objects, std::move(refs), std::move(runtime));
    } else {
      *inference_context =
          absl::make_unique<InferenceContextImpl>(std::move(runtime));
    }
    return absl::OkStatus();
  }

#ifndef TFLITE_GPU_BINARY_RELEASE
  // Called on deserialization
  absl::Status OnProgram(const std::vector<Variable>& parameters,
                         const std::vector<Object>& objects,
                         const uint3& workgroup_size,
                         const uint3& num_workgroups,
                         size_t partial_shader_index) final {
    for (auto& object : objects) {
      if (IsRef(object)) {
        object_sizes_[GetRef(object)] = ByteSizeOf(object);
      }
    }

    size_t shader_idx;
    RETURN_IF_ERROR(AddFullShader(partial_shaders_[partial_shader_index],
                                  workgroup_size, &shader_idx));
    programs_.push_back({
        parameters,
        objects,
        workgroup_size,
        num_workgroups,
        shader_idx,
    });
    return absl::OkStatus();
  }

  absl::Status Serialize(
      std::vector<uint8_t>* serialized_compiled_model) const final {
    SerializedCompiledModelBuilder builder;

    // sort shaders first. They need to be serialized in order.
    std::vector<std::string> full_shaders(shaders_.size());
    for (const auto& shader : shader_to_index_) {
      full_shaders[shader.second] = shader.first;
    }

    std::unordered_map<std::string, size_t> partial_shader_to_index;
    std::vector<std::string> partial_shaders;
    for (const auto& program : programs_) {
      // Remove a header from a shader.
      std::string shader_without_header = full_shaders[program.shader_idx];
      shader_without_header.erase(0, shader_without_header.find("in;") + 3);

      // Insert shader into partial shaders array.
      auto it = partial_shader_to_index.find(shader_without_header);
      size_t shader_idx;
      if (it == partial_shader_to_index.end()) {
        shader_idx = partial_shaders.size();
        partial_shaders.push_back(shader_without_header);
        builder.AddShader(shader_without_header);
        partial_shader_to_index.insert({shader_without_header, shader_idx});
      } else {
        shader_idx = it->second;
      }
      builder.AddProgram(program.parameters, program.objects,
                         program.workgroup_size, program.num_workgroups,
                         shader_idx);
    }
    CompiledModelOptions options;
    options.dynamic_batch = dynamic_batch_;
    auto data = builder.Finalize(options);
    serialized_compiled_model->insert(serialized_compiled_model->end(),
                                      data.begin(), data.end());
    return absl::OkStatus();
  }

  absl::Status OnShader(absl::Span<const char> shader_src) final {
    std::string source(shader_src.data(), shader_src.size());
    partial_shaders_.push_back(source);
    return absl::OkStatus();
  }

  void OnOptions(const CompiledModelOptions& options) final {
    dynamic_batch_ = options.dynamic_batch;
  }
#endif  // TFLITE_GPU_BINARY_RELEASE

  CompilerStats stats() const final { return stats_; }

  void set_dynamic_batch(bool dynamic_batch) { dynamic_batch_ = dynamic_batch; }

 private:
  const GpuInfo gpu_info_;
  bool dynamic_batch_ = false;

  std::vector<std::string> partial_shaders_;
  std::vector<GlShader> shaders_;

  // Shaders are serialized in order of their indices.
  std::unordered_map<std::string, size_t> shader_to_index_;
  std::deque<ProgramParameters> programs_;
  std::unordered_map<ValueId, size_t> object_sizes_;
  CompilerStats stats_;
};
}  // namespace

absl::Status Compile(const CompilationOptions& options,
                     const GraphFloat32& model,
                     const std::unordered_set<int>& tflite_graph_io,
                     const NodeShader& node_shader,
                     const WorkgroupsCalculator& workgroup_calculator,
                     std::unique_ptr<CompiledModel>* compiled_model) {
  if (!IsBatchMatchesForAllValues(model)) {
    return absl::InvalidArgumentError(
        "Only identical batch dimension is supported");
  }
  GpuInfo gpu_info;
  RETURN_IF_ERROR(RequestGpuInfo(&gpu_info));
  if (!IsOpenGl31OrAbove(gpu_info)) {
    return absl::InternalError(
        "OpenGL ES 3.1 or above is required to use OpenGL inference.");
  }
  auto compiled_model_impl = absl::make_unique<CompiledModelImpl>(gpu_info);
  compiled_model_impl->set_dynamic_batch(options.dynamic_batch);
  auto compiler = NewCompiler(&node_shader, &gpu_info, options);
  RETURN_IF_ERROR(compiler->Compile(
      model, tflite_graph_io, [&](ShaderCode code) -> absl::Status {
        return compiled_model_impl->Add(workgroup_calculator, std::move(code));
      }));
  *compiled_model = std::move(compiled_model_impl);
  return absl::OkStatus();
}

#ifndef TFLITE_GPU_BINARY_RELEASE
absl::Status ReadSerializedModel(
    const std::vector<uint8_t>& serialized_model,
    std::unique_ptr<CompiledModel>* compiled_model) {
  GpuInfo gpu_info;
  RETURN_IF_ERROR(RequestGpuInfo(&gpu_info));
  if (!IsOpenGl31OrAbove(gpu_info)) {
    return absl::InternalError(
        "OpenGL ES 3.1 or above is required to use OpenGL inference.");
  }
  auto compiled_model_impl = absl::make_unique<CompiledModelImpl>(gpu_info);
  RETURN_IF_ERROR(DeserializeCompiledModel(
      absl::MakeConstSpan(serialized_model), compiled_model_impl.get()));
  *compiled_model = std::move(compiled_model_impl);
  return absl::OkStatus();
}
#endif  // TFLITE_GPU_BINARY_RELEASE

}  // namespace gl
}  // namespace gpu
}  // namespace tflite
<|endoftext|>


<commit_before>// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -triple x86_64-apple-darwin12 -analyzer-config cfg-temporary-dtors=true -std=c++11 %s > %t 2>&1
// RUN: FileCheck --input-file=%t %s

class C {
public:
  C();
  C(C *);
};

typedef __typeof(sizeof(int)) size_t;
void *operator new(size_t size, void *placement);

namespace operator_new {

// CHECK: void operatorNewWithConstructor()
// CHECK:          1: CFGNewAllocator(C *)
// CHECK-NEXT:     2:  (CXXConstructExpr, [B1.3], class C)
// CHECK-NEXT:     3: new C([B1.2])
void operatorNewWithConstructor() {
  new C();
}

// CHECK: void operatorNewWithConstructorWithOperatorNewWithContstructor()
// CHECK:          1: CFGNewAllocator(C *)
// CHECK-NEXT:     2: CFGNewAllocator(C *)
// CHECK-NEXT:     3:  (CXXConstructExpr, [B1.4], class C)
// CHECK-NEXT:     4: new C([B1.3])
// CHECK-NEXT:     5: [B1.4] (CXXConstructExpr, [B1.6], class C)
// CHECK-NEXT:     6: new C([B1.5])
void operatorNewWithConstructorWithOperatorNewWithContstructor() {
	new C(new C());
}

// CHECK: void operatorPlacementNewWithConstructorWithinPlacementArgument()
// CHECK:          1: CFGNewAllocator(C *)
// CHECK-NEXT:     2:  (CXXConstructExpr, [B1.3], class C)
// CHECK-NEXT:     3: new C([B1.2])
// CHECK-NEXT:     4: [B1.3] (ImplicitCastExpr, BitCast, void *)
// CHECK-NEXT:     5: CFGNewAllocator(C *)
// CHECK-NEXT:     6:  (CXXConstructExpr, [B1.7], class C)
// CHECK-NEXT:     7: new ([B1.4]) C([B1.6])
void operatorPlacementNewWithConstructorWithinPlacementArgument() {
	new (new C()) C();
}

} // namespace operator_new
<commit_msg>[CFG] Add construction context for simple variable declarations.<commit_after>// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -triple x86_64-apple-darwin12 -analyzer-config cfg-temporary-dtors=true -std=c++11 %s > %t 2>&1
// RUN: FileCheck --input-file=%t %s

class C {
public:
  C();
  C(C *);

  static C get();
};

typedef __typeof(sizeof(int)) size_t;
void *operator new(size_t size, void *placement);

namespace operator_new {

// CHECK: void operatorNewWithConstructor()
// CHECK:          1: CFGNewAllocator(C *)
// CHECK-NEXT:     2:  (CXXConstructExpr, [B1.3], class C)
// CHECK-NEXT:     3: new C([B1.2])
void operatorNewWithConstructor() {
  new C();
}

// CHECK: void operatorNewWithConstructorWithOperatorNewWithContstructor()
// CHECK:          1: CFGNewAllocator(C *)
// CHECK-NEXT:     2: CFGNewAllocator(C *)
// CHECK-NEXT:     3:  (CXXConstructExpr, [B1.4], class C)
// CHECK-NEXT:     4: new C([B1.3])
// CHECK-NEXT:     5: [B1.4] (CXXConstructExpr, [B1.6], class C)
// CHECK-NEXT:     6: new C([B1.5])
void operatorNewWithConstructorWithOperatorNewWithContstructor() {
	new C(new C());
}

// CHECK: void operatorPlacementNewWithConstructorWithinPlacementArgument()
// CHECK:          1: CFGNewAllocator(C *)
// CHECK-NEXT:     2:  (CXXConstructExpr, [B1.3], class C)
// CHECK-NEXT:     3: new C([B1.2])
// CHECK-NEXT:     4: [B1.3] (ImplicitCastExpr, BitCast, void *)
// CHECK-NEXT:     5: CFGNewAllocator(C *)
// CHECK-NEXT:     6:  (CXXConstructExpr, [B1.7], class C)
// CHECK-NEXT:     7: new ([B1.4]) C([B1.6])
void operatorPlacementNewWithConstructorWithinPlacementArgument() {
	new (new C()) C();
}

} // namespace operator_new

namespace decl_stmt {

// CHECK: void simpleVariable()
// CHECK:          1:  (CXXConstructExpr, [B1.2], class C)
// CHECK-NEXT:     2: C c;
void simpleVariable() {
  C c;
}

// CHECK: void simpleVariableWithBraces()
// CHECK:          1: {} (CXXConstructExpr, [B1.2], class C)
// CHECK-NEXT:     2: C c{};
void simpleVariableWithBraces() {
  C c{};
}

// CHECK: void simpleVariableWithConstructorArgument()
// CHECK:          1: 0
// CHECK-NEXT:     2: [B1.1] (ImplicitCastExpr, NullToPointer, class C *)
// CHECK-NEXT:     3: [B1.2] (CXXConstructExpr, [B1.4], class C)
// CHECK-NEXT:     4: C c(0);
void simpleVariableWithConstructorArgument() {
  C c(0);
}

// CHECK: void simpleVariableWithOperatorNewInConstructorArgument()
// CHECK:          1: CFGNewAllocator(C *)
// CHECK-NEXT:     2:  (CXXConstructExpr, [B1.3], class C)
// CHECK-NEXT:     3: new C([B1.2])
// CHECK-NEXT:     4: [B1.3] (CXXConstructExpr, [B1.5], class C)
// CHECK-NEXT:     5: C c(new C());
void simpleVariableWithOperatorNewInConstructorArgument() {
  C c(new C());
}

// CHECK: void simpleVariableWithOperatorNewInBraces()
// CHECK:          1: CFGNewAllocator(C *)
// CHECK-NEXT:     2:  (CXXConstructExpr, [B1.3], class C)
// CHECK-NEXT:     3: new C([B1.2])
// CHECK-NEXT:     4: {[B1.3]} (CXXConstructExpr, [B1.5], class C)
// CHECK-NEXT:     5: C c{new C()};
void simpleVariableWithOperatorNewInBraces() {
  C c{new C()};
}

// TODO: Should find construction target here.
// CHECK: void simpleVariableInitializedByValue()
// CHECK:          1: C::get
// CHECK-NEXT:     2: [B1.1] (ImplicitCastExpr, FunctionToPointerDecay, class C (*)(void))
// CHECK-NEXT:     3: [B1.2]()
// CHECK-NEXT:     4: [B1.3]
// CHECK-NEXT:     5: [B1.4] (CXXConstructExpr, class C)
// CHECK-NEXT:     6: C c = C::get();
void simpleVariableInitializedByValue() {
  C c = C::get();
}

// TODO: Should find construction target here.
// CHECK: void referenceVariableWithConstructor()
// CHECK:          1: 0
// CHECK-NEXT:     2: [B1.1] (ImplicitCastExpr, NullToPointer, class C *)
// CHECK-NEXT:     3: [B1.2] (CXXConstructExpr, const class C)
// CHECK-NEXT:     4: [B1.3]
// CHECK-NEXT:     5: const C &c(0);
void referenceVariableWithConstructor() {
  const C &c(0);
}

// TODO: Should find construction target here.
// CHECK: void referenceVariableWithInitializer()
// CHECK:          1: C() (CXXConstructExpr, class C)
// CHECK-NEXT:     2: [B1.1] (ImplicitCastExpr, NoOp, const class C)
// CHECK-NEXT:     3: [B1.2]
// CHECK-NEXT:     4: const C &c = C();
void referenceVariableWithInitializer() {
  const C &c = C();
}

} // end namespace decl_stmt
<|endoftext|>


<commit_before>#include <thread>
#include <vector>
#include <algorithm>
#include <numeric>
#include <cassert>
#include <iostream>
#include <agency/execution_policy.hpp>

template<class Iterator, class T, class BinaryFunction>
T reduce(agency::sequential_execution_policy, Iterator first, Iterator last, T init, BinaryFunction binary_op)
{
  return std::accumulate(first, last, init, binary_op);
}

template<class Iterator, class T, class BinaryFunction>
T reduce(Iterator first, Iterator last, T init, BinaryFunction binary_op)
{
  auto n = std::distance(first, last);

  // chop up data into partitions
  auto partition_size = 10000;
  auto num_partitions = (n + partition_size - 1) / partition_size;
  std::vector<T> partial_sums(num_partitions);

  agency::bulk_invoke(agency::par(num_partitions), [=,&partial_sums](agency::parallel_agent& g)
  {
    auto i = g.index();

    auto partition_begin = first + partition_size * i;
    auto partition_end   = std::min(last, partition_begin + partition_size);

    partial_sums[i] = reduce(agency::seq, partition_begin + 1, partition_end, *partition_begin, binary_op);
  });

  return reduce(agency::seq, partial_sums.begin(), partial_sums.end(), init, binary_op);
}

int main()
{
  size_t n = 1 << 20;
  std::vector<int> data(n);
  std::generate(data.begin(), data.end(), [](){ return std::rand() % 20; });

  assert(reduce(data.begin(), data.end(), 0, std::plus<int>()) == accumulate(data.begin(), data.end(), 0, std::plus<int>()));

  std::cout << "OK" << std::endl;

  return 0;
}

<commit_msg>Receive partial_sums as a result from bulk_invoke()<commit_after>#include <thread>
#include <vector>
#include <algorithm>
#include <numeric>
#include <cassert>
#include <iostream>
#include <agency/execution_policy.hpp>

template<class Iterator, class T, class BinaryFunction>
T reduce(agency::sequential_execution_policy, Iterator first, Iterator last, T init, BinaryFunction binary_op)
{
  return std::accumulate(first, last, init, binary_op);
}

template<class Iterator, class T, class BinaryFunction>
T reduce(Iterator first, Iterator last, T init, BinaryFunction binary_op)
{
  auto n = std::distance(first, last);

  // chop up data into partitions
  auto partition_size = 10000;
  auto num_partitions = (n + partition_size - 1) / partition_size;

  auto partial_sums = agency::bulk_invoke(agency::par(num_partitions), [=](agency::parallel_agent& g)
  {
    auto i = g.index();

    auto partition_begin = first + partition_size * i;
    auto partition_end   = std::min(last, partition_begin + partition_size);

    return reduce(agency::seq, partition_begin + 1, partition_end, *partition_begin, binary_op);
  });

  return reduce(agency::seq, partial_sums.begin(), partial_sums.end(), init, binary_op);
}

int main()
{
  size_t n = 1 << 20;
  std::vector<int> data(n);
  std::generate(data.begin(), data.end(), [](){ return std::rand() % 20; });

  assert(reduce(data.begin(), data.end(), 0, std::plus<int>()) == accumulate(data.begin(), data.end(), 0, std::plus<int>()));

  std::cout << "OK" << std::endl;

  return 0;
}

<|endoftext|>


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

#include "System/LRUCache.hpp"

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <vector>

using namespace sw;

namespace {

template<typename Cache>
void checkRange(const Cache &cache, std::vector<std::pair<typename Cache::Key, typename Cache::Data>> list)
{
	size_t i = 0;
	for(auto it : cache)
	{
		ASSERT_EQ(list[i].first, it.key());
		ASSERT_EQ(list[i].second, it.data());
		i++;
	}
	ASSERT_EQ(i, list.size());
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// LRUCache
////////////////////////////////////////////////////////////////////////////////
TEST(LRUCache, Empty)
{
	LRUCache<std::string, std::string> cache(8);
	ASSERT_EQ(cache.lookup(""), "");
	ASSERT_EQ(cache.lookup("123"), "");
	for(auto ignored : cache)
	{
		(void)ignored;
		FAIL() << "Should not loop on empty cache";
	}
}

TEST(LRUCache, AddNoEviction)
{
	LRUCache<std::string, std::string> cache(4);

	cache.add("1", "one");
	cache.add("2", "two");
	cache.add("3", "three");
	cache.add("4", "four");

	ASSERT_EQ(cache.lookup("1"), "one");
	ASSERT_EQ(cache.lookup("2"), "two");
	ASSERT_EQ(cache.lookup("3"), "three");
	ASSERT_EQ(cache.lookup("4"), "four");

	checkRange(cache, {
	                      { "4", "four" },
	                      { "3", "three" },
	                      { "2", "two" },
	                      { "1", "one" },
	                  });
}

TEST(LRUCache, AddWithEviction)
{
	LRUCache<std::string, std::string> cache(4);

	cache.add("1", "one");
	cache.add("2", "two");
	cache.add("3", "three");
	cache.add("4", "four");
	cache.add("5", "five");
	cache.add("6", "six");

	ASSERT_EQ(cache.lookup("1"), "");
	ASSERT_EQ(cache.lookup("2"), "");
	ASSERT_EQ(cache.lookup("3"), "three");
	ASSERT_EQ(cache.lookup("4"), "four");
	ASSERT_EQ(cache.lookup("5"), "five");
	ASSERT_EQ(cache.lookup("6"), "six");

	checkRange(cache, {
	                      { "6", "six" },
	                      { "5", "five" },
	                      { "4", "four" },
	                      { "3", "three" },
	                  });
}

TEST(LRUCache, AddClearAdd)
{
	LRUCache<std::string, std::string> cache(4);

	// Add some data.
	cache.add("1", "one");
	cache.add("2", "two");
	cache.add("3", "three");
	cache.add("4", "four");
	cache.add("5", "five");
	cache.add("6", "six");

	// Clear it.
	cache.clear();

	// Check has no data.
	ASSERT_EQ(cache.lookup("1"), "");
	ASSERT_EQ(cache.lookup("2"), "");
	ASSERT_EQ(cache.lookup("3"), "");
	ASSERT_EQ(cache.lookup("4"), "");
	ASSERT_EQ(cache.lookup("5"), "");
	ASSERT_EQ(cache.lookup("6"), "");

	checkRange(cache, {});

	// Add it again.
	cache.add("1", "one");
	cache.add("2", "two");
	cache.add("3", "three");
	cache.add("4", "four");
	cache.add("5", "five");
	cache.add("6", "six");

	// Check has data.
	ASSERT_EQ(cache.lookup("1"), "");
	ASSERT_EQ(cache.lookup("2"), "");
	ASSERT_EQ(cache.lookup("3"), "three");
	ASSERT_EQ(cache.lookup("4"), "four");
	ASSERT_EQ(cache.lookup("5"), "five");
	ASSERT_EQ(cache.lookup("6"), "six");

	checkRange(cache, {
	                      { "6", "six" },
	                      { "5", "five" },
	                      { "4", "four" },
	                      { "3", "three" },
	                  });
}

TEST(LRUCache, Reordering)
{
	LRUCache<std::string, std::string> cache(4);

	// Fill
	cache.add("1", "one");
	cache.add("2", "two");
	cache.add("3", "three");
	cache.add("4", "four");

	// Push 2 then 4 to most recent
	cache.add("2", "two");
	cache.add("4", "four");

	checkRange(cache, {
	                      { "4", "four" },
	                      { "2", "two" },
	                      { "3", "three" },
	                      { "1", "one" },
	                  });
}<commit_msg>Fix chrome autorollers<commit_after>// Copyright 2020 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "System/LRUCache.hpp"

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <vector>

using namespace sw;

namespace {

template<typename Cache>
void checkRange(const Cache &cache, std::vector<std::pair<typename Cache::Key, typename Cache::Data>> list)
{
	size_t i = 0;
	for(auto it : cache)
	{
		ASSERT_EQ(list[i].first, it.key());
		ASSERT_EQ(list[i].second, it.data());
		i++;
	}
	ASSERT_EQ(i, list.size());
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// LRUCache
////////////////////////////////////////////////////////////////////////////////
TEST(LRUCache, Empty)
{
	LRUCache<std::string, std::string> cache(8);
	ASSERT_EQ(cache.lookup(""), "");
	ASSERT_EQ(cache.lookup("123"), "");
	bool looped = false;
	for(auto ignored : cache)
	{
		(void)ignored;
		looped = true;
	}
	if(looped)
	{
		FAIL() << "Should not loop on empty cache";
	}
}

TEST(LRUCache, AddNoEviction)
{
	LRUCache<std::string, std::string> cache(4);

	cache.add("1", "one");
	cache.add("2", "two");
	cache.add("3", "three");
	cache.add("4", "four");

	ASSERT_EQ(cache.lookup("1"), "one");
	ASSERT_EQ(cache.lookup("2"), "two");
	ASSERT_EQ(cache.lookup("3"), "three");
	ASSERT_EQ(cache.lookup("4"), "four");

	checkRange(cache, {
	                      { "4", "four" },
	                      { "3", "three" },
	                      { "2", "two" },
	                      { "1", "one" },
	                  });
}

TEST(LRUCache, AddWithEviction)
{
	LRUCache<std::string, std::string> cache(4);

	cache.add("1", "one");
	cache.add("2", "two");
	cache.add("3", "three");
	cache.add("4", "four");
	cache.add("5", "five");
	cache.add("6", "six");

	ASSERT_EQ(cache.lookup("1"), "");
	ASSERT_EQ(cache.lookup("2"), "");
	ASSERT_EQ(cache.lookup("3"), "three");
	ASSERT_EQ(cache.lookup("4"), "four");
	ASSERT_EQ(cache.lookup("5"), "five");
	ASSERT_EQ(cache.lookup("6"), "six");

	checkRange(cache, {
	                      { "6", "six" },
	                      { "5", "five" },
	                      { "4", "four" },
	                      { "3", "three" },
	                  });
}

TEST(LRUCache, AddClearAdd)
{
	LRUCache<std::string, std::string> cache(4);

	// Add some data.
	cache.add("1", "one");
	cache.add("2", "two");
	cache.add("3", "three");
	cache.add("4", "four");
	cache.add("5", "five");
	cache.add("6", "six");

	// Clear it.
	cache.clear();

	// Check has no data.
	ASSERT_EQ(cache.lookup("1"), "");
	ASSERT_EQ(cache.lookup("2"), "");
	ASSERT_EQ(cache.lookup("3"), "");
	ASSERT_EQ(cache.lookup("4"), "");
	ASSERT_EQ(cache.lookup("5"), "");
	ASSERT_EQ(cache.lookup("6"), "");

	checkRange(cache, {});

	// Add it again.
	cache.add("1", "one");
	cache.add("2", "two");
	cache.add("3", "three");
	cache.add("4", "four");
	cache.add("5", "five");
	cache.add("6", "six");

	// Check has data.
	ASSERT_EQ(cache.lookup("1"), "");
	ASSERT_EQ(cache.lookup("2"), "");
	ASSERT_EQ(cache.lookup("3"), "three");
	ASSERT_EQ(cache.lookup("4"), "four");
	ASSERT_EQ(cache.lookup("5"), "five");
	ASSERT_EQ(cache.lookup("6"), "six");

	checkRange(cache, {
	                      { "6", "six" },
	                      { "5", "five" },
	                      { "4", "four" },
	                      { "3", "three" },
	                  });
}

TEST(LRUCache, Reordering)
{
	LRUCache<std::string, std::string> cache(4);

	// Fill
	cache.add("1", "one");
	cache.add("2", "two");
	cache.add("3", "three");
	cache.add("4", "four");

	// Push 2 then 4 to most recent
	cache.add("2", "two");
	cache.add("4", "four");

	checkRange(cache, {
	                      { "4", "four" },
	                      { "2", "two" },
	                      { "3", "three" },
	                      { "1", "one" },
	                  });
}<|endoftext|>


<commit_before>/*
 *  ______                   __  __              __
 * /\  _  \           __    /\ \/\ \            /\ \__
 * \ \ \L\ \  __  __ /\_\   \_\ \ \ \____    ___\ \ ,_\   ____
 *  \ \  __ \/\ \/\ \\/\ \  /'_` \ \ '__`\  / __`\ \ \/  /',__\
 *   \ \ \/\ \ \ \_/ |\ \ \/\ \L\ \ \ \L\ \/\ \L\ \ \ \_/\__, `\
 *    \ \_\ \_\ \___/  \ \_\ \___,_\ \_,__/\ \____/\ \__\/\____/
 *     \/_/\/_/\/__/    \/_/\/__,_ /\/___/  \/___/  \/__/\/___/
 * @copyright Copyright 2017 Avidbots Corp.
 * @name  model_tf_publisher_test.cpp
 * @brief test model TF publisher plugin
 * @author Chunshang Li
 *
 * Software License Agreement (BSD License)
 *
 *  Copyright (c) 2017, Avidbots Corp.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the Avidbots Corp. nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

#include <flatland_plugins/model_tf_publisher.h>
#include <flatland_server/exceptions.h>
#include <flatland_server/model_plugin.h>
#include <flatland_server/timekeeper.h>
#include <flatland_server/world.h>
#include <geometry_msgs/TransformStamped.h>
#include <gtest/gtest.h>
#include <tf/transform_datatypes.h>
#include <tf2_ros/transform_listener.h>
#include <regex>

namespace fs = boost::filesystem;
using namespace flatland_server;
using namespace flatland_plugins;

class ModelTfPublisherTest : public ::testing::Test {
 public:
  boost::filesystem::path this_file_dir;
  boost::filesystem::path world_yaml;
  World* w;

  void SetUp() override {
    this_file_dir = boost::filesystem::path(__FILE__).parent_path();
    w = nullptr;
  }

  void TearDown() override {
    if (w != nullptr) {
      delete w;
    }
  }

  static bool fltcmp(const double& n1, const double& n2) {
    if (std::isinf(n1) && std::isinf(n2)) {
      return true;
    }

    if (std::isnan(n1) && std::isnan(n2)) {
      return true;
    }

    bool ret = fabs(n1 - n2) < 1e-5;
    return ret;
  }

  // Test if transform equals to expected
  bool TfEq(const geometry_msgs::TransformStamped& tf, float x, float y,
            float a) {
    tf::Quaternion q;
    tf::quaternionMsgToTF(tf.transform.rotation, q);
    tf::Matrix3x3 rot_matrix(q);
    double roll, pitch, yaw;
    rot_matrix.getRPY(roll, pitch, yaw);

    if (!fltcmp(x, tf.transform.translation.x) ||
        !fltcmp(y, tf.transform.translation.y) ||
        !fltcmp(0, tf.transform.translation.z) || !fltcmp(roll, 0) ||
        !fltcmp(pitch, 0) || !fltcmp(yaw, a)) {
      printf("Transformation\n");
      printf("Actual: x=%f y=%f z=%f, roll=%f pitch=%f yaw=%f\n",
             tf.transform.translation.x, tf.transform.translation.y,
             tf.transform.translation.z, roll, pitch, yaw);
      printf("Expected: x=%f y=%f z=%f, roll=%f pitch=%f yaw=%f\n", x, y, 0.0,
             0.0, 0.0, a);
      return false;
    }

    return true;
  }
};

/**
 * Test the transformation for the model robot in a given plugin configuration
 */
TEST_F(ModelTfPublisherTest, tf_publish_test_A) {
  world_yaml =
      this_file_dir /
      fs::path("model_tf_publisher_tests/tf_publish_test_A/world.yaml");

  Timekeeper timekeeper;
  timekeeper.SetMaxStepSize(1.0);
  w = World::MakeWorld(world_yaml.string());
  ModelTfPublisher* p = dynamic_cast<ModelTfPublisher*>(
      w->plugin_manager_.model_plugins_[0].get());

  EXPECT_DOUBLE_EQ(5000.0, p->update_rate_);
  EXPECT_STREQ("antenna", p->reference_body_->name_.c_str());

  tf2_ros::Buffer tf_buffer;
  tf2_ros::TransformListener tf_listener(tf_buffer);
  geometry_msgs::TransformStamped tf_world_to_base;
  geometry_msgs::TransformStamped tf_world_to_antenna;
  geometry_msgs::TransformStamped tf_base_to_left_wheel;
  geometry_msgs::TransformStamped tf_base_to_right_wheel;
  geometry_msgs::TransformStamped tf_base_to_front_bumper;
  geometry_msgs::TransformStamped tf_base_to_rear_bumper;

  // let it spin for 10 times to make sure the message gets through
  ros::WallRate rate(500);
  for (int i = 0; i < 10; i++) {
    w->Update(timekeeper);
    ros::spinOnce();
    rate.sleep();
  }

  // check for the transformations that should exist
  tf_world_to_base =
      tf_buffer.lookupTransform("world", "my_robot/base", ros::Time(0));
  tf_world_to_antenna =
      tf_buffer.lookupTransform("world", "my_robot/antenna", ros::Time(0));
  tf_base_to_left_wheel = tf_buffer.lookupTransform(
      "my_robot/base", "my_robot/left_wheel", ros::Time(0));
  tf_base_to_right_wheel = tf_buffer.lookupTransform(
      "my_robot/base", "my_robot/right_wheel", ros::Time(0));

  // check for the transformations that should not exist
  try {
    tf_base_to_front_bumper = tf_buffer.lookupTransform(
        "my_robot/base", "my_robot/front_bumper", ros::Time(0));
    ADD_FAILURE() << "Expected an exception, but none were raised";
  } catch (const tf2::TransformException& e) {
    EXPECT_STREQ(
        "\"my_robot/front_bumper\" passed to lookupTransform argument "
        "source_frame does not exist. ",
        e.what());
  }

  try {
    tf_base_to_rear_bumper = tf_buffer.lookupTransform(
        "my_robot/base", "my_robot/rear_bumper", ros::Time(0));
    ADD_FAILURE() << "Expected an exception, but none were raised";
  } catch (const tf2::TransformException& e) {
    EXPECT_STREQ(
        "\"my_robot/rear_bumper\" passed to lookupTransform argument "
        "source_frame does not exist. ",
        e.what());
  }

  // check transformations are correct
  EXPECT_TRUE(TfEq(tf_world_to_base, 8, 6, -0.575958653));
  EXPECT_TRUE(TfEq(tf_world_to_antenna, 8, 6, -0.575958653));
  EXPECT_TRUE(TfEq(tf_base_to_left_wheel, -0.25, 1, 0));
  EXPECT_TRUE(TfEq(tf_base_to_right_wheel, -0.25, -1, 0));
}

/**
 * Test the transformation for the model robot in another plugin configuration
 */
TEST_F(ModelTfPublisherTest, tf_publish_test_B) {
  world_yaml =
      this_file_dir /
      fs::path("model_tf_publisher_tests/tf_publish_test_B/world.yaml");

  Timekeeper timekeeper;
  timekeeper.SetMaxStepSize(1.0);
  w = World::MakeWorld(world_yaml.string());
  ModelTfPublisher* p = dynamic_cast<ModelTfPublisher*>(
      w->plugin_manager_.model_plugins_[0].get());

  EXPECT_DOUBLE_EQ(std::numeric_limits<double>::infinity(), p->update_rate_);
  EXPECT_STREQ("base", p->reference_body_->name_.c_str());

  tf2_ros::Buffer tf_buffer;
  tf2_ros::TransformListener tf_listener(tf_buffer);
  geometry_msgs::TransformStamped tf_map_to_base;
  geometry_msgs::TransformStamped tf_base_to_antenna;
  geometry_msgs::TransformStamped tf_base_to_left_wheel;
  geometry_msgs::TransformStamped tf_base_to_right_wheel;
  geometry_msgs::TransformStamped tf_base_to_front_bumper;
  geometry_msgs::TransformStamped tf_base_to_rear_bumper;

  // let it spin for 10 times to make sure the message gets through
  ros::WallRate rate(50);
  for (int i = 0; i < 10; i++) {
    w->Update(timekeeper);
    ros::spinOnce();
    rate.sleep();
  }

  tf_base_to_antenna =
      tf_buffer.lookupTransform("base", "antenna", ros::Time(0));
  tf_base_to_left_wheel =
      tf_buffer.lookupTransform("base", "left_wheel", ros::Time(0));
  tf_base_to_right_wheel =
      tf_buffer.lookupTransform("base", "right_wheel", ros::Time(0));
  tf_base_to_front_bumper =
      tf_buffer.lookupTransform("base", "front_bumper", ros::Time(0));
  tf_base_to_rear_bumper =
      tf_buffer.lookupTransform("base", "rear_bumper", ros::Time(0));

  try {
    tf_map_to_base = tf_buffer.lookupTransform("map", "base", ros::Time(0));
    ADD_FAILURE() << "Expected an exception, but none were raised";
  } catch (const tf2::TransformException& e) {
    EXPECT_STREQ(
        "\"map\" passed to lookupTransform argument target_frame does not "
        "exist. ",
        e.what());
  }

  EXPECT_TRUE(TfEq(tf_base_to_antenna, 0, 0, 0));
  EXPECT_TRUE(TfEq(tf_base_to_left_wheel, -0.25, 1, 0));
  EXPECT_TRUE(TfEq(tf_base_to_right_wheel, -0.25, -1, 0));
  EXPECT_TRUE(TfEq(tf_base_to_front_bumper, 2, 0, 0));
  EXPECT_TRUE(TfEq(tf_base_to_rear_bumper, -2, 0, 0));
}

/**
 * Test the transformation for the provided model yaml, which will fail due
 * to a nonexistent reference body
 */
TEST_F(ModelTfPublisherTest, invalid_A) {
  world_yaml =
      this_file_dir / fs::path("model_tf_publisher_tests/invalid_A/world.yaml");

  try {
    w = World::MakeWorld(world_yaml.string());

    FAIL() << "Expected an exception, but none were raised";
  } catch (const PluginException& e) {
    std::cmatch match;
    std::string regex_str = ".*Body with name \"random_body\" does not exist.*";
    std::regex regex(regex_str);
    EXPECT_TRUE(std::regex_match(e.what(), match, regex))
        << "Exception Message '" + std::string(e.what()) + "'" +
               " did not match against regex '" + regex_str + "'";
  } catch (const std::exception& e) {
    ADD_FAILURE() << "Was expecting a PluginException, another exception was "
                     "caught instead: "
                  << e.what();
  }
}

/**
 * Test the transformation for the provided model yaml, which will fail due
 * to a nonexistent body specified in the exclude list
 */
TEST_F(ModelTfPublisherTest, invalid_B) {
  world_yaml =
      this_file_dir / fs::path("model_tf_publisher_tests/invalid_B/world.yaml");

  try {
    w = World::MakeWorld(world_yaml.string());

    FAIL() << "Expected an exception, but none were raised";
  } catch (const PluginException& e) {
    std::cmatch match;
    std::string regex_str =
        ".*Body with name \"random_body_1\" does not exist.*";
    std::regex regex(regex_str);
    EXPECT_TRUE(std::regex_match(e.what(), match, regex))
        << "Exception Message '" + std::string(e.what()) + "'" +
               " did not match against regex '" + regex_str + "'";
  } catch (const std::exception& e) {
    ADD_FAILURE() << "Was expecting a PluginException, another exception was "
                     "caught instead: "
                  << e.what();
  }
}

// Run all the tests that were declared with TEST()
int main(int argc, char** argv) {
  ros::init(argc, argv, "model_tf_plugin_test");
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
<commit_msg>increase number of iterations to make sure the message goes through<commit_after>/*
 *  ______                   __  __              __
 * /\  _  \           __    /\ \/\ \            /\ \__
 * \ \ \L\ \  __  __ /\_\   \_\ \ \ \____    ___\ \ ,_\   ____
 *  \ \  __ \/\ \/\ \\/\ \  /'_` \ \ '__`\  / __`\ \ \/  /',__\
 *   \ \ \/\ \ \ \_/ |\ \ \/\ \L\ \ \ \L\ \/\ \L\ \ \ \_/\__, `\
 *    \ \_\ \_\ \___/  \ \_\ \___,_\ \_,__/\ \____/\ \__\/\____/
 *     \/_/\/_/\/__/    \/_/\/__,_ /\/___/  \/___/  \/__/\/___/
 * @copyright Copyright 2017 Avidbots Corp.
 * @name  model_tf_publisher_test.cpp
 * @brief test model TF publisher plugin
 * @author Chunshang Li
 *
 * Software License Agreement (BSD License)
 *
 *  Copyright (c) 2017, Avidbots Corp.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the Avidbots Corp. nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

#include <flatland_plugins/model_tf_publisher.h>
#include <flatland_server/exceptions.h>
#include <flatland_server/model_plugin.h>
#include <flatland_server/timekeeper.h>
#include <flatland_server/world.h>
#include <geometry_msgs/TransformStamped.h>
#include <gtest/gtest.h>
#include <tf/transform_datatypes.h>
#include <tf2_ros/transform_listener.h>
#include <regex>

namespace fs = boost::filesystem;
using namespace flatland_server;
using namespace flatland_plugins;

class ModelTfPublisherTest : public ::testing::Test {
 public:
  boost::filesystem::path this_file_dir;
  boost::filesystem::path world_yaml;
  World* w;

  void SetUp() override {
    this_file_dir = boost::filesystem::path(__FILE__).parent_path();
    w = nullptr;
  }

  void TearDown() override {
    if (w != nullptr) {
      delete w;
    }
  }

  static bool fltcmp(const double& n1, const double& n2) {
    if (std::isinf(n1) && std::isinf(n2)) {
      return true;
    }

    if (std::isnan(n1) && std::isnan(n2)) {
      return true;
    }

    bool ret = fabs(n1 - n2) < 1e-5;
    return ret;
  }

  // Test if transform equals to expected
  bool TfEq(const geometry_msgs::TransformStamped& tf, float x, float y,
            float a) {
    tf::Quaternion q;
    tf::quaternionMsgToTF(tf.transform.rotation, q);
    tf::Matrix3x3 rot_matrix(q);
    double roll, pitch, yaw;
    rot_matrix.getRPY(roll, pitch, yaw);

    if (!fltcmp(x, tf.transform.translation.x) ||
        !fltcmp(y, tf.transform.translation.y) ||
        !fltcmp(0, tf.transform.translation.z) || !fltcmp(roll, 0) ||
        !fltcmp(pitch, 0) || !fltcmp(yaw, a)) {
      printf("Transformation\n");
      printf("Actual: x=%f y=%f z=%f, roll=%f pitch=%f yaw=%f\n",
             tf.transform.translation.x, tf.transform.translation.y,
             tf.transform.translation.z, roll, pitch, yaw);
      printf("Expected: x=%f y=%f z=%f, roll=%f pitch=%f yaw=%f\n", x, y, 0.0,
             0.0, 0.0, a);
      return false;
    }

    return true;
  }
};

/**
 * Test the transformation for the model robot in a given plugin configuration
 */
TEST_F(ModelTfPublisherTest, tf_publish_test_A) {
  world_yaml =
      this_file_dir /
      fs::path("model_tf_publisher_tests/tf_publish_test_A/world.yaml");

  Timekeeper timekeeper;
  timekeeper.SetMaxStepSize(1.0);
  w = World::MakeWorld(world_yaml.string());
  ModelTfPublisher* p = dynamic_cast<ModelTfPublisher*>(
      w->plugin_manager_.model_plugins_[0].get());

  EXPECT_DOUBLE_EQ(5000.0, p->update_rate_);
  EXPECT_STREQ("antenna", p->reference_body_->name_.c_str());

  tf2_ros::Buffer tf_buffer;
  tf2_ros::TransformListener tf_listener(tf_buffer);
  geometry_msgs::TransformStamped tf_world_to_base;
  geometry_msgs::TransformStamped tf_world_to_antenna;
  geometry_msgs::TransformStamped tf_base_to_left_wheel;
  geometry_msgs::TransformStamped tf_base_to_right_wheel;
  geometry_msgs::TransformStamped tf_base_to_front_bumper;
  geometry_msgs::TransformStamped tf_base_to_rear_bumper;

  // let it spin for 10 times to make sure the message gets through
  ros::WallRate rate(500);
  for (int i = 0; i < 100; i++) {
    w->Update(timekeeper);
    ros::spinOnce();
    rate.sleep();
  }

  // check for the transformations that should exist
  tf_world_to_base =
      tf_buffer.lookupTransform("world", "my_robot/base", ros::Time(0));
  tf_world_to_antenna =
      tf_buffer.lookupTransform("world", "my_robot/antenna", ros::Time(0));
  tf_base_to_left_wheel = tf_buffer.lookupTransform(
      "my_robot/base", "my_robot/left_wheel", ros::Time(0));
  tf_base_to_right_wheel = tf_buffer.lookupTransform(
      "my_robot/base", "my_robot/right_wheel", ros::Time(0));

  // check for the transformations that should not exist
  try {
    tf_base_to_front_bumper = tf_buffer.lookupTransform(
        "my_robot/base", "my_robot/front_bumper", ros::Time(0));
    ADD_FAILURE() << "Expected an exception, but none were raised";
  } catch (const tf2::TransformException& e) {
    EXPECT_STREQ(
        "\"my_robot/front_bumper\" passed to lookupTransform argument "
        "source_frame does not exist. ",
        e.what());
  }

  try {
    tf_base_to_rear_bumper = tf_buffer.lookupTransform(
        "my_robot/base", "my_robot/rear_bumper", ros::Time(0));
    ADD_FAILURE() << "Expected an exception, but none were raised";
  } catch (const tf2::TransformException& e) {
    EXPECT_STREQ(
        "\"my_robot/rear_bumper\" passed to lookupTransform argument "
        "source_frame does not exist. ",
        e.what());
  }

  // check transformations are correct
  EXPECT_TRUE(TfEq(tf_world_to_base, 8, 6, -0.575958653));
  EXPECT_TRUE(TfEq(tf_world_to_antenna, 8, 6, -0.575958653));
  EXPECT_TRUE(TfEq(tf_base_to_left_wheel, -0.25, 1, 0));
  EXPECT_TRUE(TfEq(tf_base_to_right_wheel, -0.25, -1, 0));
}

/**
 * Test the transformation for the model robot in another plugin configuration
 */
TEST_F(ModelTfPublisherTest, tf_publish_test_B) {
  world_yaml =
      this_file_dir /
      fs::path("model_tf_publisher_tests/tf_publish_test_B/world.yaml");

  Timekeeper timekeeper;
  timekeeper.SetMaxStepSize(1.0);
  w = World::MakeWorld(world_yaml.string());
  ModelTfPublisher* p = dynamic_cast<ModelTfPublisher*>(
      w->plugin_manager_.model_plugins_[0].get());

  EXPECT_DOUBLE_EQ(std::numeric_limits<double>::infinity(), p->update_rate_);
  EXPECT_STREQ("base", p->reference_body_->name_.c_str());

  tf2_ros::Buffer tf_buffer;
  tf2_ros::TransformListener tf_listener(tf_buffer);
  geometry_msgs::TransformStamped tf_map_to_base;
  geometry_msgs::TransformStamped tf_base_to_antenna;
  geometry_msgs::TransformStamped tf_base_to_left_wheel;
  geometry_msgs::TransformStamped tf_base_to_right_wheel;
  geometry_msgs::TransformStamped tf_base_to_front_bumper;
  geometry_msgs::TransformStamped tf_base_to_rear_bumper;

  // let it spin for 10 times to make sure the message gets through
  ros::WallRate rate(500);
  for (int i = 0; i < 100; i++) {
    w->Update(timekeeper);
    ros::spinOnce();
    rate.sleep();
  }

  tf_base_to_antenna =
      tf_buffer.lookupTransform("base", "antenna", ros::Time(0));
  tf_base_to_left_wheel =
      tf_buffer.lookupTransform("base", "left_wheel", ros::Time(0));
  tf_base_to_right_wheel =
      tf_buffer.lookupTransform("base", "right_wheel", ros::Time(0));
  tf_base_to_front_bumper =
      tf_buffer.lookupTransform("base", "front_bumper", ros::Time(0));
  tf_base_to_rear_bumper =
      tf_buffer.lookupTransform("base", "rear_bumper", ros::Time(0));

  try {
    tf_map_to_base = tf_buffer.lookupTransform("map", "base", ros::Time(0));
    ADD_FAILURE() << "Expected an exception, but none were raised";
  } catch (const tf2::TransformException& e) {
    EXPECT_STREQ(
        "\"map\" passed to lookupTransform argument target_frame does not "
        "exist. ",
        e.what());
  }

  EXPECT_TRUE(TfEq(tf_base_to_antenna, 0, 0, 0));
  EXPECT_TRUE(TfEq(tf_base_to_left_wheel, -0.25, 1, 0));
  EXPECT_TRUE(TfEq(tf_base_to_right_wheel, -0.25, -1, 0));
  EXPECT_TRUE(TfEq(tf_base_to_front_bumper, 2, 0, 0));
  EXPECT_TRUE(TfEq(tf_base_to_rear_bumper, -2, 0, 0));
}

/**
 * Test the transformation for the provided model yaml, which will fail due
 * to a nonexistent reference body
 */
TEST_F(ModelTfPublisherTest, invalid_A) {
  world_yaml =
      this_file_dir / fs::path("model_tf_publisher_tests/invalid_A/world.yaml");

  try {
    w = World::MakeWorld(world_yaml.string());

    FAIL() << "Expected an exception, but none were raised";
  } catch (const PluginException& e) {
    std::cmatch match;
    std::string regex_str = ".*Body with name \"random_body\" does not exist.*";
    std::regex regex(regex_str);
    EXPECT_TRUE(std::regex_match(e.what(), match, regex))
        << "Exception Message '" + std::string(e.what()) + "'" +
               " did not match against regex '" + regex_str + "'";
  } catch (const std::exception& e) {
    ADD_FAILURE() << "Was expecting a PluginException, another exception was "
                     "caught instead: "
                  << e.what();
  }
}

/**
 * Test the transformation for the provided model yaml, which will fail due
 * to a nonexistent body specified in the exclude list
 */
TEST_F(ModelTfPublisherTest, invalid_B) {
  world_yaml =
      this_file_dir / fs::path("model_tf_publisher_tests/invalid_B/world.yaml");

  try {
    w = World::MakeWorld(world_yaml.string());

    FAIL() << "Expected an exception, but none were raised";
  } catch (const PluginException& e) {
    std::cmatch match;
    std::string regex_str =
        ".*Body with name \"random_body_1\" does not exist.*";
    std::regex regex(regex_str);
    EXPECT_TRUE(std::regex_match(e.what(), match, regex))
        << "Exception Message '" + std::string(e.what()) + "'" +
               " did not match against regex '" + regex_str + "'";
  } catch (const std::exception& e) {
    ADD_FAILURE() << "Was expecting a PluginException, another exception was "
                     "caught instead: "
                  << e.what();
  }
}

// Run all the tests that were declared with TEST()
int main(int argc, char** argv) {
  ros::init(argc, argv, "model_tf_plugin_test");
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
<|endoftext|>


<commit_before>/*
 * <one line to give the library's name and an idea of what it does.>
 * Copyright (C) 2014  <copyright holder> <email>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "keyboardlayoutconfig.h"
#include <QProcess>
#include <QFile>
#include <QHash>
#include <QDebug>
#include "selectkeyboardlayoutdialog.h"
#include <LXQt/Settings>

KeyboardLayoutConfig::KeyboardLayoutConfig(LxQt::Settings* _settings, QWidget* parent):
  QWidget(parent),
  settings(_settings) {
  ui.setupUi(this);

  loadLists();
  loadSettings();
  initControls();

  connect(ui.addLayout, SIGNAL(clicked(bool)), SLOT(onAddLayout()));
  connect(ui.removeLayout, SIGNAL(clicked(bool)), SLOT(onRemoveLayout()));
  connect(ui.moveUp, SIGNAL(clicked(bool)), SLOT(onMoveUp()));
  connect(ui.moveDown, SIGNAL(clicked(bool)), SLOT(onMoveDown()));
  connect(ui.keyboardModel, SIGNAL(currentIndexChanged(int)), SLOT(accept()));
  connect(ui.switchKey, SIGNAL(currentIndexChanged(int)), SLOT(accept()));
}

KeyboardLayoutConfig::~KeyboardLayoutConfig() {
}

void KeyboardLayoutConfig::loadSettings() {
  // load current settings from the output of setxkbmap command
  QProcess setxkbmap;
  setxkbmap.start(QLatin1String("setxkbmap -query -verbose 5"));
  setxkbmap.waitForFinished();
  if(setxkbmap.exitStatus() == QProcess::NormalExit) {
    while(!setxkbmap.atEnd()) {
      QByteArray line = setxkbmap.readLine();
      if(line.startsWith("model:")) {
        keyboardModel_ = QString::fromLatin1(line.mid(6).trimmed());
      }
      else if(line.startsWith("layout:")) {
        QList<QByteArray> items = line.mid(7).trimmed().split(',');
        Q_FOREACH(QByteArray item, items) {
          QString lang;
          QString variant;
          int p = item.indexOf('(');
          if(p >= 0) { // has variant
            int p2 = item.lastIndexOf(')');
            if(p2 >= 0)
              variant = QString::fromLatin1(item.mid(p + 1, (p2 - p - 1)));
            lang = QString::fromLatin1(item.left(p));
          }
          else
            lang = QString::fromLatin1(item);
          // add the current lang/variant parit to the list
          currentLayouts_.append(QPair<QString, QString>(lang, variant));
        }
      }
      else if(line.startsWith("options:")) {
        QList<QByteArray> options = line.mid(9).trimmed().split(',');
        Q_FOREACH(QByteArray option, options) {
          if(option.startsWith("grp:"))
            switchKey_ = QString::fromLatin1(option);
          else
            currentOptions_ << QString::fromLatin1(option);
        }
      }
    }
    setxkbmap.close();
  }
}

enum ListSection{
  NoSection,
  ModelSection,
  LayoutSection,
  VariantSection,
  OptionSection
};

void KeyboardLayoutConfig::loadLists() {
  // load known lists from xkb data files
  // FIXME: maybe we should use different files for different OSes?
  QFile file(QLatin1String("/usr/share/X11/xkb/rules/base.lst"));
  if(file.open(QIODevice::ReadOnly)) {
    ListSection section = NoSection;
    while(!file.atEnd()) {
      QByteArray line = file.readLine().trimmed();
      if(section == NoSection) {
        if(line.startsWith("! model"))
          section = ModelSection;
        else if(line.startsWith("! layout"))
          section = LayoutSection;
        else if(line.startsWith("! variant"))
          section = VariantSection;
        else if(line.startsWith("! option"))
          section = OptionSection;
      }
      else {
        if(line.isEmpty()) {
          section = NoSection;
          continue;
        }
        int sep = line.indexOf(' ');
        QString name = QString::fromLatin1(line, sep);
        while(line[sep] == ' ') // skip spaces
          ++sep;
        QString description = QString::fromUtf8(line.constData() + sep);

        switch(section) {
          case ModelSection: {
            ui.keyboardModel->addItem(description, name);
            break;
          }
          case LayoutSection:
            knownLayouts_[name] = KeyboardLayoutInfo(description);
            break;
          case VariantSection: {
            // the descriptions of variants are prefixed by their language ids
            sep = description.indexOf(": ");
            if(sep >= 0) {
              QString lang = description.left(sep);
              QMap<QString, KeyboardLayoutInfo>::iterator it = knownLayouts_.find(lang);
              if(it != knownLayouts_.end()) {
                KeyboardLayoutInfo& info = *it;
                info.variants.append(LayoutVariantInfo(name, description.mid(sep + 2)));
              }
            }
            break;
          }
          case OptionSection:
            if(line.startsWith("grp:")) { // key used to switch to another layout
              ui.switchKey->addItem(description, name);
            }
            break;
          default:;
        }
      }
    }
    file.close();
  }
}

void KeyboardLayoutConfig::initControls() {
  QList<QPair<QString, QString> >::iterator it;
  for(it = currentLayouts_.begin(); it != currentLayouts_.end(); ++it) {
    QString name = it->first;
    QString variant = it->second;
    addLayout(name, variant);
  }

  int n = ui.keyboardModel->count();
  int row;
  for(row = 0; row < n; ++row) {
    if(ui.keyboardModel->itemData(row, Qt::UserRole).toString() == keyboardModel_) {
      ui.keyboardModel->setCurrentIndex(row);
      break;
    }
  }

  n = ui.switchKey->count();
  for(row = 0; row < n; ++row) {
    if(ui.switchKey->itemData(row, Qt::UserRole).toString() == switchKey_) {
      ui.switchKey->setCurrentIndex(row);
      break;
    }
  }

}

void KeyboardLayoutConfig::addLayout(QString name, QString variant) {
  qDebug() << "add" << name << variant;
  const KeyboardLayoutInfo& info = knownLayouts_.value(name);
  QTreeWidgetItem* item = new QTreeWidgetItem();
  item->setData(0, Qt::DisplayRole, info.description);
  item->setData(0, Qt::UserRole, name);
  const LayoutVariantInfo* vinfo = info.findVariant(variant);
  if(vinfo) {
    item->setData(1, Qt::DisplayRole, vinfo->description);
    item->setData(1, Qt::UserRole, variant);
  }
  ui.layouts->addTopLevelItem(item);
}

void KeyboardLayoutConfig::reset() {
  ui.layouts->clear();
  initControls();
  accept();
}

void KeyboardLayoutConfig::accept() {
  // call setxkbmap to apply the changes
  QProcess setxkbmap;
  // clear existing options
  setxkbmap.start("setxkbmap -option");
  setxkbmap.waitForFinished();
  setxkbmap.close();

  QString command = "setxkbmap";
  // set keyboard model
  QString model;
  int cur_model = ui.keyboardModel->currentIndex();
  if(cur_model >= 0) {
    model = ui.keyboardModel->itemData(cur_model, Qt::UserRole).toString();
    command += " -model ";
    command += model;
  }

  // set keyboard layout
  int n = ui.layouts->topLevelItemCount();
  QString layouts, variants;
  if(n > 0) {
    for(int row = 0; row < n; ++row) {
      QTreeWidgetItem* item = ui.layouts->topLevelItem(row);
      layouts += item->data(0, Qt::UserRole).toString();
      variants += item->data(1, Qt::UserRole).toString();
      if(row < n - 1) { // not the last row
        layouts += ',';
        variants += ',';
      }
    }
    command += " -layout ";
    command += layouts;
    command += " -variant ";
    command += variants;
  }

  Q_FOREACH(QString option, currentOptions_) {
    command += " -option ";
    command += option;
  }

  QString switchKey;
  int cur_switch_key = ui.switchKey->currentIndex();
  if(cur_switch_key > 0) { // index 0 is "None"
    switchKey = ui.switchKey->itemData(cur_switch_key, Qt::UserRole).toString();
    command += " -option ";
    command += switchKey;
  }

  qDebug() << command;

  // execute the command line
  setxkbmap.start(command);
  setxkbmap.waitForFinished();

  // save to lxqt-session config file.
  settings->beginGroup("Keyboard");
  settings->setValue("layout", layouts);
  settings->setValue("variant", variants);
  settings->setValue("model", model);
  if(switchKey.isEmpty() && currentOptions_ .isEmpty())
    settings->remove("options");
  else
    settings->setValue("options", switchKey.isEmpty() ? currentOptions_ : (currentOptions_ << switchKey));
  settings->endGroup();
}

void KeyboardLayoutConfig::onAddLayout() {
  SelectKeyboardLayoutDialog dlg(knownLayouts_, this);
  if(dlg.exec() == QDialog::Accepted) {
    addLayout(dlg.selectedLayout(), dlg.selectedVariant());
    accept();
  }
}

void KeyboardLayoutConfig::onRemoveLayout() {
  if(ui.layouts->topLevelItemCount() > 1) {
    QTreeWidgetItem* item = ui.layouts->currentItem();
    if(item) {
      delete item;
      accept();
    }
  }
}

void KeyboardLayoutConfig::onMoveDown() {
  QTreeWidgetItem* item = ui.layouts->currentItem();
  if(!item)
    return;
  int pos = ui.layouts->indexOfTopLevelItem(item);
  if(pos < ui.layouts->topLevelItemCount() - 1) { // not the last item
    ui.layouts->takeTopLevelItem(pos);
    ui.layouts->insertTopLevelItem(pos + 1, item);
    ui.layouts->setCurrentItem(item);
    accept();
  }
}

void KeyboardLayoutConfig::onMoveUp() {
  QTreeWidgetItem* item = ui.layouts->currentItem();
  if(!item)
    return;
  int pos = ui.layouts->indexOfTopLevelItem(item);
  if(pos > 0) { // not the first item
    ui.layouts->takeTopLevelItem(pos);
    ui.layouts->insertTopLevelItem(pos - 1, item);
    ui.layouts->setCurrentItem(item);
    accept();
  }
}

<commit_msg>Fix keyboard layout settings reading and writting<commit_after>/*
 * <one line to give the library's name and an idea of what it does.>
 * Copyright (C) 2014  <copyright holder> <email>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "keyboardlayoutconfig.h"
#include <QProcess>
#include <QFile>
#include <QHash>
#include <QDebug>
#include "selectkeyboardlayoutdialog.h"
#include <LXQt/Settings>

KeyboardLayoutConfig::KeyboardLayoutConfig(LxQt::Settings* _settings, QWidget* parent):
  QWidget(parent),
  settings(_settings) {
  ui.setupUi(this);

  loadLists();
  loadSettings();
  initControls();

  connect(ui.addLayout, SIGNAL(clicked(bool)), SLOT(onAddLayout()));
  connect(ui.removeLayout, SIGNAL(clicked(bool)), SLOT(onRemoveLayout()));
  connect(ui.moveUp, SIGNAL(clicked(bool)), SLOT(onMoveUp()));
  connect(ui.moveDown, SIGNAL(clicked(bool)), SLOT(onMoveDown()));
  connect(ui.keyboardModel, SIGNAL(currentIndexChanged(int)), SLOT(accept()));
  connect(ui.switchKey, SIGNAL(currentIndexChanged(int)), SLOT(accept()));
}

KeyboardLayoutConfig::~KeyboardLayoutConfig() {
}

void KeyboardLayoutConfig::loadSettings() {
  // load current settings from the output of setxkbmap command
  QProcess setxkbmap;
  setxkbmap.start(QLatin1String("setxkbmap -query -verbose 5"));
  setxkbmap.waitForFinished();
  if(setxkbmap.exitStatus() == QProcess::NormalExit) {
    QList<QByteArray> layouts, variants;
    while(!setxkbmap.atEnd()) {
      QByteArray line = setxkbmap.readLine();
      if(line.startsWith("model:")) {
        keyboardModel_ = QString::fromLatin1(line.mid(6).trimmed());
      }
      else if(line.startsWith("layout:")) {
        layouts = line.mid(7).trimmed().split(',');
      }
      else if(line.startsWith("variant:")) {
        variants = line.mid(8).trimmed().split(',');
      }
      else if(line.startsWith("options:")) {
        QList<QByteArray> options = line.mid(9).trimmed().split(',');
        Q_FOREACH(QByteArray option, options) {
          if(option.startsWith("grp:"))
            switchKey_ = QString::fromLatin1(option);
          else
            currentOptions_ << QString::fromLatin1(option);
        }
      }
    }

    const int size = layouts.size(), variantsSize = variants.size();
    for(int i = 0; i < size; ++i) {
      currentLayouts_.append(QPair<QString, QString>(layouts.at(i), variantsSize > 0 ? variants.at(i) : QString()));
    }

    setxkbmap.close();
  }
}

enum ListSection{
  NoSection,
  ModelSection,
  LayoutSection,
  VariantSection,
  OptionSection
};

void KeyboardLayoutConfig::loadLists() {
  // load known lists from xkb data files
  // FIXME: maybe we should use different files for different OSes?
  QFile file(QLatin1String("/usr/share/X11/xkb/rules/base.lst"));
  if(file.open(QIODevice::ReadOnly)) {
    ListSection section = NoSection;
    while(!file.atEnd()) {
      QByteArray line = file.readLine().trimmed();
      if(section == NoSection) {
        if(line.startsWith("! model"))
          section = ModelSection;
        else if(line.startsWith("! layout"))
          section = LayoutSection;
        else if(line.startsWith("! variant"))
          section = VariantSection;
        else if(line.startsWith("! option"))
          section = OptionSection;
      }
      else {
        if(line.isEmpty()) {
          section = NoSection;
          continue;
        }
        int sep = line.indexOf(' ');
        QString name = QString::fromLatin1(line, sep);
        while(line[sep] == ' ') // skip spaces
          ++sep;
        QString description = QString::fromUtf8(line.constData() + sep);

        switch(section) {
          case ModelSection: {
            ui.keyboardModel->addItem(description, name);
            break;
          }
          case LayoutSection:
            knownLayouts_[name] = KeyboardLayoutInfo(description);
            break;
          case VariantSection: {
            // the descriptions of variants are prefixed by their language ids
            sep = description.indexOf(": ");
            if(sep >= 0) {
              QString lang = description.left(sep);
              QMap<QString, KeyboardLayoutInfo>::iterator it = knownLayouts_.find(lang);
              if(it != knownLayouts_.end()) {
                KeyboardLayoutInfo& info = *it;
                info.variants.append(LayoutVariantInfo(name, description.mid(sep + 2)));
              }
            }
            break;
          }
          case OptionSection:
            if(line.startsWith("grp:")) { // key used to switch to another layout
              ui.switchKey->addItem(description, name);
            }
            break;
          default:;
        }
      }
    }
    file.close();
  }
}

void KeyboardLayoutConfig::initControls() {
  QList<QPair<QString, QString> >::iterator it;
  for(it = currentLayouts_.begin(); it != currentLayouts_.end(); ++it) {
    QString name = it->first;
    QString variant = it->second;
    addLayout(name, variant);
  }

  int n = ui.keyboardModel->count();
  int row;
  for(row = 0; row < n; ++row) {
    if(ui.keyboardModel->itemData(row, Qt::UserRole).toString() == keyboardModel_) {
      ui.keyboardModel->setCurrentIndex(row);
      break;
    }
  }

  n = ui.switchKey->count();
  for(row = 0; row < n; ++row) {
    if(ui.switchKey->itemData(row, Qt::UserRole).toString() == switchKey_) {
      ui.switchKey->setCurrentIndex(row);
      break;
    }
  }

}

void KeyboardLayoutConfig::addLayout(QString name, QString variant) {
  qDebug() << "add" << name << variant;
  const KeyboardLayoutInfo& info = knownLayouts_.value(name);
  QTreeWidgetItem* item = new QTreeWidgetItem();
  item->setData(0, Qt::DisplayRole, info.description);
  item->setData(0, Qt::UserRole, name);
  const LayoutVariantInfo* vinfo = info.findVariant(variant);
  if(vinfo) {
    item->setData(1, Qt::DisplayRole, vinfo->description);
    item->setData(1, Qt::UserRole, variant);
  }
  ui.layouts->addTopLevelItem(item);
}

void KeyboardLayoutConfig::reset() {
  ui.layouts->clear();
  initControls();
  accept();
}

void KeyboardLayoutConfig::accept() {
  // call setxkbmap to apply the changes
  QProcess setxkbmap;
  // clear existing options
  setxkbmap.start("setxkbmap -option");
  setxkbmap.waitForFinished();
  setxkbmap.close();

  QString command = "setxkbmap";
  // set keyboard model
  QString model;
  int cur_model = ui.keyboardModel->currentIndex();
  if(cur_model >= 0) {
    model = ui.keyboardModel->itemData(cur_model, Qt::UserRole).toString();
    command += " -model ";
    command += model;
  }

  // set keyboard layout
  int n = ui.layouts->topLevelItemCount();
  QString layouts, variants;
  if(n > 0) {
    for(int row = 0; row < n; ++row) {
      QTreeWidgetItem* item = ui.layouts->topLevelItem(row);
      layouts += item->data(0, Qt::UserRole).toString();
      variants += item->data(1, Qt::UserRole).toString();
      if(row < n - 1) { // not the last row
        layouts += ',';
        variants += ',';
      }
    }
    command += " -layout ";
    command += layouts;

    if (variants.indexOf(',') > -1 || !variants.isEmpty()) {
      command += " -variant ";
      command += variants;
    }
  }

  Q_FOREACH(QString option, currentOptions_) {
    if (!option.startsWith("grp:")) {
      command += " -option ";
      command += option;
    }
  }

  QString switchKey;
  int cur_switch_key = ui.switchKey->currentIndex();
  if(cur_switch_key > 0) { // index 0 is "None"
    switchKey = ui.switchKey->itemData(cur_switch_key, Qt::UserRole).toString();
    command += " -option ";
    command += switchKey;
  }

  qDebug() << command;

  // execute the command line
  setxkbmap.start(command);
  setxkbmap.waitForFinished();

  // save to lxqt-session config file.
  settings->beginGroup("Keyboard");
  settings->setValue("layout", layouts);
  settings->setValue("variant", variants);
  settings->setValue("model", model);
  if(switchKey.isEmpty() && currentOptions_ .isEmpty())
    settings->remove("options");
  else
    settings->setValue("options", switchKey.isEmpty() ? currentOptions_ : (currentOptions_ << switchKey));
  settings->endGroup();
}

void KeyboardLayoutConfig::onAddLayout() {
  SelectKeyboardLayoutDialog dlg(knownLayouts_, this);
  if(dlg.exec() == QDialog::Accepted) {
    addLayout(dlg.selectedLayout(), dlg.selectedVariant());
    accept();
  }
}

void KeyboardLayoutConfig::onRemoveLayout() {
  if(ui.layouts->topLevelItemCount() > 1) {
    QTreeWidgetItem* item = ui.layouts->currentItem();
    if(item) {
      delete item;
      accept();
    }
  }
}

void KeyboardLayoutConfig::onMoveDown() {
  QTreeWidgetItem* item = ui.layouts->currentItem();
  if(!item)
    return;
  int pos = ui.layouts->indexOfTopLevelItem(item);
  if(pos < ui.layouts->topLevelItemCount() - 1) { // not the last item
    ui.layouts->takeTopLevelItem(pos);
    ui.layouts->insertTopLevelItem(pos + 1, item);
    ui.layouts->setCurrentItem(item);
    accept();
  }
}

void KeyboardLayoutConfig::onMoveUp() {
  QTreeWidgetItem* item = ui.layouts->currentItem();
  if(!item)
    return;
  int pos = ui.layouts->indexOfTopLevelItem(item);
  if(pos > 0) { // not the first item
    ui.layouts->takeTopLevelItem(pos);
    ui.layouts->insertTopLevelItem(pos - 1, item);
    ui.layouts->setCurrentItem(item);
    accept();
  }
}

<|endoftext|>