void ArnoldAnimationPatch::renderSingleFrame(const set<Device*>& devices, string basepath, string filename) {
  if (m_mode != SimulationAnimationMode::STOPPED)
    disableContinuousRenderMode();

  FrameDeviceInfo frame;

  // Fulls time and mode for frame info.
  createFrameInfoHeader(frame);
  createFrameInfoBody(devices, frame);

  std::stringstream ss;
  ss << "Rendering single frame: " << frame.time;
  Logger::log(LDEBUG, ss.str());

  // Interrupts the current rendering if in the interactive mode.
  if (m_mode == SimulationAnimationMode::INTERACTIVE ||
    m_mode == SimulationAnimationMode::RECORDING) {
    interruptRender();
  }

  // Render immediately.
  if (!m_interface->isDistributedOpen()) {
	  m_interface->init(this->toJSON());
	  
	  // Check if we were able to open a connection
	  if (!m_interface->isDistributedOpen()) {
		  std::cerr << "Connection could not be established. " << 
			  "Check to make sure your host and port " << 
			  "parameters are correct and that nobody " << 
			  "else is currently using the remote renderer" << std::endl;

		  return;
	  }
  }

  bool success = false;
  float* bp = nullptr;
  int cid;
  int w = getWidth();
  int h = getHeight();

  if (CachingArnoldInterface* ci = dynamic_cast<CachingArnoldInterface*>(m_interface)) {
#ifdef USE_ARNOLD
    success = ci->render(devices, w, h, cid) == AI_SUCCESS;
#else
    success = ci->render(devices, w, h, cid) == 0;
#endif
    // we need to pull the proper buffer from the right context
    bp = ci->getBufferForContext(cid);
    frame.clear();
  }
  else {
    updateLight(devices);
    success = ArnoldPatch::renderLoop(devices);
    frame.clear();
    bp = getBufferPointer();
  }

  if (success) {
    unsigned char *bytes = new unsigned char[w * h * 4];
    floats_to_bytes(bytes, bp, w, h);

    string file = basepath + "/" + filename + ".png";

    if (!imageio_save_image(file.c_str(), bytes, getWidth(), getHeight())) {
      std::stringstream err_ss;
      err_ss << "Error to write png: " << filename;
      Logger::log(ERR, err_ss.str());
    }
  }

  if (CachingArnoldInterface* ci = dynamic_cast<CachingArnoldInterface*>(m_interface)) {
    ci->closeContext(cid);
  }
}
void ArnoldAnimationPatch::renderSingleFrameToBuffer(const set<Device*>& devices, unsigned char* buff, int w, int h) {
  if (m_mode != SimulationAnimationMode::STOPPED)
    disableContinuousRenderMode();

  FrameDeviceInfo frame;

  // Fulls time and mode for frame info.
  createFrameInfoHeader(frame);
  createFrameInfoBody(devices, frame, true);

  std::stringstream ss;
  ss << "Rendering single frame: " << frame.time;
  Logger::log(LDEBUG, ss.str());

  // Interrupts the current rendering if in the interactive mode.
  if (m_mode == SimulationAnimationMode::INTERACTIVE ||
    m_mode == SimulationAnimationMode::RECORDING) {
    interruptRender();
  }

  // Render immediately.
  if (!m_interface->isDistributedOpen()) {
	  m_interface->init(this->toJSON());

	  // Check if we were able to open a connection
	  if (!m_interface->isDistributedOpen()) {
		  std::cerr << "Connection could not be established. " <<
			  "Check to make sure your host and port " <<
			  "parameters are correct and that nobody " <<
			  "else is currently using the remote renderer" << std::endl;

		  return;
	  }
  }

  bool success = false;
  float* bp = nullptr;
  int cid;

  if (CachingArnoldInterface* ci = dynamic_cast<CachingArnoldInterface*>(m_interface)) {
#ifdef USE_ARNOLD
    success = ci->render(devices, w, h, cid) == AI_SUCCESS;
#else
    success = ci->render(devices, w, h, cid) == 0;
#endif
    // we need to pull the proper buffer from the right context
    bp = ci->getBufferForContext(cid);
    frame.clear();
  }
  else {
    updateLight(devices);
    success = ArnoldPatch::renderLoop(devices);
    frame.clear();
    bp = getBufferPointer();
  }

  if (success) {
    for (int j = 0; j < h; j++) {
      for (int i = 0; i < w; i++) {
        int offset = (j * w + i) * 4;

        // convert to bytes
        buff[offset] = static_cast<unsigned char>(bp[offset + 2] * 0xff);
        buff[offset + 1] = static_cast<unsigned char>(bp[offset + 1] * 0xff);
        buff[offset + 2] = static_cast<unsigned char>(bp[offset + 0] * 0xff);
        buff[offset + 3] = static_cast<unsigned char>(bp[offset + 3] * 0xff);
      }
    }
  }

  if (CachingArnoldInterface* ci = dynamic_cast<CachingArnoldInterface*>(m_interface)) {
    ci->closeContext(cid);
  }
}
void SimulationAnimationPatch::workerLoop() {
    FrameDeviceInfo frame;

    while(1) {
		// Releases the lock immediately if the queue is still empty.
		while (frame.time < 0) {
			if (m_mode == SimulationAnimationMode::STOPPED)
				continue;

			m_queue.lock();
			if (m_queuedFrameDeviceInfo.size() > 0) {
				if (m_mode == SimulationAnimationMode::RECORDING) {
					// Clears irrelated info
					std::vector<FrameDeviceInfo>::iterator i;
					for (i = m_queuedFrameDeviceInfo.begin();
						 i != m_queuedFrameDeviceInfo.end() &&
						 i->mode == SimulationAnimationMode::INTERACTIVE; i++) {
						i->clear();
					}
					m_queuedFrameDeviceInfo.erase(m_queuedFrameDeviceInfo.begin(), i);
                
					// Deep copy (since we're going to clear it)
					for (i = m_queuedFrameDeviceInfo.begin();
						 i != m_queuedFrameDeviceInfo.end(); i++) {
						// Copies the last recording info to do preview
						if (i->mode == SimulationAnimationMode::RECORDING) {
							frame.copyByValue(*i);
							// Waits to be rendered with higher sampling rate
							i->mode = SimulationAnimationMode::RENDERING;
						}
					}
				}
				else if (m_mode == SimulationAnimationMode::RENDERING) {
					// No frame would be skipped.
					frame = m_queuedFrameDeviceInfo[0];
					m_queuedFrameDeviceInfo.erase(m_queuedFrameDeviceInfo.begin());
				}
				else if (m_mode == SimulationAnimationMode::INTERACTIVE) {
					frame = m_queuedFrameDeviceInfo.back();
					for (FrameDeviceInfo &info : m_queuedFrameDeviceInfo) {
						if (info.time != m_queuedFrameDeviceInfo.back().time)
							info.clear();
					}
					m_queuedFrameDeviceInfo.clear();
				}
			}
			// No more frames for rendering
			else if (m_mode == SimulationAnimationMode::RENDERING) {
				onWorkerFinished();
			}
			m_queue.unlock();

			// The frame is valid.
			if (frame.time >= 0) {
				if (frame.mode == SimulationAnimationMode::STOPPED) {
					m_mode = SimulationAnimationMode::STOPPED;
					Logger::log(INFO, "Worker stopped...");

					return ;
				}
				else if (frame.mode == SimulationAnimationMode::RECORDING) {
					onRecording();
				}
				else if (frame.mode == SimulationAnimationMode::RENDERING) {
					onRendering();
				}
				// Finished recording work
				else if (frame.mode == SimulationAnimationMode::INTERACTIVE &&
						 (m_mode == SimulationAnimationMode::RENDERING)) {
					onWorkerFinished();
				}
			}
		}

		// Releases copies of devices.
		workerRender(frame);
		frame.clear();
    }
}