<commit_before>////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2017 ArangoDB GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Simon Grätzer
////////////////////////////////////////////////////////////////////////////////

#include "RocksDBReplicationManager.h"
#include "Basics/MutexLocker.h"
#include "Logger/Logger.h"
#include "RocksDBEngine/RocksDBCommon.h"
#include "RocksDBEngine/RocksDBEngine.h"
#include "RocksDBEngine/RocksDBReplicationContext.h"

#include <velocypack/Builder.h>
#include <velocypack/velocypack-aliases.h>

using namespace arangodb;
using namespace arangodb::rocksutils;

size_t const RocksDBReplicationManager::MaxCollectCount = 32;

////////////////////////////////////////////////////////////////////////////////
/// @brief create a context repository
////////////////////////////////////////////////////////////////////////////////

RocksDBReplicationManager::RocksDBReplicationManager() : _lock(), _contexts() {
  _contexts.reserve(64);
}

////////////////////////////////////////////////////////////////////////////////
/// @brief destroy a context repository
////////////////////////////////////////////////////////////////////////////////

RocksDBReplicationManager::~RocksDBReplicationManager() {
  try {
    garbageCollect(true);
  } catch (...) {
  }

  // wait until all used contexts have vanished
  int tries = 0;

  while (true) {
    if (!containsUsedContext()) {
      break;
    }

    if (tries == 0) {
      LOG_TOPIC(INFO, arangodb::Logger::FIXME)
          << "waiting for used contexts to become unused";
    } else if (tries == 120) {
      LOG_TOPIC(WARN, arangodb::Logger::FIXME)
          << "giving up waiting for unused contexts";
    }

    usleep(500000);
    ++tries;
  }

  {
    MUTEX_LOCKER(mutexLocker, _lock);

    for (auto it : _contexts) {
      delete it.second;
    }

    _contexts.clear();
  }
}

//////////////////////////////////////////////////////////////////////////////
/// @brief creates a new context which must be later returned using release()
/// or destroy(); guarantees that RocksDB file deletion is disabled while
/// there are active contexts
//////////////////////////////////////////////////////////////////////////////

RocksDBReplicationContext* RocksDBReplicationManager::createContext() {
  auto context = std::make_unique<RocksDBReplicationContext>();
  TRI_ASSERT(context.get() != nullptr);
  TRI_ASSERT(context->isUsed());

  RocksDBReplicationId const id = context->id();

  {
    MUTEX_LOCKER(mutexLocker, _lock);

    _contexts.emplace(id, context.get());
    if (_contexts.size() == 1) {
      disableFileDeletions();
    }
  }
  return context.release();
}

////////////////////////////////////////////////////////////////////////////////
/// @brief remove a context by id
////////////////////////////////////////////////////////////////////////////////

bool RocksDBReplicationManager::remove(RocksDBReplicationId id) {
  RocksDBReplicationContext* context = nullptr;

  {
    MUTEX_LOCKER(mutexLocker, _lock);

    auto it = _contexts.find(id);
    if (it == _contexts.end()) {
      // not found
      return false;
    }

    context = it->second;
    TRI_ASSERT(context != nullptr);

    if (context->isDeleted()) {
      // already deleted
      return false;
    }

    if (context->isUsed()) {
      // context is in use by someone else. now mark as deleted
      context->deleted();
      return true;
    }

    // context not in use by someone else
    _contexts.erase(it);
    if (_contexts.empty()) {
      enableFileDeletions();
    }
  }

  TRI_ASSERT(context != nullptr);

  delete context;
  return true;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief find an existing context by id
/// if found, the context will be returned with the usage flag set to true.
/// it must be returned later using release()
////////////////////////////////////////////////////////////////////////////////

RocksDBReplicationContext* RocksDBReplicationManager::find(
    RocksDBReplicationId id, bool& busy, double ttl) {
  RocksDBReplicationContext* context = nullptr;
  busy = false;

  {
    MUTEX_LOCKER(mutexLocker, _lock);

    auto it = _contexts.find(id);
    if (it == _contexts.end()) {
      // not found
      return nullptr;
    }

    context = it->second;
    TRI_ASSERT(context != nullptr);

    if (context->isDeleted()) {
      // already deleted
      return nullptr;
    }

    if (context->isUsed()) {
      busy = true;
      return nullptr;
    }

    context->use(ttl);
  }

  return context;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief return a context for later use
////////////////////////////////////////////////////////////////////////////////

void RocksDBReplicationManager::release(RocksDBReplicationContext* context) {
  {
    MUTEX_LOCKER(mutexLocker, _lock);

    TRI_ASSERT(context->isUsed());
    context->release();

    if (!context->isDeleted()) {
      return;
    }

    // remove from the list
    _contexts.erase(context->id());
  }

  // and free the context
  delete context;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief return a context for garbage collection
////////////////////////////////////////////////////////////////////////////////

void RocksDBReplicationManager::destroy(RocksDBReplicationContext* context) {
  if (context != nullptr) {
    remove(context->id());
  }
}
////////////////////////////////////////////////////////////////////////////////
/// @brief whether or not the repository contains a used context
////////////////////////////////////////////////////////////////////////////////

bool RocksDBReplicationManager::containsUsedContext() {
  MUTEX_LOCKER(mutexLocker, _lock);

  for (auto it : _contexts) {
    if (it.second->isUsed()) {
      return true;
    }
  }

  return false;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief drop contexts by database (at least mark them as deleted)
////////////////////////////////////////////////////////////////////////////////

void RocksDBReplicationManager::drop(TRI_vocbase_t* vocbase) {
  {
    MUTEX_LOCKER(mutexLocker, _lock);

    for (auto& context : _contexts) {
      if (context.second->vocbase() == vocbase) {
        context.second->deleted();
      }
    }
  }

  garbageCollect(true);
}

////////////////////////////////////////////////////////////////////////////////
/// @brief drop all contexts (at least mark them as deleted)
////////////////////////////////////////////////////////////////////////////////

void RocksDBReplicationManager::dropAll() {
  {
    MUTEX_LOCKER(mutexLocker, _lock);

    for (auto& context : _contexts) {
      context.second->deleted();
    }
  }

  garbageCollect(true);
}

////////////////////////////////////////////////////////////////////////////////
/// @brief run a garbage collection on the contexts
////////////////////////////////////////////////////////////////////////////////

bool RocksDBReplicationManager::garbageCollect(bool force) {
  auto const now = TRI_microtime();
  std::vector<RocksDBReplicationContext*> found;

  try {
    found.reserve(MaxCollectCount);

    MUTEX_LOCKER(mutexLocker, _lock);

    for (auto it = _contexts.begin(); it != _contexts.end();
         /* no hoisting */) {
      auto context = it->second;

      if (context->isUsed()) {
        // must not destroy used contexts
        ++it;
        continue;
      }

      if (force || context->expires() < now) {
        context->deleted();
      }

      if (context->isDeleted()) {
        try {
          found.emplace_back(context);
          it = _contexts.erase(it);
        } catch (...) {
          // stop iteration
          break;
        }

        if (!force && found.size() >= MaxCollectCount) {
          break;
        }
      } else {
        ++it;
      }
    }

    // FIXME effectively force should only be called on shutdown
    // nevertheless this is quite ugly
    if (_contexts.size() == 0 && !force) {
      enableFileDeletions();
    }
  } catch (...) {
    // go on and remove whatever we found so far
  }

  // remove contexts outside the lock
  for (auto it : found) {
    delete it;
  }

  return (!found.empty());
}

void RocksDBReplicationManager::disableFileDeletions() {
  auto rocks = globalRocksDB();
  auto s = rocks->DisableFileDeletions();
  TRI_ASSERT(s.ok());
}

void RocksDBReplicationManager::enableFileDeletions() {
  auto rocks = globalRocksDB();
  auto s = rocks->DisableFileDeletions();
  TRI_ASSERT(s.ok());
}

RocksDBReplicationContextGuard::RocksDBReplicationContextGuard(
    RocksDBReplicationManager* manager, RocksDBReplicationContext* ctx)
    : _manager(manager), _ctx(ctx) {}

RocksDBReplicationContextGuard::~RocksDBReplicationContextGuard() {
  if (_ctx != nullptr) {
    _manager->release(_ctx);
  }
}
<commit_msg>Fixed logic bug preventing file deletions in RocksDB engine.<commit_after>////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2017 ArangoDB GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Simon Grätzer
////////////////////////////////////////////////////////////////////////////////

#include "RocksDBReplicationManager.h"
#include "Basics/MutexLocker.h"
#include "Logger/Logger.h"
#include "RocksDBEngine/RocksDBCommon.h"
#include "RocksDBEngine/RocksDBEngine.h"
#include "RocksDBEngine/RocksDBReplicationContext.h"

#include <velocypack/Builder.h>
#include <velocypack/velocypack-aliases.h>

using namespace arangodb;
using namespace arangodb::rocksutils;

size_t const RocksDBReplicationManager::MaxCollectCount = 32;

////////////////////////////////////////////////////////////////////////////////
/// @brief create a context repository
////////////////////////////////////////////////////////////////////////////////

RocksDBReplicationManager::RocksDBReplicationManager() : _lock(), _contexts() {
  _contexts.reserve(64);
}

////////////////////////////////////////////////////////////////////////////////
/// @brief destroy a context repository
////////////////////////////////////////////////////////////////////////////////

RocksDBReplicationManager::~RocksDBReplicationManager() {
  try {
    garbageCollect(true);
  } catch (...) {
  }

  // wait until all used contexts have vanished
  int tries = 0;

  while (true) {
    if (!containsUsedContext()) {
      break;
    }

    if (tries == 0) {
      LOG_TOPIC(INFO, arangodb::Logger::FIXME)
          << "waiting for used contexts to become unused";
    } else if (tries == 120) {
      LOG_TOPIC(WARN, arangodb::Logger::FIXME)
          << "giving up waiting for unused contexts";
    }

    usleep(500000);
    ++tries;
  }

  {
    MUTEX_LOCKER(mutexLocker, _lock);

    for (auto it : _contexts) {
      delete it.second;
    }

    _contexts.clear();
  }
}

//////////////////////////////////////////////////////////////////////////////
/// @brief creates a new context which must be later returned using release()
/// or destroy(); guarantees that RocksDB file deletion is disabled while
/// there are active contexts
//////////////////////////////////////////////////////////////////////////////

RocksDBReplicationContext* RocksDBReplicationManager::createContext() {
  auto context = std::make_unique<RocksDBReplicationContext>();
  TRI_ASSERT(context.get() != nullptr);
  TRI_ASSERT(context->isUsed());

  RocksDBReplicationId const id = context->id();

  {
    MUTEX_LOCKER(mutexLocker, _lock);

    _contexts.emplace(id, context.get());
    if (_contexts.size() == 1) {
      disableFileDeletions();
    }
  }
  return context.release();
}

////////////////////////////////////////////////////////////////////////////////
/// @brief remove a context by id
////////////////////////////////////////////////////////////////////////////////

bool RocksDBReplicationManager::remove(RocksDBReplicationId id) {
  RocksDBReplicationContext* context = nullptr;

  {
    MUTEX_LOCKER(mutexLocker, _lock);

    auto it = _contexts.find(id);
    if (it == _contexts.end()) {
      // not found
      return false;
    }

    context = it->second;
    TRI_ASSERT(context != nullptr);

    if (context->isDeleted()) {
      // already deleted
      return false;
    }

    if (context->isUsed()) {
      // context is in use by someone else. now mark as deleted
      context->deleted();
      return true;
    }

    // context not in use by someone else
    _contexts.erase(it);
    if (_contexts.empty()) {
      enableFileDeletions();
    }
  }

  TRI_ASSERT(context != nullptr);

  delete context;
  return true;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief find an existing context by id
/// if found, the context will be returned with the usage flag set to true.
/// it must be returned later using release()
////////////////////////////////////////////////////////////////////////////////

RocksDBReplicationContext* RocksDBReplicationManager::find(
    RocksDBReplicationId id, bool& busy, double ttl) {
  RocksDBReplicationContext* context = nullptr;
  busy = false;

  {
    MUTEX_LOCKER(mutexLocker, _lock);

    auto it = _contexts.find(id);
    if (it == _contexts.end()) {
      // not found
      return nullptr;
    }

    context = it->second;
    TRI_ASSERT(context != nullptr);

    if (context->isDeleted()) {
      // already deleted
      return nullptr;
    }

    if (context->isUsed()) {
      busy = true;
      return nullptr;
    }

    context->use(ttl);
  }

  return context;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief return a context for later use
////////////////////////////////////////////////////////////////////////////////

void RocksDBReplicationManager::release(RocksDBReplicationContext* context) {
  {
    MUTEX_LOCKER(mutexLocker, _lock);

    TRI_ASSERT(context->isUsed());
    context->release();

    if (!context->isDeleted()) {
      return;
    }

    // remove from the list
    _contexts.erase(context->id());
  }

  // and free the context
  delete context;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief return a context for garbage collection
////////////////////////////////////////////////////////////////////////////////

void RocksDBReplicationManager::destroy(RocksDBReplicationContext* context) {
  if (context != nullptr) {
    remove(context->id());
  }
}
////////////////////////////////////////////////////////////////////////////////
/// @brief whether or not the repository contains a used context
////////////////////////////////////////////////////////////////////////////////

bool RocksDBReplicationManager::containsUsedContext() {
  MUTEX_LOCKER(mutexLocker, _lock);

  for (auto it : _contexts) {
    if (it.second->isUsed()) {
      return true;
    }
  }

  return false;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief drop contexts by database (at least mark them as deleted)
////////////////////////////////////////////////////////////////////////////////

void RocksDBReplicationManager::drop(TRI_vocbase_t* vocbase) {
  {
    MUTEX_LOCKER(mutexLocker, _lock);

    for (auto& context : _contexts) {
      if (context.second->vocbase() == vocbase) {
        context.second->deleted();
      }
    }
  }

  garbageCollect(true);
}

////////////////////////////////////////////////////////////////////////////////
/// @brief drop all contexts (at least mark them as deleted)
////////////////////////////////////////////////////////////////////////////////

void RocksDBReplicationManager::dropAll() {
  {
    MUTEX_LOCKER(mutexLocker, _lock);

    for (auto& context : _contexts) {
      context.second->deleted();
    }
  }

  garbageCollect(true);
}

////////////////////////////////////////////////////////////////////////////////
/// @brief run a garbage collection on the contexts
////////////////////////////////////////////////////////////////////////////////

bool RocksDBReplicationManager::garbageCollect(bool force) {
  auto const now = TRI_microtime();
  std::vector<RocksDBReplicationContext*> found;

  try {
    found.reserve(MaxCollectCount);

    MUTEX_LOCKER(mutexLocker, _lock);

    auto oldSize = _contexts.size();

    for (auto it = _contexts.begin(); it != _contexts.end();
         /* no hoisting */) {
      auto context = it->second;

      if (context->isUsed()) {
        // must not destroy used contexts
        ++it;
        continue;
      }

      if (force || context->expires() < now) {
        context->deleted();
      }

      if (context->isDeleted()) {
        try {
          found.emplace_back(context);
          it = _contexts.erase(it);
        } catch (...) {
          // stop iteration
          break;
        }

        if (!force && found.size() >= MaxCollectCount) {
          break;
        }
      } else {
        ++it;
      }
    }

    // FIXME effectively force should only be called on shutdown
    // nevertheless this is quite ugly
    if ((oldSize > 0) && (_contexts.size() == 0) && !force) {
      enableFileDeletions();
    }
  } catch (...) {
    // go on and remove whatever we found so far
  }

  // remove contexts outside the lock
  for (auto it : found) {
    delete it;
  }

  return (!found.empty());
}

void RocksDBReplicationManager::disableFileDeletions() {
  auto rocks = globalRocksDB();
  auto s = rocks->DisableFileDeletions();
  TRI_ASSERT(s.ok());
}

void RocksDBReplicationManager::enableFileDeletions() {
  auto rocks = globalRocksDB();
  auto s = rocks->EnableFileDeletions(false);
  TRI_ASSERT(s.ok());
}

RocksDBReplicationContextGuard::RocksDBReplicationContextGuard(
    RocksDBReplicationManager* manager, RocksDBReplicationContext* ctx)
    : _manager(manager), _ctx(ctx) {}

RocksDBReplicationContextGuard::~RocksDBReplicationContextGuard() {
  if (_ctx != nullptr) {
    _manager->release(_ctx);
  }
}
<|endoftext|>


<commit_before>/*
The OpenTRV project licenses this file to you
under the Apache Licence, Version 2.0 (the "Licence");
you may not use this file except in compliance
with the Licence. You may obtain a copy of the Licence at

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

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

Author(s) / Copyright (s): Damon Hart-Davis 2016
*/

/*
 * Driver for OTV0p2Base JSON stats output tests.
 */

#include <stdint.h>
#include <gtest/gtest.h>
#include <OTV0p2Base.h>

// Test handling of JSON stats.
//
// Imported 2016/10/29 from Unit_Tests.cpp testJSONStats().
TEST(JSONStats,JSONStats)
{
    OTV0P2BASE::SimpleStatsRotation<2> ss1;
    ss1.setID("1234");
    EXPECT_EQ(0, ss1.size());
    EXPECT_EQ(0, ss1.writeJSON(NULL, OTV0P2BASE::randRNG8(), OTV0P2BASE::randRNG8(), OTV0P2BASE::randRNG8NextBoolean()));

    char buf[OTV0P2BASE::MSG_JSON_MAX_LENGTH + 2]; // Allow for trailing '\0' and spare byte.
    // Create minimal JSON message with no data content. just the (supplied) ID.
    const uint8_t l1 = ss1.writeJSON((uint8_t*)buf, sizeof(buf), OTV0P2BASE::randRNG8(), OTV0P2BASE::randRNG8NextBoolean());
    EXPECT_EQ(12, l1) << buf;
    EXPECT_STREQ(buf, "{\"@\":\"1234\"}") << buf;
    ss1.enableCount(false);
    EXPECT_EQ(12, ss1.writeJSON((uint8_t*)buf, sizeof(buf), OTV0P2BASE::randRNG8(), OTV0P2BASE::randRNG8NextBoolean()));
    EXPECT_STREQ(buf, "{\"@\":\"1234\"}") << buf;
    // Check that count works.
    ss1.enableCount(true);
    EXPECT_EQ(0, ss1.size());
    EXPECT_EQ(18, ss1.writeJSON((uint8_t*)buf, sizeof(buf), OTV0P2BASE::randRNG8(), OTV0P2BASE::randRNG8NextBoolean()));
    //OTV0P2BASE::serialPrintAndFlush(buf); OTV0P2BASE::serialPrintlnAndFlush();
    EXPECT_STREQ(buf, "{\"@\":\"1234\",\"+\":2}");
    // Turn count off for rest of tests.
    ss1.enableCount(false);
    EXPECT_EQ(12, ss1.writeJSON((uint8_t*)buf, sizeof(buf), OTV0P2BASE::randRNG8(), OTV0P2BASE::randRNG8NextBoolean()));
    // Check that removal of absent entry does nothing.
    EXPECT_TRUE(!ss1.remove("bogus"));
    EXPECT_EQ(0, ss1.size());
    // Check that new item can be added/put (with no/default properties).
    ss1.put("f1", 0);
    EXPECT_EQ(1, ss1.size());
    EXPECT_EQ(19, ss1.writeJSON((uint8_t*)buf, sizeof(buf), 0, OTV0P2BASE::randRNG8NextBoolean()));
    EXPECT_STREQ(buf, "{\"@\":\"1234\",\"f1\":0}");
    ss1.put("f1", 42);
    EXPECT_EQ(1, ss1.size());
    EXPECT_EQ(20, ss1.writeJSON((uint8_t*)buf, sizeof(buf), 0, OTV0P2BASE::randRNG8NextBoolean()));
    EXPECT_STREQ(buf, "{\"@\":\"1234\",\"f1\":42}");
    ss1.put("f1", -111);
    EXPECT_EQ(1, ss1.size());
    EXPECT_EQ(22, ss1.writeJSON((uint8_t*)buf, sizeof(buf), 0, OTV0P2BASE::randRNG8NextBoolean()));
    EXPECT_STREQ(buf, "{\"@\":\"1234\",\"f1\":-111}");
}

#if 0

// Test handling of JSON messages for transmission and reception.
// Includes bit-twiddling, CRC computation, and other error checking.
static void testJSONForTX()
  {
#if defined(ENABLE_JSON_OUTPUT)
  DEBUG_SERIAL_PRINTLN_FLASHSTRING("JSONForTX");
  char buf[MSG_JSON_MAX_LENGTH + 2]; // Allow for trailing '\0' or CRC + 0xff terminator.
  // Clear the buffer.
  memset(buf, 0, sizeof(buf));
  // Fail sanity check on a completely empty buffer (zero-length string).
  AssertIsTrue(!quickValidateRawSimpleJSONMessage(buf));
  // Fail sanity check on a few initially-plausible length-1 values.
  buf[0] = '{';
  AssertIsTrue(!quickValidateRawSimpleJSONMessage(buf));
  buf[0] = '}';
  AssertIsTrue(!quickValidateRawSimpleJSONMessage(buf));
  buf[0] = '[';
  AssertIsTrue(!quickValidateRawSimpleJSONMessage(buf));
  buf[0] = ']';
  AssertIsTrue(!quickValidateRawSimpleJSONMessage(buf));
  buf[0] = ' ';
  AssertIsTrue(!quickValidateRawSimpleJSONMessage(buf));
  // Fail sanity check with already-adjusted (minimal) nessage.
  buf[0] = '{';
  buf[1] = ('}' | 0x80);
  AssertIsTrue(!quickValidateRawSimpleJSONMessage(buf));
  // Minimal correct messaage should pass.
  buf[0] = '{';
  buf[1] = '}';
  AssertIsTrue(quickValidateRawSimpleJSONMessage(buf));
  // Try a longer valid trivial message.
  strcpy_P(buf, (const char PROGMEM *)F("{  }"));
  AssertIsTrue(quickValidateRawSimpleJSONMessage(buf));
  // Invalidate it with a non-printable char and check that it is rejected.
  buf[2] = '\1';
  AssertIsTrue(!quickValidateRawSimpleJSONMessage(buf));
  // Try a longer valid non-trivial message.
  __FlashStringHelper const* longJSONMsg1 = F("{\"@\":\"cdfb\",\"T|C16\":299,\"H|%\":83,\"L\":255,\"B|cV\":256}");
  memset(buf, 0, sizeof(buf));
  strcpy_P(buf, (const char PROGMEM *)longJSONMsg1);
  AssertIsTrue(quickValidateRawSimpleJSONMessage(buf));
  // Invalidate it with a high-bit set and check that it is rejected.
  buf[5] |= 0x80;
  AssertIsTrue(!quickValidateRawSimpleJSONMessage(buf));
  // CRC fun!
  memset(buf, 0, sizeof(buf));
  buf[0] = '{';
  buf[1] = '}';
  const uint8_t crc1 = adjustJSONMsgForTXAndComputeCRC(buf);
  // Check that top bit is not set (ie CRC was computed OK).
  AssertIsTrueWithErr(!(crc1 & 0x80), crc1);
  // Check for expected CRC value.
  AssertIsTrueWithErr((0x38 == crc1), crc1);
  // Check that initial part unaltered.
  AssertIsTrueWithErr(('{' == buf[0]), buf[0]);
  // Check that top bit has been set in trailing brace.
  AssertIsTrueWithErr(((char)('}' | 0x80) == buf[1]), buf[1]);
  // Check that trailing '\0' still present.
  AssertIsTrueWithErr((0 == buf[2]), buf[2]);
  // Check that TX-format can be converted for RX.
  buf[2] = crc1;
  buf[3] = 0xff; // As for normal TX...
// FIXME
//  const int8_t l1 = adjustJSONMsgForRXAndCheckCRC(buf, sizeof(buf));
//  AssertIsTrueWithErr(2 == l1, l1);
//  AssertIsTrueWithErr(2 == strlen(buf), strlen(buf));
//  AssertIsTrue(quickValidateRawSimpleJSONMessage(buf));
  // Now a longer message...
  memset(buf, 0, sizeof(buf));
  strcpy_P(buf, (const char PROGMEM *)longJSONMsg1);
  const int8_t l2o = strlen(buf);
  const uint8_t crc2 = adjustJSONMsgForTXAndComputeCRC(buf);
  // Check that top bit is not set (ie CRC was computed OK).
  AssertIsTrueWithErr(!(crc2 & 0x80), crc2);
  // Check for expected CRC value.
  AssertIsTrueWithErr((0x77 == crc2), crc2);
// FIXME
//  // Check that TX-format can be converted for RX.
//  buf[l2o] = crc2;
//  buf[l2o+1] = 0xff;
// FIXME
//  const int8_t l2 = adjustJSONMsgForRXAndCheckCRC(buf, sizeof(buf));
//  AssertIsTrueWithErr(l2o == l2, l2);
//  AssertIsTrue(quickValidateRawSimpleJSONMessage(buf));
#endif
  }

#endif // 0
<commit_msg>TODO-970: more JSON tests<commit_after>/*
The OpenTRV project licenses this file to you
under the Apache Licence, Version 2.0 (the "Licence");
you may not use this file except in compliance
with the Licence. You may obtain a copy of the Licence at

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

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

Author(s) / Copyright (s): Damon Hart-Davis 2016
*/

/*
 * Driver for OTV0p2Base JSON stats output tests.
 */

#include <stdint.h>
#include <gtest/gtest.h>
#include <OTV0p2Base.h>

// Test handling of JSON stats.
//
// Imported 2016/10/29 from Unit_Tests.cpp testJSONStats().
TEST(JSONStats,JSONStats)
{
    OTV0P2BASE::SimpleStatsRotation<2> ss1;
    ss1.setID("1234");
    EXPECT_EQ(0, ss1.size());
    EXPECT_EQ(0, ss1.writeJSON(NULL, OTV0P2BASE::randRNG8(), OTV0P2BASE::randRNG8(), OTV0P2BASE::randRNG8NextBoolean()));

    char buf[OTV0P2BASE::MSG_JSON_MAX_LENGTH + 2]; // Allow for trailing '\0' and spare byte.
    // Create minimal JSON message with no data content. just the (supplied) ID.
    const uint8_t l1 = ss1.writeJSON((uint8_t*)buf, sizeof(buf), OTV0P2BASE::randRNG8(), OTV0P2BASE::randRNG8NextBoolean());
    EXPECT_EQ(12, l1) << buf;
    EXPECT_STREQ(buf, "{\"@\":\"1234\"}") << buf;
    ss1.enableCount(false);
    EXPECT_EQ(12, ss1.writeJSON((uint8_t*)buf, sizeof(buf), OTV0P2BASE::randRNG8(), OTV0P2BASE::randRNG8NextBoolean()));
    EXPECT_STREQ(buf, "{\"@\":\"1234\"}") << buf;
    // Check that count works.
    ss1.enableCount(true);
    EXPECT_EQ(0, ss1.size());
    EXPECT_EQ(18, ss1.writeJSON((uint8_t*)buf, sizeof(buf), OTV0P2BASE::randRNG8(), OTV0P2BASE::randRNG8NextBoolean()));
    //OTV0P2BASE::serialPrintAndFlush(buf); OTV0P2BASE::serialPrintlnAndFlush();
    EXPECT_STREQ(buf, "{\"@\":\"1234\",\"+\":2}");
    // Turn count off for rest of tests.
    ss1.enableCount(false);
    EXPECT_EQ(12, ss1.writeJSON((uint8_t*)buf, sizeof(buf), OTV0P2BASE::randRNG8(), OTV0P2BASE::randRNG8NextBoolean()));
    // Check that removal of absent entry does nothing.
    EXPECT_TRUE(!ss1.remove("bogus"));
    EXPECT_EQ(0, ss1.size());
    // Check that new item can be added/put (with no/default properties).
    ss1.put("f1", 0);
    EXPECT_EQ(1, ss1.size());
    EXPECT_EQ(19, ss1.writeJSON((uint8_t*)buf, sizeof(buf), 0, OTV0P2BASE::randRNG8NextBoolean()));
    EXPECT_STREQ(buf, "{\"@\":\"1234\",\"f1\":0}");
    ss1.put("f1", 42);
    EXPECT_EQ(1, ss1.size());
    EXPECT_EQ(20, ss1.writeJSON((uint8_t*)buf, sizeof(buf), 0, OTV0P2BASE::randRNG8NextBoolean()));
    EXPECT_STREQ(buf, "{\"@\":\"1234\",\"f1\":42}");
    ss1.put("f1", -111);
    EXPECT_EQ(1, ss1.size());
    EXPECT_EQ(22, ss1.writeJSON((uint8_t*)buf, sizeof(buf), 0, OTV0P2BASE::randRNG8NextBoolean()));
    EXPECT_STREQ(buf, "{\"@\":\"1234\",\"f1\":-111}");

    EXPECT_TRUE(OTV0P2BASE::quickValidateRawSimpleJSONMessage(buf));
}

// Test handling of JSON messages for transmission and reception.
// Includes bit-twiddling, CRC computation, and other error checking.
//
// Imported 2016/10/29 from Unit_Tests.cpp testJSONForTX().
TEST(JSONStats,JSONForTX)
  {
  char buf[OTV0P2BASE::MSG_JSON_MAX_LENGTH + 2]; // Allow for trailing '\0' or CRC + 0xff terminator.
  // Clear the buffer.
  memset(buf, 0, sizeof(buf));
  // Fail sanity check on a completely empty buffer (zero-length string).
  EXPECT_TRUE(!OTV0P2BASE::quickValidateRawSimpleJSONMessage(buf));
  // Fail sanity check on a few initially-plausible length-1 values.
  buf[0] = '{';
  EXPECT_TRUE(!OTV0P2BASE::quickValidateRawSimpleJSONMessage(buf));
  buf[0] = '}';
  EXPECT_TRUE(!OTV0P2BASE::quickValidateRawSimpleJSONMessage(buf));
  buf[0] = '[';
  EXPECT_TRUE(!OTV0P2BASE::quickValidateRawSimpleJSONMessage(buf));
  buf[0] = ']';
  EXPECT_TRUE(!OTV0P2BASE::quickValidateRawSimpleJSONMessage(buf));
  buf[0] = ' ';
  EXPECT_TRUE(!OTV0P2BASE::quickValidateRawSimpleJSONMessage(buf));
  // Fail sanity check with already-adjusted (minimal) nessage.
  buf[0] = '{';
  buf[1] = ('}' | 0x80);
  EXPECT_TRUE(!OTV0P2BASE::quickValidateRawSimpleJSONMessage(buf));
  // Minimal correct messaage should pass.
  buf[0] = '{';
  buf[1] = '}';
  EXPECT_TRUE(OTV0P2BASE::quickValidateRawSimpleJSONMessage(buf));
  // Try a longer valid trivial message.
  strcpy(buf, "{  }");
  EXPECT_TRUE(OTV0P2BASE::quickValidateRawSimpleJSONMessage(buf));
  // Invalidate it with a non-printable char and check that it is rejected.
  buf[2] = '\1';
  EXPECT_TRUE(!OTV0P2BASE::quickValidateRawSimpleJSONMessage(buf));
  // Try a longer valid non-trivial message.
  const char * longJSONMsg1 = "{\"@\":\"cdfb\",\"T|C16\":299,\"H|%\":83,\"L\":255,\"B|cV\":256}";
  memset(buf, 0, sizeof(buf));
  strcpy(buf, longJSONMsg1);
  EXPECT_TRUE(OTV0P2BASE::quickValidateRawSimpleJSONMessage(buf));
  // Invalidate it with a high-bit set and check that it is rejected.
  buf[5] |= 0x80;
  EXPECT_TRUE(!OTV0P2BASE::quickValidateRawSimpleJSONMessage(buf));
  // CRC fun!
  memset(buf, 0, sizeof(buf));
  buf[0] = '{';
  buf[1] = '}';
  const uint8_t crc1 = OTV0P2BASE::adjustJSONMsgForTXAndComputeCRC(buf);
  // Check that top bit is not set (ie CRC was computed OK).
  EXPECT_TRUE(!(crc1 & 0x80));
  // Check for expected CRC value.
  EXPECT_TRUE(0x38 == crc1);
  // Check that initial part unaltered.
  EXPECT_TRUE('{' == buf[0]);
  // Check that top bit has been set in trailing brace.
  EXPECT_TRUE((char)('}' | 0x80) == buf[1]);
  // Check that trailing '\0' still present.
  EXPECT_TRUE(0 == buf[2]);
  // Check that TX-format can be converted for RX.
  buf[2] = crc1;
  buf[3] = 0xff; // As for normal TX...
// FIXME
//  const int8_t l1 = adjustJSONMsgForRXAndCheckCRC(buf, sizeof(buf));
//  AssertIsTrueWithErr(2 == l1, l1);
//  AssertIsTrueWithErr(2 == strlen(buf), strlen(buf));
//  AssertIsTrue(quickValidateRawSimpleJSONMessage(buf));
  // Now a longer message...
  memset(buf, 0, sizeof(buf));
  strcpy(buf, longJSONMsg1);
//  const int l2o = strlen(buf);
  const uint8_t crc2 = OTV0P2BASE::adjustJSONMsgForTXAndComputeCRC(buf);
  // Check that top bit is not set (ie CRC was computed OK).
  EXPECT_TRUE(!(crc2 & 0x80));
  // Check for expected CRC value.
  EXPECT_TRUE(0x77 == crc2);
// FIXME
//  // Check that TX-format can be converted for RX.
//  buf[l2o] = crc2;
//  buf[l2o+1] = 0xff;
// FIXME
//  const int8_t l2 = adjustJSONMsgForRXAndCheckCRC(buf, sizeof(buf));
//  AssertIsTrueWithErr(l2o == l2, l2);
//  AssertIsTrue(quickValidateRawSimpleJSONMessage(buf));
  }
<|endoftext|>


<commit_before>/*
 Copyright 2017 Herik Lima de Castro and Marcelo Medeiros Eler
 Distributed under MIT license, or public domain if desired and
 recognized in your jurisdiction.
 See file LICENSE for detail.
*/

#include <cwf/cppwebapplication.h>
#include <controllers/savefilescontroller.h>

int main(int argc, char *argv[])
{
    CWF::CppWebApplication server(argc, argv, "/home/herik/CPPWebFramework/examples/SaveFiles/server/");

    server.addUrlController<SaveFilesController>("/savefiles");

    return server.start();
}
<commit_msg>SaveFiles example adjust<commit_after>/*
 Copyright 2017 Herik Lima de Castro and Marcelo Medeiros Eler
 Distributed under MIT license, or public domain if desired and
 recognized in your jurisdiction.
 See file LICENSE for detail.
*/

#include <cwf/cppwebapplication.h>
#include <controllers/savefilescontroller.h>

int main(int argc, char *argv[])
{
    CWF::CppWebApplication server(argc, argv, "/home/herik/CPPWebFramework/examples/SaveFiles/server/");

    server.addController<SaveFilesController>("/savefiles");

    return server.start();
}
<|endoftext|>


<commit_before>#include <rmc++.h>
#include <utility>
#include <functional>
#include <memory>
#include "epoch_rmc.hpp"
// Very very closely modeled after crossbeam by aturon.

namespace rmclib {
#if 0
} // f. this
#endif
/////////////////////////////

const int kNumEpochs = 3;

thread_local LocalEpoch Epoch::local_epoch_;
rmc::atomic<Participant *> Participants::head_;

// XXX: Should this be in a class??
static rmc::atomic<uintptr_t> global_epoch_{0};
static ConcurrentBag global_garbage_[kNumEpochs];

Participant *Participants::enroll() {
    Participant *p = new Participant();

    Participant *head = head_;
    for (;;) {
        p->next_ = head;
        if (head_.compare_exchange_weak(head, p)) break;
    }

    return p;
}

/////// Participant is where most of the interesting stuff happens
bool Participant::quickEnter() noexcept {
    uintptr_t new_count = in_critical_ + 1;
    in_critical_ = new_count;
    // Nothing to do if we were already in a critical section
    if (new_count > 1) return false;

    rmc::push_here();

    // Copy the global epoch to the local one;
    // if it has changed, garbage collect
    uintptr_t global_epoch = global_epoch_;
    epoch_ = global_epoch;
    return true;
}

void Participant::enter() noexcept {
    uintptr_t epoch = epoch_;
    if (!quickEnter()) return;

    // If the epoch has changed, garbage collect
    if (epoch != epoch_) {
        garbage_.collect();
    }

    if (garbage_.needsCollect()) {
        tryCollect();
    }
}

void Participant::exit() noexcept {
    VEDGE(pre, exit);
    uintptr_t new_count = in_critical_ - 1;
    L(exit, in_critical_ = new_count);
}

bool Participant::tryCollect() {
    // I think we might not need the load_epoch; we'll be CASing it anyways.
    // Crossbeam makes it SC, though.
    XEDGE(load_epoch, load_head); // XXX: discount double check
    XEDGE(load_head, a);
    XEDGE(a, update_epoch);
    // This could maybe be XEDGE but making it VEDGE lets us make the
    // invariant -vt-> based.
    VEDGE(update_epoch, collect); // XXX: discount double check
    XEDGE(collect, update_local);

    uintptr_t cur_epoch = L(load_epoch, global_epoch_);

    // XXX: TODO: lazily remove stuff from this list
    for (Participant *p = L(load_head, Participants::head_);
         p; p = L(a, p->next_)) {
        // We can only advance the epoch if every thread in a critical
        // section is in the current epoch.
        if (L(a, p->in_critical_) && L(a, p->epoch_) != cur_epoch) {
            return false;
        }
    }

    // Try to advance the global epoch
    uintptr_t new_epoch = cur_epoch + 1;
    if (!L(update_epoch,
           global_epoch_.compare_exchange_strong(cur_epoch, new_epoch))) {
        return false;
    }

    // Garbage collect
    LS(collect, {
        global_garbage_[(new_epoch+1) % kNumEpochs].collect();
        garbage_.collect();
    });
    // Now that the collection is done, we can safely update our
    // local epoch.
    LS(update_local, epoch_ = new_epoch);

    return true;
}

/////////////
void RealLocalGarbage::collectBag(Bag &bag) {
    for (auto f : bag) {
        f();
    }
    // XXX: should we shrink capacity?
    bag.clear();
}

void RealLocalGarbage::collect() {
    if (size() == 0) return;

    collectBag(old_);
    std::swap(old_, cur_);
    std::swap(cur_, new_);
}

void RealLocalGarbage::migrateGarbage() {
    // We put all three local bags into the current global bag.
    // We could do better than this but why bother, I think.
    auto cleanup = [old = std::move(old_),
                    cur = std::move(cur_),
                    newp = std::move(new_)]() mutable {
        collectBag(old);
        collectBag(cur);
        collectBag(newp);
    };
    global_garbage_[global_epoch_ % kNumEpochs].registerCleanup(cleanup);
}

void DummyLocalGarbage::registerCleanup(GarbageCleanup f) {
    global_garbage_[global_epoch_ % kNumEpochs].registerCleanup(f);
}

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

// XXX: I think we could drop the edges here entirely and just rely on

void ConcurrentBag::registerCleanup(std::function<void()> f) {
    // Don't need edge from head_ load because 'push' will also
    // read from the write to head.
    VEDGE(node_setup, push);

    auto *node = L(node_setup, new ConcurrentBag::Node(std::move(f)));

    // Push the node onto a Treiber stack
    for (;;) {
        ConcurrentBag::Node *head = head_;
        L(node_setup, node->next_ = head);
        if (L(push, head_.compare_exchange_weak(head, node))) break;
    }
}

void ConcurrentBag::collect() {
    VEDGE(popall, post);

    // Avoid xchg if empty
    if (!head_) return;

    // Pop the whole stack off
    // Since we only ever unconditionally destroy the whole stack,
    // we don't need to worry about ABA really.
    // (Which for stacks comes up if pop() believes that the address
    // of a node being the same means the next pointer is also...)
    std::unique_ptr<ConcurrentBag::Node> head(
        L(popall, head_.exchange(nullptr)));

    while (head) {
        head->cleanup_();
        head.reset(head->next_);
    }
}

}
<commit_msg>Fix a real dumb bug in epoch_rmc<commit_after>#include <rmc++.h>
#include <utility>
#include <functional>
#include <memory>
#include "epoch_rmc.hpp"
// Very very closely modeled after crossbeam by aturon.

namespace rmclib {
#if 0
} // f. this
#endif
/////////////////////////////

const int kNumEpochs = 3;

thread_local LocalEpoch Epoch::local_epoch_;
rmc::atomic<Participant *> Participants::head_;

// XXX: Should this be in a class??
static rmc::atomic<uintptr_t> global_epoch_{0};
static ConcurrentBag global_garbage_[kNumEpochs];

Participant *Participants::enroll() {
    VEDGE(init_p, cas);

    Participant *p = L(init_p, new Participant());

    Participant *head = head_;
    for (;;) {
        L(init_p, p->next_ = head);
        if (L(cas, head_.compare_exchange_weak(head, p))) break;
    }

    return p;
}

/////// Participant is where most of the interesting stuff happens
bool Participant::quickEnter() noexcept {
    uintptr_t new_count = in_critical_ + 1;
    in_critical_ = new_count;
    // Nothing to do if we were already in a critical section
    if (new_count > 1) return false;

    rmc::push_here();

    // Copy the global epoch to the local one;
    // if it has changed, garbage collect
    uintptr_t global_epoch = global_epoch_;
    epoch_ = global_epoch;
    return true;
}

void Participant::enter() noexcept {
    uintptr_t epoch = epoch_;
    if (!quickEnter()) return;

    // If the epoch has changed, garbage collect
    if (epoch != epoch_) {
        garbage_.collect();
    }

    if (garbage_.needsCollect()) {
        tryCollect();
    }
}

void Participant::exit() noexcept {
    VEDGE(pre, exit);
    uintptr_t new_count = in_critical_ - 1;
    L(exit, in_critical_ = new_count);
}

bool Participant::tryCollect() {
    // I think we might not need the load_epoch; we'll be CASing it anyways.
    // Crossbeam makes it SC, though.
    XEDGE(load_epoch, load_head); // XXX: discount double check
    XEDGE(load_head, a);
    XEDGE(a, update_epoch);
    // This could maybe be XEDGE but making it VEDGE lets us make the
    // invariant -vt-> based.
    VEDGE(update_epoch, collect); // XXX: discount double check
    XEDGE(collect, update_local);

    uintptr_t cur_epoch = L(load_epoch, global_epoch_);

    // XXX: TODO: lazily remove stuff from this list
    for (Participant *p = L(load_head, Participants::head_);
         p; p = L(a, p->next_)) {
        // We can only advance the epoch if every thread in a critical
        // section is in the current epoch.
        if (L(a, p->in_critical_) && L(a, p->epoch_) != cur_epoch) {
            return false;
        }
    }

    // Try to advance the global epoch
    uintptr_t new_epoch = cur_epoch + 1;
    if (!L(update_epoch,
           global_epoch_.compare_exchange_strong(cur_epoch, new_epoch))) {
        return false;
    }

    // Garbage collect
    LS(collect, {
        global_garbage_[(new_epoch+1) % kNumEpochs].collect();
        garbage_.collect();
    });
    // Now that the collection is done, we can safely update our
    // local epoch.
    LS(update_local, epoch_ = new_epoch);

    return true;
}

/////////////
void RealLocalGarbage::collectBag(Bag &bag) {
    for (auto f : bag) {
        f();
    }
    // XXX: should we shrink capacity?
    bag.clear();
}

void RealLocalGarbage::collect() {
    if (size() == 0) return;

    collectBag(old_);
    std::swap(old_, cur_);
    std::swap(cur_, new_);
}

void RealLocalGarbage::migrateGarbage() {
    // We put all three local bags into the current global bag.
    // We could do better than this but why bother, I think.
    auto cleanup = [old = std::move(old_),
                    cur = std::move(cur_),
                    newp = std::move(new_)]() mutable {
        collectBag(old);
        collectBag(cur);
        collectBag(newp);
    };
    global_garbage_[global_epoch_ % kNumEpochs].registerCleanup(cleanup);
}

void DummyLocalGarbage::registerCleanup(GarbageCleanup f) {
    global_garbage_[global_epoch_ % kNumEpochs].registerCleanup(f);
}

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

// XXX: I think we could drop the edges here entirely and just rely on

void ConcurrentBag::registerCleanup(std::function<void()> f) {
    // Don't need edge from head_ load because 'push' will also
    // read from the write to head.
    VEDGE(node_setup, push);

    auto *node = L(node_setup, new ConcurrentBag::Node(std::move(f)));

    // Push the node onto a Treiber stack
    for (;;) {
        ConcurrentBag::Node *head = head_;
        L(node_setup, node->next_ = head);
        if (L(push, head_.compare_exchange_weak(head, node))) break;
    }
}

void ConcurrentBag::collect() {
    VEDGE(popall, post);

    // Avoid xchg if empty
    if (!head_) return;

    // Pop the whole stack off
    // Since we only ever unconditionally destroy the whole stack,
    // we don't need to worry about ABA really.
    // (Which for stacks comes up if pop() believes that the address
    // of a node being the same means the next pointer is also...)
    std::unique_ptr<ConcurrentBag::Node> head(
        L(popall, head_.exchange(nullptr)));

    while (head) {
        head->cleanup_();
        head.reset(head->next_);
    }
}

}
<|endoftext|>


<commit_before>// Copyright (C) 2010-2014 Joshua Boyce.
// See the file COPYING for copying permission.

#include "input.hpp"

#include <algorithm>
#include <cstdint>
#include <mutex>
#include <queue>

#include <windows.h>
#include <winnt.h>
#include <winternl.h>

#include <hadesmem/config.hpp>
#include <hadesmem/detail/smart_handle.hpp>
#include <hadesmem/detail/str_conv.hpp>

#include "callbacks.hpp"
#include "cursor.hpp"
#include "direct_input.hpp"
#include "hook_disabler.hpp"
#include "render.hpp"
#include "main.hpp"
#include "window.hpp"

namespace
{
hadesmem::cerberus::Callbacks<hadesmem::cerberus::OnInputQueueEntry>&
  GetOnInputQueueEntryCallbacks()
{
  static hadesmem::cerberus::Callbacks<hadesmem::cerberus::OnInputQueueEntry>
    callbacks;
  return callbacks;
}

class InputImpl : public hadesmem::cerberus::InputInterface
{
public:
  virtual std::size_t RegisterOnInputQueueEntry(
    std::function<hadesmem::cerberus::OnInputQueueEntry> const& callback) final
  {
    auto& callbacks = GetOnInputQueueEntryCallbacks();
    return callbacks.Register(callback);
  }

  virtual void UnregisterOnInputQueueEntry(std::size_t id) final
  {
    auto& callbacks = GetOnInputQueueEntryCallbacks();
    return callbacks.Unregister(id);
  }
};

int& GetShowCursorCount() HADESMEM_DETAIL_NOEXCEPT
{
  static int show_cursor_count{};
  return show_cursor_count;
}

std::pair<bool, HCURSOR>& GetOldCursor() HADESMEM_DETAIL_NOEXCEPT
{
  static std::pair<bool, HCURSOR> old_cursor{};
  return old_cursor;
}

std::pair<bool, POINT>& GetOldCursorPos() HADESMEM_DETAIL_NOEXCEPT
{
  static std::pair<bool, POINT> cursor_pos{};
  return cursor_pos;
}

RECT& GetOldClipCursor() HADESMEM_DETAIL_NOEXCEPT
{
  static RECT old_clip_cursor{};
  return old_clip_cursor;
}

struct WndProcInputMsg
{
  HWND hwnd_;
  UINT msg_;
  WPARAM wparam_;
  LPARAM lparam_;
};

std::queue<WndProcInputMsg>& GetWndProcInputMsgQueue()
{
  static std::queue<WndProcInputMsg> queue;
  return queue;
}

std::recursive_mutex& GetWndProcInputMsgQueueMutex()
{
  static std::recursive_mutex mutex;
  return mutex;
}

void SetOrRestoreCursor(bool visible)
{
  hadesmem::cerberus::HookDisabler disable_set_cursor_hook{
    &hadesmem::cerberus::GetDisableSetCursorHook()};

  auto& old_cursor = GetOldCursor();

  if (visible)
  {
    HCURSOR const arrow_cursor = ::LoadCursorW(nullptr, IDC_ARROW);
    if (!arrow_cursor)
    {
      DWORD const last_error = ::GetLastError();
      HADESMEM_DETAIL_THROW_EXCEPTION(
        hadesmem::Error{} << hadesmem::ErrorString{"LoadCursorW failed."}
                          << hadesmem::ErrorCodeWinLast{last_error});
    }

    HADESMEM_DETAIL_TRACE_A("Setting arrow cursor.");
    old_cursor.second = ::SetCursor(arrow_cursor);
  }
  else if (old_cursor.first)
  {
    HADESMEM_DETAIL_TRACE_A("Setting old cursor.");
    old_cursor.second = ::SetCursor(old_cursor.second);
  }

  old_cursor.first = true;
}

void SaveCurrentCursorPos()
{
  auto& old_cursor_pos = GetOldCursorPos();

  hadesmem::cerberus::HookDisabler disable_get_cursor_pos_hook{
    &hadesmem::cerberus::GetDisableGetCursorPosHook()};

  POINT cur_cursor_pos{};
  if (!::GetCursorPos(&cur_cursor_pos))
  {
    DWORD const last_error = ::GetLastError();
    HADESMEM_DETAIL_THROW_EXCEPTION(
      hadesmem::Error{} << hadesmem::ErrorString{"GetCursorPos failed."}
                        << hadesmem::ErrorCodeWinLast{last_error});
  }

  old_cursor_pos.first = true;
  old_cursor_pos.second = cur_cursor_pos;
}

void ClearOldCursorPos()
{
  auto& old_cursor_pos = GetOldCursorPos();
  old_cursor_pos.first = false;
  old_cursor_pos.second = POINT{};
}

void RestoreOldCursorPos()
{
  auto& old_cursor_pos = GetOldCursorPos();

  if (!old_cursor_pos.first)
  {
    return;
  }

  hadesmem::cerberus::HookDisabler disable_set_cursor_pos_hook{
    &hadesmem::cerberus::GetDisableSetCursorPosHook()};

  if (!::SetCursorPos(old_cursor_pos.second.x, old_cursor_pos.second.y))
  {
    DWORD const last_error = ::GetLastError();
    HADESMEM_DETAIL_THROW_EXCEPTION(
      hadesmem::Error{} << hadesmem::ErrorString{"SetCursorPos failed."}
                        << hadesmem::ErrorCodeWinLast{last_error});
  }

  ClearOldCursorPos();
}

void ShowCursor()
{
  hadesmem::cerberus::HookDisabler disable_show_cursor_hook{
    &hadesmem::cerberus::GetDisableShowCursorHook()};

  auto& show_cursor_count = GetShowCursorCount();
  do
  {
    HADESMEM_DETAIL_TRACE_A("Showing cursor.");
    ++show_cursor_count;
  } while (::ShowCursor(TRUE) < 0);
}

void HideCursor()
{
  hadesmem::cerberus::HookDisabler disable_show_cursor_hook{
    &hadesmem::cerberus::GetDisableShowCursorHook()};

  auto& show_cursor_count = GetShowCursorCount();
  while (show_cursor_count > 0)
  {
    HADESMEM_DETAIL_TRACE_A("Hiding cursor.");
    --show_cursor_count;
    ::ShowCursor(FALSE);
  }
}

void SaveCurrentClipCursor()
{
  hadesmem::cerberus::HookDisabler disable_get_clip_cursor_hook{
    &hadesmem::cerberus::GetDisableGetClipCursorHook()};

  RECT clip_cursor{};
  if (!::GetClipCursor(&clip_cursor))
  {
    DWORD const last_error = ::GetLastError();
    HADESMEM_DETAIL_THROW_EXCEPTION(
      hadesmem::Error{} << hadesmem::ErrorString{"GetClipCursor failed."}
                        << hadesmem::ErrorCodeWinLast{last_error});
  }

  HADESMEM_DETAIL_TRACE_FORMAT_A("Saving current clip cursor: Left [%ld] Top "
                                 "[%ld] Right [%ld] Bottom [%ld]",
                                 clip_cursor.left,
                                 clip_cursor.top,
                                 clip_cursor.right,
                                 clip_cursor.bottom);

  auto& old_clip_cursor = GetOldClipCursor();
  old_clip_cursor = clip_cursor;
}

void ClipCursorWrap(RECT clip_cursor)
{
  hadesmem::cerberus::HookDisabler disable_clip_cursor_hook{
    &hadesmem::cerberus::GetDisableClipCursorHook()};

  if (!::ClipCursor(&clip_cursor))
  {
    DWORD const last_error = ::GetLastError();
    HADESMEM_DETAIL_THROW_EXCEPTION(
      hadesmem::Error{} << hadesmem::ErrorString{"ClipCursor failed."}
                        << hadesmem::ErrorCodeWinLast{last_error});
  }
}

void SetNewClipCursor()
{
  RECT new_clip_cursor{};
  if (!::GetWindowRect(hadesmem::cerberus::GetCurrentWindow(),
                       &new_clip_cursor))
  {
    DWORD const last_error = ::GetLastError();
    HADESMEM_DETAIL_THROW_EXCEPTION(
      hadesmem::Error{} << hadesmem::ErrorString{"GetWindowRect failed."}
                        << hadesmem::ErrorCodeWinLast{last_error});
  }

  HADESMEM_DETAIL_TRACE_FORMAT_A(
    "Setting new clip cursor: Left [%ld] Top [%ld] Right [%ld] Bottom [%ld]",
    new_clip_cursor.left,
    new_clip_cursor.top,
    new_clip_cursor.right,
    new_clip_cursor.bottom);

  ClipCursorWrap(new_clip_cursor);
}

void RestoreOldClipCursor()
{
  auto& clip_cursor = GetOldClipCursor();

  HADESMEM_DETAIL_TRACE_FORMAT_A("Restoring old clip cursor: Left [%ld] Top "
                                 "[%ld] Right [%ld] Bottom [%ld]",
                                 clip_cursor.left,
                                 clip_cursor.top,
                                 clip_cursor.right,
                                 clip_cursor.bottom);

  ClipCursorWrap(clip_cursor);
}

void ToggleGuiVisible()
{
  auto const visible = !hadesmem::cerberus::GetGuiVisible();
  HADESMEM_DETAIL_TRACE_A(visible ? "Showing GUI." : "Hiding GUI.");
  hadesmem::cerberus::SetGuiVisible(visible, !visible);
}

void WindowProcCallback(
  HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, bool* handled)
{
  {
    auto& queue = GetWndProcInputMsgQueue();
    auto& mutex = GetWndProcInputMsgQueueMutex();
    std::lock_guard<std::recursive_mutex> lock{mutex};
    queue.push(WndProcInputMsg{hwnd, msg, wparam, lparam});
  }

  if (msg == WM_KEYDOWN && !((lparam >> 30) & 1) && wparam == VK_F9 &&
      ::GetAsyncKeyState(VK_SHIFT) & 0x8000)
  {
    ToggleGuiVisible();
    *handled = true;
    return;
  }

  auto const visible = hadesmem::cerberus::GetGuiVisible();
  bool const blocked_msg = msg == WM_INPUT ||
                           (msg >= WM_KEYFIRST && msg <= WM_KEYLAST) ||
                           (msg >= WM_MOUSEFIRST && msg <= WM_MOUSELAST);
  if (visible && blocked_msg)
  {
    *handled = true;
    return;
  }
}

void OnSetCursor(HCURSOR cursor,
                 bool* handled,
                 HCURSOR* retval) HADESMEM_DETAIL_NOEXCEPT
{
  auto& old_cursor = GetOldCursor();
  HCURSOR const old_cursor_raw = old_cursor.second;
  old_cursor.first = true;
  old_cursor.second = cursor;

  if (hadesmem::cerberus::GetGuiVisible())
  {
    *retval = old_cursor_raw;
    *handled = true;
  }
}

void OnDirectInput(bool* handled) HADESMEM_DETAIL_NOEXCEPT
{
  if (hadesmem::cerberus::GetGuiVisible())
  {
    *handled = true;
  }
}

void OnGetCursorPos(LPPOINT point, bool* handled) HADESMEM_DETAIL_NOEXCEPT
{
  if (hadesmem::cerberus::GetGuiVisible() && point)
  {
    auto& old_cursor_pos = GetOldCursorPos();
    point->x = old_cursor_pos.second.x;
    point->y = old_cursor_pos.second.y;

    *handled = true;
  }
}

void OnSetCursorPos(int x, int y, bool* handled) HADESMEM_DETAIL_NOEXCEPT
{
  if (hadesmem::cerberus::GetGuiVisible())
  {
    auto& old_cursor_pos = GetOldCursorPos();
    old_cursor_pos.first = true;
    old_cursor_pos.second.x = x;
    old_cursor_pos.second.y = y;

    *handled = true;
  }
}

void
  OnShowCursor(BOOL show, bool* handled, int* retval) HADESMEM_DETAIL_NOEXCEPT
{
  if (hadesmem::cerberus::GetGuiVisible())
  {
    auto& show_cursor_count = GetShowCursorCount();
    if (show)
    {
      ++show_cursor_count;
    }
    else
    {
      --show_cursor_count;
    }

    *retval = show_cursor_count;
    *handled = true;
  }
}

void OnClipCursor(RECT const* rect,
                  bool* handled,
                  BOOL* retval) HADESMEM_DETAIL_NOEXCEPT
{
  if (hadesmem::cerberus::GetGuiVisible() && rect)
  {
    auto& clip_cursor = GetOldClipCursor();
    clip_cursor = *rect;
    *retval = TRUE;
    *handled = true;
  }
}

void OnGetClipCursor(RECT* rect,
                     bool* handled,
                     BOOL* retval) HADESMEM_DETAIL_NOEXCEPT
{
  if (hadesmem::cerberus::GetGuiVisible() && rect)
  {
    auto& clip_cursor = GetOldClipCursor();
    *rect = clip_cursor;
    *retval = TRUE;
    *handled = true;
  }
}
}

namespace hadesmem
{
namespace cerberus
{
void SetGuiVisibleForInput(bool visible, bool old_visible)
{
  if (visible != old_visible)
  {
    SetOrRestoreCursor(visible);

    if (visible)
    {
      SaveCurrentCursorPos();

      ShowCursor();

      SaveCurrentClipCursor();

      SetNewClipCursor();
    }
    else
    {
      RestoreOldCursorPos();

      HideCursor();

      RestoreOldClipCursor();
    }
  }
  else
  {
    ClearOldCursorPos();

    SetNewClipCursor();
  }
}

void HandleInputQueue()
{
  auto& queue = GetWndProcInputMsgQueue();
  auto& mutex = GetWndProcInputMsgQueueMutex();
  std::lock_guard<std::recursive_mutex> lock{mutex};
  while (!queue.empty())
  {
    WndProcInputMsg msg = queue.front();
    auto& callbacks = GetOnInputQueueEntryCallbacks();
    callbacks.Run(msg.hwnd_, msg.msg_, msg.wparam_, msg.lparam_);
    queue.pop();
  }
}

InputInterface& GetInputInterface() HADESMEM_DETAIL_NOEXCEPT
{
  static InputImpl input_impl;
  return input_impl;
}

void InitializeInput()
{
  auto& window = GetWindowInterface();
  window.RegisterOnWndProcMsg(WindowProcCallback);

  auto& cursor = GetCursorInterface();
  cursor.RegisterOnSetCursor(OnSetCursor);
  cursor.RegisterOnGetCursorPos(OnGetCursorPos);
  cursor.RegisterOnSetCursorPos(OnSetCursorPos);
  cursor.RegisterOnShowCursor(OnShowCursor);
  cursor.RegisterOnClipCursor(OnClipCursor);
  cursor.RegisterOnGetClipCursor(OnGetClipCursor);

  auto& direct_input = GetDirectInputInterface();
  direct_input.RegisterOnDirectInput(OnDirectInput);
}
}
}
<commit_msg>* [Cerberus] Fix exception during shut-down of "AaaaaAAaaaAAAaaAAAAaAAAAA!!! for the Awesome".<commit_after>// Copyright (C) 2010-2014 Joshua Boyce.
// See the file COPYING for copying permission.

#include "input.hpp"

#include <algorithm>
#include <cstdint>
#include <mutex>
#include <queue>

#include <windows.h>
#include <winnt.h>
#include <winternl.h>

#include <hadesmem/config.hpp>
#include <hadesmem/detail/smart_handle.hpp>
#include <hadesmem/detail/str_conv.hpp>

#include "callbacks.hpp"
#include "cursor.hpp"
#include "direct_input.hpp"
#include "hook_disabler.hpp"
#include "render.hpp"
#include "main.hpp"
#include "window.hpp"

namespace
{
hadesmem::cerberus::Callbacks<hadesmem::cerberus::OnInputQueueEntry>&
  GetOnInputQueueEntryCallbacks()
{
  static hadesmem::cerberus::Callbacks<hadesmem::cerberus::OnInputQueueEntry>
    callbacks;
  return callbacks;
}

class InputImpl : public hadesmem::cerberus::InputInterface
{
public:
  virtual std::size_t RegisterOnInputQueueEntry(
    std::function<hadesmem::cerberus::OnInputQueueEntry> const& callback) final
  {
    auto& callbacks = GetOnInputQueueEntryCallbacks();
    return callbacks.Register(callback);
  }

  virtual void UnregisterOnInputQueueEntry(std::size_t id) final
  {
    auto& callbacks = GetOnInputQueueEntryCallbacks();
    return callbacks.Unregister(id);
  }
};

int& GetShowCursorCount() HADESMEM_DETAIL_NOEXCEPT
{
  static int show_cursor_count{};
  return show_cursor_count;
}

std::pair<bool, HCURSOR>& GetOldCursor() HADESMEM_DETAIL_NOEXCEPT
{
  static std::pair<bool, HCURSOR> old_cursor{};
  return old_cursor;
}

std::pair<bool, POINT>& GetOldCursorPos() HADESMEM_DETAIL_NOEXCEPT
{
  static std::pair<bool, POINT> cursor_pos{};
  return cursor_pos;
}

RECT& GetOldClipCursor() HADESMEM_DETAIL_NOEXCEPT
{
  static RECT old_clip_cursor{};
  return old_clip_cursor;
}

struct WndProcInputMsg
{
  HWND hwnd_;
  UINT msg_;
  WPARAM wparam_;
  LPARAM lparam_;
};

std::queue<WndProcInputMsg>& GetWndProcInputMsgQueue()
{
  static std::queue<WndProcInputMsg> queue;
  return queue;
}

std::recursive_mutex& GetWndProcInputMsgQueueMutex()
{
  static std::recursive_mutex mutex;
  return mutex;
}

void SetOrRestoreCursor(bool visible)
{
  hadesmem::cerberus::HookDisabler disable_set_cursor_hook{
    &hadesmem::cerberus::GetDisableSetCursorHook()};

  auto& old_cursor = GetOldCursor();

  if (visible)
  {
    HCURSOR const arrow_cursor = ::LoadCursorW(nullptr, IDC_ARROW);
    if (!arrow_cursor)
    {
      DWORD const last_error = ::GetLastError();
      HADESMEM_DETAIL_THROW_EXCEPTION(
        hadesmem::Error{} << hadesmem::ErrorString{"LoadCursorW failed."}
                          << hadesmem::ErrorCodeWinLast{last_error});
    }

    HADESMEM_DETAIL_TRACE_A("Setting arrow cursor.");
    old_cursor.second = ::SetCursor(arrow_cursor);
  }
  else if (old_cursor.first)
  {
    HADESMEM_DETAIL_TRACE_A("Setting old cursor.");
    old_cursor.second = ::SetCursor(old_cursor.second);
  }

  old_cursor.first = true;
}

void SaveCurrentCursorPos()
{
  auto& old_cursor_pos = GetOldCursorPos();

  hadesmem::cerberus::HookDisabler disable_get_cursor_pos_hook{
    &hadesmem::cerberus::GetDisableGetCursorPosHook()};

  POINT cur_cursor_pos{};
  if (!::GetCursorPos(&cur_cursor_pos))
  {
    DWORD const last_error = ::GetLastError();
    HADESMEM_DETAIL_THROW_EXCEPTION(
      hadesmem::Error{} << hadesmem::ErrorString{"GetCursorPos failed."}
                        << hadesmem::ErrorCodeWinLast{last_error});
  }

  old_cursor_pos.first = true;
  old_cursor_pos.second = cur_cursor_pos;
}

void ClearOldCursorPos()
{
  auto& old_cursor_pos = GetOldCursorPos();
  old_cursor_pos.first = false;
  old_cursor_pos.second = POINT{};
}

void RestoreOldCursorPos()
{
  auto& old_cursor_pos = GetOldCursorPos();

  if (!old_cursor_pos.first)
  {
    return;
  }

  hadesmem::cerberus::HookDisabler disable_set_cursor_pos_hook{
    &hadesmem::cerberus::GetDisableSetCursorPosHook()};

  if (!::SetCursorPos(old_cursor_pos.second.x, old_cursor_pos.second.y))
  {
    DWORD const last_error = ::GetLastError();
    HADESMEM_DETAIL_THROW_EXCEPTION(
      hadesmem::Error{} << hadesmem::ErrorString{"SetCursorPos failed."}
                        << hadesmem::ErrorCodeWinLast{last_error});
  }

  ClearOldCursorPos();
}

void ShowCursor()
{
  hadesmem::cerberus::HookDisabler disable_show_cursor_hook{
    &hadesmem::cerberus::GetDisableShowCursorHook()};

  auto& show_cursor_count = GetShowCursorCount();
  do
  {
    HADESMEM_DETAIL_TRACE_A("Showing cursor.");
    ++show_cursor_count;
  } while (::ShowCursor(TRUE) < 0);
}

void HideCursor()
{
  hadesmem::cerberus::HookDisabler disable_show_cursor_hook{
    &hadesmem::cerberus::GetDisableShowCursorHook()};

  auto& show_cursor_count = GetShowCursorCount();
  while (show_cursor_count > 0)
  {
    HADESMEM_DETAIL_TRACE_A("Hiding cursor.");
    --show_cursor_count;
    ::ShowCursor(FALSE);
  }
}

void SaveCurrentClipCursor()
{
  hadesmem::cerberus::HookDisabler disable_get_clip_cursor_hook{
    &hadesmem::cerberus::GetDisableGetClipCursorHook()};

  RECT clip_cursor{};
  if (!::GetClipCursor(&clip_cursor))
  {
    DWORD const last_error = ::GetLastError();
    HADESMEM_DETAIL_THROW_EXCEPTION(
      hadesmem::Error{} << hadesmem::ErrorString{"GetClipCursor failed."}
                        << hadesmem::ErrorCodeWinLast{last_error});
  }

  HADESMEM_DETAIL_TRACE_FORMAT_A("Saving current clip cursor: Left [%ld] Top "
                                 "[%ld] Right [%ld] Bottom [%ld]",
                                 clip_cursor.left,
                                 clip_cursor.top,
                                 clip_cursor.right,
                                 clip_cursor.bottom);

  auto& old_clip_cursor = GetOldClipCursor();
  old_clip_cursor = clip_cursor;
}

void ClipCursorWrap(RECT clip_cursor)
{
  hadesmem::cerberus::HookDisabler disable_clip_cursor_hook{
    &hadesmem::cerberus::GetDisableClipCursorHook()};

  if (!::ClipCursor(&clip_cursor))
  {
    DWORD const last_error = ::GetLastError();
    HADESMEM_DETAIL_THROW_EXCEPTION(
      hadesmem::Error{} << hadesmem::ErrorString{"ClipCursor failed."}
                        << hadesmem::ErrorCodeWinLast{last_error});
  }
}

void SetNewClipCursor()
{
  auto const wnd = hadesmem::cerberus::GetCurrentWindow();
  if (!::IsWindow(wnd))
  {
    HADESMEM_DETAIL_TRACE_A("WARNING! Invalid window.");
    return;
  }

  RECT new_clip_cursor{};
  if (!::GetWindowRect(wnd, &new_clip_cursor))
  {
    DWORD const last_error = ::GetLastError();
    HADESMEM_DETAIL_THROW_EXCEPTION(
      hadesmem::Error{} << hadesmem::ErrorString{"GetWindowRect failed."}
                        << hadesmem::ErrorCodeWinLast{last_error});
  }

  HADESMEM_DETAIL_TRACE_FORMAT_A(
    "Setting new clip cursor: Left [%ld] Top [%ld] Right [%ld] Bottom [%ld]",
    new_clip_cursor.left,
    new_clip_cursor.top,
    new_clip_cursor.right,
    new_clip_cursor.bottom);

  ClipCursorWrap(new_clip_cursor);
}

void RestoreOldClipCursor()
{
  auto& clip_cursor = GetOldClipCursor();

  HADESMEM_DETAIL_TRACE_FORMAT_A("Restoring old clip cursor: Left [%ld] Top "
                                 "[%ld] Right [%ld] Bottom [%ld]",
                                 clip_cursor.left,
                                 clip_cursor.top,
                                 clip_cursor.right,
                                 clip_cursor.bottom);

  ClipCursorWrap(clip_cursor);
}

void ToggleGuiVisible()
{
  auto const visible = !hadesmem::cerberus::GetGuiVisible();
  HADESMEM_DETAIL_TRACE_A(visible ? "Showing GUI." : "Hiding GUI.");
  hadesmem::cerberus::SetGuiVisible(visible, !visible);
}

void WindowProcCallback(
  HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, bool* handled)
{
  {
    auto& queue = GetWndProcInputMsgQueue();
    auto& mutex = GetWndProcInputMsgQueueMutex();
    std::lock_guard<std::recursive_mutex> lock{mutex};
    queue.push(WndProcInputMsg{hwnd, msg, wparam, lparam});
  }

  if (msg == WM_KEYDOWN && !((lparam >> 30) & 1) && wparam == VK_F9 &&
      ::GetAsyncKeyState(VK_SHIFT) & 0x8000)
  {
    ToggleGuiVisible();
    *handled = true;
    return;
  }

  auto const visible = hadesmem::cerberus::GetGuiVisible();
  bool const blocked_msg = msg == WM_INPUT ||
                           (msg >= WM_KEYFIRST && msg <= WM_KEYLAST) ||
                           (msg >= WM_MOUSEFIRST && msg <= WM_MOUSELAST);
  if (visible && blocked_msg)
  {
    *handled = true;
    return;
  }
}

void OnSetCursor(HCURSOR cursor,
                 bool* handled,
                 HCURSOR* retval) HADESMEM_DETAIL_NOEXCEPT
{
  auto& old_cursor = GetOldCursor();
  HCURSOR const old_cursor_raw = old_cursor.second;
  old_cursor.first = true;
  old_cursor.second = cursor;

  if (hadesmem::cerberus::GetGuiVisible())
  {
    *retval = old_cursor_raw;
    *handled = true;
  }
}

void OnDirectInput(bool* handled) HADESMEM_DETAIL_NOEXCEPT
{
  if (hadesmem::cerberus::GetGuiVisible())
  {
    *handled = true;
  }
}

void OnGetCursorPos(LPPOINT point, bool* handled) HADESMEM_DETAIL_NOEXCEPT
{
  if (hadesmem::cerberus::GetGuiVisible() && point)
  {
    auto& old_cursor_pos = GetOldCursorPos();
    point->x = old_cursor_pos.second.x;
    point->y = old_cursor_pos.second.y;

    *handled = true;
  }
}

void OnSetCursorPos(int x, int y, bool* handled) HADESMEM_DETAIL_NOEXCEPT
{
  if (hadesmem::cerberus::GetGuiVisible())
  {
    auto& old_cursor_pos = GetOldCursorPos();
    old_cursor_pos.first = true;
    old_cursor_pos.second.x = x;
    old_cursor_pos.second.y = y;

    *handled = true;
  }
}

void
  OnShowCursor(BOOL show, bool* handled, int* retval) HADESMEM_DETAIL_NOEXCEPT
{
  if (hadesmem::cerberus::GetGuiVisible())
  {
    auto& show_cursor_count = GetShowCursorCount();
    if (show)
    {
      ++show_cursor_count;
    }
    else
    {
      --show_cursor_count;
    }

    *retval = show_cursor_count;
    *handled = true;
  }
}

void OnClipCursor(RECT const* rect,
                  bool* handled,
                  BOOL* retval) HADESMEM_DETAIL_NOEXCEPT
{
  if (hadesmem::cerberus::GetGuiVisible() && rect)
  {
    auto& clip_cursor = GetOldClipCursor();
    clip_cursor = *rect;
    *retval = TRUE;
    *handled = true;
  }
}

void OnGetClipCursor(RECT* rect,
                     bool* handled,
                     BOOL* retval) HADESMEM_DETAIL_NOEXCEPT
{
  if (hadesmem::cerberus::GetGuiVisible() && rect)
  {
    auto& clip_cursor = GetOldClipCursor();
    *rect = clip_cursor;
    *retval = TRUE;
    *handled = true;
  }
}
}

namespace hadesmem
{
namespace cerberus
{
void SetGuiVisibleForInput(bool visible, bool old_visible)
{
  if (visible != old_visible)
  {
    SetOrRestoreCursor(visible);

    if (visible)
    {
      SaveCurrentCursorPos();

      ShowCursor();

      SaveCurrentClipCursor();

      SetNewClipCursor();
    }
    else
    {
      RestoreOldCursorPos();

      HideCursor();

      RestoreOldClipCursor();
    }
  }
  else
  {
    ClearOldCursorPos();

    SetNewClipCursor();
  }
}

void HandleInputQueue()
{
  auto& queue = GetWndProcInputMsgQueue();
  auto& mutex = GetWndProcInputMsgQueueMutex();
  std::lock_guard<std::recursive_mutex> lock{mutex};
  while (!queue.empty())
  {
    WndProcInputMsg msg = queue.front();
    auto& callbacks = GetOnInputQueueEntryCallbacks();
    callbacks.Run(msg.hwnd_, msg.msg_, msg.wparam_, msg.lparam_);
    queue.pop();
  }
}

InputInterface& GetInputInterface() HADESMEM_DETAIL_NOEXCEPT
{
  static InputImpl input_impl;
  return input_impl;
}

void InitializeInput()
{
  auto& window = GetWindowInterface();
  window.RegisterOnWndProcMsg(WindowProcCallback);

  auto& cursor = GetCursorInterface();
  cursor.RegisterOnSetCursor(OnSetCursor);
  cursor.RegisterOnGetCursorPos(OnGetCursorPos);
  cursor.RegisterOnSetCursorPos(OnSetCursorPos);
  cursor.RegisterOnShowCursor(OnShowCursor);
  cursor.RegisterOnClipCursor(OnClipCursor);
  cursor.RegisterOnGetClipCursor(OnGetClipCursor);

  auto& direct_input = GetDirectInputInterface();
  direct_input.RegisterOnDirectInput(OnDirectInput);
}
}
}
<|endoftext|>


<commit_before>/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
 *
 * This application is open source and may be redistributed and/or modified
 * freely and without restriction, both in commericial and non commericial applications,
 * as long as this copyright notice is maintained.
 *
 * This application 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.
*/

#include <osgDB/ReadFile>
#include <osgUtil/Optimizer>
#include <osg/CoordinateSystemNode>

#include <osg/Switch>
#include <osgText/Text>

#include <osgViewer/Viewer>
#include <osgViewer/StatsHandler>
#include <osgViewer/HelpHandler>

#include <osgGA/TrackballManipulator>
#include <osgGA/FlightManipulator>
#include <osgGA/DriveManipulator>
#include <osgGA/KeySwitchMatrixManipulator>
#include <osgGA/StateSetManipulator>
#include <osgGA/AnimationPathManipulator>
#include <osgGA/TerrainManipulator>

#include <iostream>

class ThreadingHandler : public osgGA::GUIEventHandler
{
public:

    ThreadingHandler() {}

    bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
    {
        osgViewer::Viewer* viewer = dynamic_cast<osgViewer::Viewer*>(&aa);
        if (!viewer) return false;

        switch(ea.getEventType())
        {
            case(osgGA::GUIEventAdapter::KEYUP):
            {
                if (ea.getKey()=='m')
                {
#if 0
                    switch(viewer->getThreadingModel())
                    {
                        case(osgViewer::Viewer::SingleThreaded):
                            viewer->setThreadingModel(osgViewer::Viewer::CullDrawThreadPerContext);
                            osg::notify(osg::NOTICE)<<"Threading model 'CullDrawThreadPerContext' selected."<<std::endl;
                            break;
                        case(osgViewer::Viewer::CullDrawThreadPerContext):
                            viewer->setThreadingModel(osgViewer::Viewer::DrawThreadPerContext);
                            osg::notify(osg::NOTICE)<<"Threading model 'DrawThreadPerContext' selected."<<std::endl;
                            break;
                        case(osgViewer::Viewer::DrawThreadPerContext):
                            viewer->setThreadingModel(osgViewer::Viewer::SingleThreaded);
                            osg::notify(osg::NOTICE)<<"Threading model 'SingleThreaded' selected."<<std::endl;
                            break;
                        default:
                            break;
                    }
#else
                    switch(viewer->getThreadingModel())
                    {
                        case(osgViewer::Viewer::SingleThreaded):
                            viewer->setThreadingModel(osgViewer::Viewer::CullDrawThreadPerContext);
                            osg::notify(osg::NOTICE)<<"Threading model 'CullDrawThreadPerContext' selected."<<std::endl;
                            break;
                        case(osgViewer::Viewer::CullDrawThreadPerContext):
                            viewer->setThreadingModel(osgViewer::Viewer::DrawThreadPerContext);
                            osg::notify(osg::NOTICE)<<"Threading model 'DrawThreadPerContext' selected."<<std::endl;
                            break;
                        case(osgViewer::Viewer::DrawThreadPerContext):
                            viewer->setThreadingModel(osgViewer::Viewer::CullThreadPerCameraDrawThreadPerContext);
                            osg::notify(osg::NOTICE)<<"Threading model 'CullThreadPerCameraDrawThreadPerContext' selected."<<std::endl;
                            break;
                        case(osgViewer::Viewer::CullThreadPerCameraDrawThreadPerContext):
                            viewer->setThreadingModel(osgViewer::Viewer::SingleThreaded);
                            osg::notify(osg::NOTICE)<<"Threading model 'SingleThreaded' selected."<<std::endl;
                            break;
                        case(osgViewer::Viewer::AutomaticSelection):
                            viewer->setThreadingModel(viewer->suggestBestThreadingModel());
                            osg::notify(osg::NOTICE)<<"Threading model 'AutomaticSelection' selected."<<std::endl;
                            break;
                    }
#endif
                    return true;
                }
                if (ea.getKey()=='e')
                {
                    switch(viewer->getEndBarrierPosition())
                    {
                        case(osgViewer::Viewer::BeforeSwapBuffers):
                            viewer->setEndBarrierPosition(osgViewer::Viewer::AfterSwapBuffers);
                            osg::notify(osg::NOTICE)<<"Threading model 'AfterSwapBuffers' selected."<<std::endl;
                            break;
                        case(osgViewer::Viewer::AfterSwapBuffers):
                            viewer->setEndBarrierPosition(osgViewer::Viewer::BeforeSwapBuffers);
                            osg::notify(osg::NOTICE)<<"Threading model 'BeforeSwapBuffers' selected."<<std::endl;
                            break;
                    }
                    return true;
                }
            }
            default: break;
        }

        return false;
    }

    /** Get the keyboard and mouse usage of this manipulator.*/
    virtual void getUsage(osg::ApplicationUsage& usage) const
    {
        usage.addKeyboardMouseBinding("m","Toggle threading model.");
        usage.addKeyboardMouseBinding("e","Toggle the placement of the end of frame barrier.");
    }

    bool _done;
};

int main(int argc, char** argv)
{
    // use an ArgumentParser object to manage the program arguments.
    osg::ArgumentParser arguments(&argc,argv);

    arguments.getApplicationUsage()->setApplicationName(arguments.getApplicationName());
    arguments.getApplicationUsage()->setDescription(arguments.getApplicationName()+" is the standard OpenSceneGraph example which loads and visualises 3d models.");
    arguments.getApplicationUsage()->setCommandLineUsage(arguments.getApplicationName()+" [options] filename ...");
    arguments.getApplicationUsage()->addCommandLineOption("--image <filename>","Load an image and render it on a quad");
    arguments.getApplicationUsage()->addCommandLineOption("--dem <filename>","Load an image/DEM and render it on a HeightField");
    arguments.getApplicationUsage()->addCommandLineOption("-h or --help","Display command line parameters");
    arguments.getApplicationUsage()->addCommandLineOption("--help-env","Display environmental variables available");
    arguments.getApplicationUsage()->addCommandLineOption("--help-keys","Display keyboard & mouse bindings available");
    arguments.getApplicationUsage()->addCommandLineOption("--help-all","Display all command line, env vars and keyboard & mouse bindings.");
    arguments.getApplicationUsage()->addCommandLineOption("--SingleThreaded","Select SingleThreaded threading model for viewer.");
    arguments.getApplicationUsage()->addCommandLineOption("--CullDrawThreadPerContext","Select CullDrawThreadPerContext threading model for viewer.");
    arguments.getApplicationUsage()->addCommandLineOption("--DrawThreadPerContext","Select DrawThreadPerContext threading model for viewer.");
    arguments.getApplicationUsage()->addCommandLineOption("--CullThreadPerCameraDrawThreadPerContext","Select CullThreadPerCameraDrawThreadPerContext threading model for viewer.");

    // if user request help write it out to cout.
    bool helpAll = arguments.read("--help-all");
    unsigned int helpType = ((helpAll || arguments.read("-h") || arguments.read("--help"))? osg::ApplicationUsage::COMMAND_LINE_OPTION : 0 ) |
                            ((helpAll ||  arguments.read("--help-env"))? osg::ApplicationUsage::ENVIRONMENTAL_VARIABLE : 0 ) |
                            ((helpAll ||  arguments.read("--help-keys"))? osg::ApplicationUsage::KEYBOARD_MOUSE_BINDING : 0 );
    if (helpType)
    {
        arguments.getApplicationUsage()->write(std::cout, helpType);
        return 1;
    }

    // report any errors if they have occurred when parsing the program arguments.
    if (arguments.errors())
    {
        arguments.writeErrorMessages(std::cout);
        return 1;
    }

    if (arguments.argc()<=1)
    {
        arguments.getApplicationUsage()->write(std::cout,osg::ApplicationUsage::COMMAND_LINE_OPTION);
        return 1;
    }

    osgViewer::Viewer viewer;

    // set up the camera manipulators.
    {
        osg::ref_ptr<osgGA::KeySwitchMatrixManipulator> keyswitchManipulator = new osgGA::KeySwitchMatrixManipulator;

        keyswitchManipulator->addMatrixManipulator( '1', "Trackball", new osgGA::TrackballManipulator() );
        keyswitchManipulator->addMatrixManipulator( '2', "Flight", new osgGA::FlightManipulator() );
        keyswitchManipulator->addMatrixManipulator( '3', "Drive", new osgGA::DriveManipulator() );
        keyswitchManipulator->addMatrixManipulator( '4', "Terrain", new osgGA::TerrainManipulator() );

        std::string pathfile;
        char keyForAnimationPath = '5';
        while (arguments.read("-p",pathfile))
        {
            osgGA::AnimationPathManipulator* apm = new osgGA::AnimationPathManipulator(pathfile);
            if (apm || !apm->valid())
            {
                unsigned int num = keyswitchManipulator->getNumMatrixManipulators();
                keyswitchManipulator->addMatrixManipulator( keyForAnimationPath, "Path", apm );
                keyswitchManipulator->selectMatrixManipulator(num);
                ++keyForAnimationPath;
            }
        }

        viewer.setCameraManipulator( keyswitchManipulator.get() );
    }

    // add the state manipulator
    viewer.addEventHandler( new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()) );

    // add the thread model handler
    viewer.addEventHandler(new ThreadingHandler);

    // add the stats handler
    viewer.addEventHandler(new osgViewer::StatsHandler);

    // add the help handler
    viewer.addEventHandler(new osgViewer::HelpHandler(arguments.getApplicationUsage()));

    while (arguments.read("--SingleThreaded")) viewer.setThreadingModel(osgViewer::Viewer::SingleThreaded);
    while (arguments.read("--CullDrawThreadPerContext")) viewer.setThreadingModel(osgViewer::Viewer::CullDrawThreadPerContext);
    while (arguments.read("--DrawThreadPerContext")) viewer.setThreadingModel(osgViewer::Viewer::DrawThreadPerContext);
    while (arguments.read("--CullThreadPerCameraDrawThreadPerContext")) viewer.setThreadingModel(osgViewer::Viewer::CullThreadPerCameraDrawThreadPerContext);

    unsigned int screenNum;
    while (arguments.read("--screen",screenNum))
    {
        viewer.setUpViewOnSingleScreen(screenNum);
    }

    // load the data
    osg::ref_ptr<osg::Node> loadedModel = osgDB::readNodeFiles(arguments);
    if (!loadedModel)
    {
        std::cout << arguments.getApplicationName() <<": No data loaded" << std::endl;
        return 1;
    }

    // any option left unread are converted into errors to write out later.
    arguments.reportRemainingOptionsAsUnrecognized();

    // report any errors if they have occurred when parsing the program arguments.
    if (arguments.errors())
    {
        arguments.writeErrorMessages(std::cout);
        return 1;
    }

    // optimize the scene graph, remove redundant nodes and state etc.
    osgUtil::Optimizer optimizer;
    optimizer.optimize(loadedModel.get());

    viewer.setSceneData( loadedModel.get() );

    return viewer.run();
}
<commit_msg>Added fullscreen toggle event handler<commit_after>/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
 *
 * This application is open source and may be redistributed and/or modified
 * freely and without restriction, both in commericial and non commericial applications,
 * as long as this copyright notice is maintained.
 *
 * This application 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.
*/

#include <osgDB/ReadFile>
#include <osgUtil/Optimizer>
#include <osg/CoordinateSystemNode>

#include <osg/Switch>
#include <osgText/Text>

#include <osgViewer/Viewer>
#include <osgViewer/StatsHandler>
#include <osgViewer/HelpHandler>

#include <osgGA/TrackballManipulator>
#include <osgGA/FlightManipulator>
#include <osgGA/DriveManipulator>
#include <osgGA/KeySwitchMatrixManipulator>
#include <osgGA/StateSetManipulator>
#include <osgGA/AnimationPathManipulator>
#include <osgGA/TerrainManipulator>

#include <iostream>

class ThreadingHandler : public osgGA::GUIEventHandler
{
public:

    ThreadingHandler() {}

    bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
    {
        osgViewer::Viewer* viewer = dynamic_cast<osgViewer::Viewer*>(&aa);
        if (!viewer) return false;

        switch(ea.getEventType())
        {
            case(osgGA::GUIEventAdapter::KEYUP):
            {
                if (ea.getKey()=='m')
                {
                    switch(viewer->getThreadingModel())
                    {
                        case(osgViewer::Viewer::SingleThreaded):
                            viewer->setThreadingModel(osgViewer::Viewer::CullDrawThreadPerContext);
                            osg::notify(osg::NOTICE)<<"Threading model 'CullDrawThreadPerContext' selected."<<std::endl;
                            break;
                        case(osgViewer::Viewer::CullDrawThreadPerContext):
                            viewer->setThreadingModel(osgViewer::Viewer::DrawThreadPerContext);
                            osg::notify(osg::NOTICE)<<"Threading model 'DrawThreadPerContext' selected."<<std::endl;
                            break;
                        case(osgViewer::Viewer::DrawThreadPerContext):
                            viewer->setThreadingModel(osgViewer::Viewer::CullThreadPerCameraDrawThreadPerContext);
                            osg::notify(osg::NOTICE)<<"Threading model 'CullThreadPerCameraDrawThreadPerContext' selected."<<std::endl;
                            break;
                        case(osgViewer::Viewer::CullThreadPerCameraDrawThreadPerContext):
                            viewer->setThreadingModel(osgViewer::Viewer::SingleThreaded);
                            osg::notify(osg::NOTICE)<<"Threading model 'SingleThreaded' selected."<<std::endl;
                            break;
                        case(osgViewer::Viewer::AutomaticSelection):
                            viewer->setThreadingModel(viewer->suggestBestThreadingModel());
                            osg::notify(osg::NOTICE)<<"Threading model 'AutomaticSelection' selected."<<std::endl;
                            break;
                    }
                    return true;
                }
                if (ea.getKey()=='e')
                {
                    switch(viewer->getEndBarrierPosition())
                    {
                        case(osgViewer::Viewer::BeforeSwapBuffers):
                            viewer->setEndBarrierPosition(osgViewer::Viewer::AfterSwapBuffers);
                            osg::notify(osg::NOTICE)<<"Threading model 'AfterSwapBuffers' selected."<<std::endl;
                            break;
                        case(osgViewer::Viewer::AfterSwapBuffers):
                            viewer->setEndBarrierPosition(osgViewer::Viewer::BeforeSwapBuffers);
                            osg::notify(osg::NOTICE)<<"Threading model 'BeforeSwapBuffers' selected."<<std::endl;
                            break;
                    }
                    return true;
                }
            }
            default: break;
        }

        return false;
    }

    /** Get the keyboard and mouse usage of this manipulator.*/
    virtual void getUsage(osg::ApplicationUsage& usage) const
    {
        usage.addKeyboardMouseBinding("m","Toggle threading model.");
        usage.addKeyboardMouseBinding("e","Toggle the placement of the end of frame barrier.");
    }

    bool _done;
};

class FullScreenToggleHandler : public osgGA::GUIEventHandler
{
public:

    FullScreenToggleHandler() {}

    bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
    {
        osgViewer::Viewer* viewer = dynamic_cast<osgViewer::Viewer*>(&aa);
        if (!viewer) return false;

        switch(ea.getEventType())
        {
            case(osgGA::GUIEventAdapter::KEYUP):
            {
                if (ea.getKey()=='f')
                {
                    osgViewer::Viewer::Windows windows;
                    viewer->getWindows(windows);

                    for(osgViewer::Viewer::Windows::iterator itr = windows.begin();
                        itr != windows.end();
                        ++itr)
                    {
                        toggleFullscreen(*itr);
                    }
                }
            }
            default: break;
        }

        return false;
    }

    /** Get the keyboard and mouse usage of this manipulator.*/
    virtual void getUsage(osg::ApplicationUsage& usage) const
    {
        usage.addKeyboardMouseBinding("f","Toggle full screen.");
    }

    void toggleFullscreen(osgViewer::GraphicsWindow* window)
    {

        osg::GraphicsContext::WindowingSystemInterface* wsi = osg::GraphicsContext::getWindowingSystemInterface();
        if (!wsi)
        {
            osg::notify(osg::NOTICE)<<"Error, no WindowSystemInterface available, cannot toggle window fullscreen."<<std::endl;
            return;
        }

        unsigned int screen_width, screen_height;
        wsi->getScreenResolution(*(window->getTraits()), screen_width, screen_height);

        int x, y, width, height;
        window->getWindowRectangle(x, y, width, height);

        bool isFullScreen = x==0 && y==0 && width==screen_width && height==screen_height;
        if (isFullScreen)
        {
            window->setWindowRectangle(screen_width/4, screen_height/4, screen_width/2, screen_height/2);
            window->setWindowDecoration(true);
        }
        else
        {
            window->setWindowDecoration(false);
            window->setWindowRectangle(0, 0, screen_width, screen_height);
        }

        window->grabFocusIfPointerInWindow();

        return;

    }

    bool _done;
};

int main(int argc, char** argv)
{
    // use an ArgumentParser object to manage the program arguments.
    osg::ArgumentParser arguments(&argc,argv);

    arguments.getApplicationUsage()->setApplicationName(arguments.getApplicationName());
    arguments.getApplicationUsage()->setDescription(arguments.getApplicationName()+" is the standard OpenSceneGraph example which loads and visualises 3d models.");
    arguments.getApplicationUsage()->setCommandLineUsage(arguments.getApplicationName()+" [options] filename ...");
    arguments.getApplicationUsage()->addCommandLineOption("--image <filename>","Load an image and render it on a quad");
    arguments.getApplicationUsage()->addCommandLineOption("--dem <filename>","Load an image/DEM and render it on a HeightField");
    arguments.getApplicationUsage()->addCommandLineOption("-h or --help","Display command line parameters");
    arguments.getApplicationUsage()->addCommandLineOption("--help-env","Display environmental variables available");
    arguments.getApplicationUsage()->addCommandLineOption("--help-keys","Display keyboard & mouse bindings available");
    arguments.getApplicationUsage()->addCommandLineOption("--help-all","Display all command line, env vars and keyboard & mouse bindings.");
    arguments.getApplicationUsage()->addCommandLineOption("--SingleThreaded","Select SingleThreaded threading model for viewer.");
    arguments.getApplicationUsage()->addCommandLineOption("--CullDrawThreadPerContext","Select CullDrawThreadPerContext threading model for viewer.");
    arguments.getApplicationUsage()->addCommandLineOption("--DrawThreadPerContext","Select DrawThreadPerContext threading model for viewer.");
    arguments.getApplicationUsage()->addCommandLineOption("--CullThreadPerCameraDrawThreadPerContext","Select CullThreadPerCameraDrawThreadPerContext threading model for viewer.");

    // if user request help write it out to cout.
    bool helpAll = arguments.read("--help-all");
    unsigned int helpType = ((helpAll || arguments.read("-h") || arguments.read("--help"))? osg::ApplicationUsage::COMMAND_LINE_OPTION : 0 ) |
                            ((helpAll ||  arguments.read("--help-env"))? osg::ApplicationUsage::ENVIRONMENTAL_VARIABLE : 0 ) |
                            ((helpAll ||  arguments.read("--help-keys"))? osg::ApplicationUsage::KEYBOARD_MOUSE_BINDING : 0 );
    if (helpType)
    {
        arguments.getApplicationUsage()->write(std::cout, helpType);
        return 1;
    }

    // report any errors if they have occurred when parsing the program arguments.
    if (arguments.errors())
    {
        arguments.writeErrorMessages(std::cout);
        return 1;
    }

    if (arguments.argc()<=1)
    {
        arguments.getApplicationUsage()->write(std::cout,osg::ApplicationUsage::COMMAND_LINE_OPTION);
        return 1;
    }

    osgViewer::Viewer viewer;

    // set up the camera manipulators.
    {
        osg::ref_ptr<osgGA::KeySwitchMatrixManipulator> keyswitchManipulator = new osgGA::KeySwitchMatrixManipulator;

        keyswitchManipulator->addMatrixManipulator( '1', "Trackball", new osgGA::TrackballManipulator() );
        keyswitchManipulator->addMatrixManipulator( '2', "Flight", new osgGA::FlightManipulator() );
        keyswitchManipulator->addMatrixManipulator( '3', "Drive", new osgGA::DriveManipulator() );
        keyswitchManipulator->addMatrixManipulator( '4', "Terrain", new osgGA::TerrainManipulator() );

        std::string pathfile;
        char keyForAnimationPath = '5';
        while (arguments.read("-p",pathfile))
        {
            osgGA::AnimationPathManipulator* apm = new osgGA::AnimationPathManipulator(pathfile);
            if (apm || !apm->valid())
            {
                unsigned int num = keyswitchManipulator->getNumMatrixManipulators();
                keyswitchManipulator->addMatrixManipulator( keyForAnimationPath, "Path", apm );
                keyswitchManipulator->selectMatrixManipulator(num);
                ++keyForAnimationPath;
            }
        }

        viewer.setCameraManipulator( keyswitchManipulator.get() );
    }

    // add the state manipulator
    viewer.addEventHandler( new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()) );

    // add the thread model handler
    viewer.addEventHandler(new ThreadingHandler);

    // add the full screen toggle handler
    viewer.addEventHandler(new FullScreenToggleHandler);

    // add the stats handler
    viewer.addEventHandler(new osgViewer::StatsHandler);

    // add the help handler
    viewer.addEventHandler(new osgViewer::HelpHandler(arguments.getApplicationUsage()));

    while (arguments.read("--SingleThreaded")) viewer.setThreadingModel(osgViewer::Viewer::SingleThreaded);
    while (arguments.read("--CullDrawThreadPerContext")) viewer.setThreadingModel(osgViewer::Viewer::CullDrawThreadPerContext);
    while (arguments.read("--DrawThreadPerContext")) viewer.setThreadingModel(osgViewer::Viewer::DrawThreadPerContext);
    while (arguments.read("--CullThreadPerCameraDrawThreadPerContext")) viewer.setThreadingModel(osgViewer::Viewer::CullThreadPerCameraDrawThreadPerContext);

    unsigned int screenNum;
    while (arguments.read("--screen",screenNum))
    {
        viewer.setUpViewOnSingleScreen(screenNum);
    }

    // load the data
    osg::ref_ptr<osg::Node> loadedModel = osgDB::readNodeFiles(arguments);
    if (!loadedModel)
    {
        std::cout << arguments.getApplicationName() <<": No data loaded" << std::endl;
        return 1;
    }

    // any option left unread are converted into errors to write out later.
    arguments.reportRemainingOptionsAsUnrecognized();

    // report any errors if they have occurred when parsing the program arguments.
    if (arguments.errors())
    {
        arguments.writeErrorMessages(std::cout);
        return 1;
    }

    // optimize the scene graph, remove redundant nodes and state etc.
    osgUtil::Optimizer optimizer;
    optimizer.optimize(loadedModel.get());

    viewer.setSceneData( loadedModel.get() );

    return viewer.run();
}
<|endoftext|>