Make saving screenshot thread- and exception-safe
* introduce ResourceOwningThread wrapper for safely passing resources to new threads * make CEventQueue thread-safe * start screenshot saving thread using ResourceOwningThread * change direct call at end of writing screenshot to thread-safe event communicationmaster
parent
5005e760b9
commit
18f9bfb575
|
@ -105,7 +105,6 @@ struct ApplicationPrivate
|
||||||
|
|
||||||
CApplication::CApplication()
|
CApplication::CApplication()
|
||||||
: m_private(MakeUnique<ApplicationPrivate>())
|
: m_private(MakeUnique<ApplicationPrivate>())
|
||||||
, m_eventQueue(MakeUnique<CEventQueue>())
|
|
||||||
, m_configFile(MakeUnique<CConfigFile>())
|
, m_configFile(MakeUnique<CConfigFile>())
|
||||||
, m_input(MakeUnique<CInput>())
|
, m_input(MakeUnique<CInput>())
|
||||||
, m_pathManager(MakeUnique<CPathManager>())
|
, m_pathManager(MakeUnique<CPathManager>())
|
||||||
|
@ -590,6 +589,8 @@ bool CApplication::Create()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_eventQueue = MakeUnique<CEventQueue>();
|
||||||
|
|
||||||
// Create the robot application.
|
// Create the robot application.
|
||||||
m_controller = MakeUnique<CController>(this, !defaultValues);
|
m_controller = MakeUnique<CController>(this, !defaultValues);
|
||||||
|
|
||||||
|
|
|
@ -505,6 +505,8 @@ void InitializeEventTypeTexts()
|
||||||
EVENT_TYPE_TEXT[EVENT_STUDIO_RUN] = "EVENT_STUDIO_RUN";
|
EVENT_TYPE_TEXT[EVENT_STUDIO_RUN] = "EVENT_STUDIO_RUN";
|
||||||
EVENT_TYPE_TEXT[EVENT_STUDIO_REALTIME] = "EVENT_STUDIO_REALTIME";
|
EVENT_TYPE_TEXT[EVENT_STUDIO_REALTIME] = "EVENT_STUDIO_REALTIME";
|
||||||
EVENT_TYPE_TEXT[EVENT_STUDIO_STEP] = "EVENT_STUDIO_STEP";
|
EVENT_TYPE_TEXT[EVENT_STUDIO_STEP] = "EVENT_STUDIO_STEP";
|
||||||
|
|
||||||
|
EVENT_TYPE_TEXT[EVENT_WRITE_SCENE_FINISHED] = "EVENT_WRITE_SCENE_FINISHED";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ParseEventType(EventType eventType)
|
std::string ParseEventType(EventType eventType)
|
||||||
|
@ -546,47 +548,72 @@ std::string ParseEventType(EventType eventType)
|
||||||
|
|
||||||
|
|
||||||
CEventQueue::CEventQueue()
|
CEventQueue::CEventQueue()
|
||||||
{
|
: m_mutex{},
|
||||||
Flush();
|
m_fifo{},
|
||||||
}
|
m_head{0},
|
||||||
|
m_tail{0},
|
||||||
|
m_total{0}
|
||||||
|
{}
|
||||||
|
|
||||||
CEventQueue::~CEventQueue()
|
CEventQueue::~CEventQueue()
|
||||||
{
|
{}
|
||||||
}
|
|
||||||
|
|
||||||
void CEventQueue::Flush()
|
|
||||||
{
|
|
||||||
m_head = 0;
|
|
||||||
m_tail = 0;
|
|
||||||
m_total = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** If the maximum size of queue has been reached, returns \c false.
|
/** If the maximum size of queue has been reached, returns \c false.
|
||||||
Else, adds the event to the queue and returns \c true. */
|
Else, adds the event to the queue and returns \c true. */
|
||||||
bool CEventQueue::AddEvent(const Event &event)
|
bool CEventQueue::AddEvent(const Event &event)
|
||||||
{
|
{
|
||||||
if ( m_total >= MAX_EVENT_QUEUE )
|
bool result{};
|
||||||
|
|
||||||
|
SDL_LockMutex(*m_mutex);
|
||||||
|
|
||||||
|
if (m_total >= MAX_EVENT_QUEUE)
|
||||||
{
|
{
|
||||||
GetLogger()->Warn("Event queue flood!\n");
|
GetLogger()->Warn("Event queue flood!\n");
|
||||||
return false;
|
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_fifo[m_head++] = event;
|
||||||
|
|
||||||
|
if (m_head >= MAX_EVENT_QUEUE)
|
||||||
|
m_head = 0;
|
||||||
|
|
||||||
|
m_total++;
|
||||||
|
|
||||||
|
result = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_fifo[m_head++] = event;
|
SDL_UnlockMutex(*m_mutex);
|
||||||
if ( m_head >= MAX_EVENT_QUEUE ) m_head = 0;
|
|
||||||
m_total ++;
|
|
||||||
|
|
||||||
return true;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** If the queue is empty, returns \c false.
|
/** If the queue is empty, returns \c false.
|
||||||
Else, gets the event from the front, puts it into \a event and returns \c true. */
|
Else, gets the event from the front, puts it into \a event and returns \c true. */
|
||||||
bool CEventQueue::GetEvent(Event &event)
|
bool CEventQueue::GetEvent(Event &event)
|
||||||
{
|
{
|
||||||
if ( m_head == m_tail ) return false;
|
bool result{};
|
||||||
|
|
||||||
event = m_fifo[m_tail++];
|
SDL_LockMutex(*m_mutex);
|
||||||
if ( m_tail >= MAX_EVENT_QUEUE ) m_tail = 0;
|
|
||||||
m_total --;
|
|
||||||
|
|
||||||
return true;
|
if (m_head == m_tail)
|
||||||
|
{
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
event = m_fifo[m_tail++];
|
||||||
|
|
||||||
|
if (m_tail >= MAX_EVENT_QUEUE)
|
||||||
|
m_tail = 0;
|
||||||
|
|
||||||
|
m_total--;
|
||||||
|
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_UnlockMutex(*m_mutex);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,10 +27,11 @@
|
||||||
|
|
||||||
#include "common/key.h"
|
#include "common/key.h"
|
||||||
|
|
||||||
|
#include "common/thread/sdl_mutex_wrapper.h"
|
||||||
|
|
||||||
#include "math/point.h"
|
#include "math/point.h"
|
||||||
#include "math/vector.h"
|
#include "math/vector.h"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
\enum EventType
|
\enum EventType
|
||||||
\brief Type of event message
|
\brief Type of event message
|
||||||
|
@ -533,6 +534,8 @@ enum EventType
|
||||||
EVENT_STUDIO_REALTIME = 2052,
|
EVENT_STUDIO_REALTIME = 2052,
|
||||||
EVENT_STUDIO_STEP = 2053,
|
EVENT_STUDIO_STEP = 2053,
|
||||||
|
|
||||||
|
EVENT_WRITE_SCENE_FINISHED = 2100, //!< indicates end of writing scene (writing screenshot image)
|
||||||
|
|
||||||
//! Maximum value of standard events
|
//! Maximum value of standard events
|
||||||
EVENT_STD_MAX,
|
EVENT_STD_MAX,
|
||||||
|
|
||||||
|
@ -736,6 +739,8 @@ std::string ParseEventType(EventType eventType);
|
||||||
*
|
*
|
||||||
* Provides an interface to a global FIFO queue with events (both system- and user-generated).
|
* Provides an interface to a global FIFO queue with events (both system- and user-generated).
|
||||||
* The queue has a fixed maximum size but it should not be a problem.
|
* The queue has a fixed maximum size but it should not be a problem.
|
||||||
|
*
|
||||||
|
* This class is thread-safe
|
||||||
*/
|
*/
|
||||||
class CEventQueue
|
class CEventQueue
|
||||||
{
|
{
|
||||||
|
@ -749,14 +754,13 @@ public:
|
||||||
//! Object's destructor
|
//! Object's destructor
|
||||||
~CEventQueue();
|
~CEventQueue();
|
||||||
|
|
||||||
//! Empties the FIFO of events
|
|
||||||
void Flush();
|
|
||||||
//! Adds an event to the queue
|
//! Adds an event to the queue
|
||||||
bool AddEvent(const Event &event);
|
bool AddEvent(const Event &event);
|
||||||
//! Removes and returns an event from queue front
|
//! Removes and returns an event from queue front
|
||||||
bool GetEvent(Event &event);
|
bool GetEvent(Event &event);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
SDLMutexWrapper m_mutex;
|
||||||
Event m_fifo[MAX_EVENT_QUEUE];
|
Event m_fifo[MAX_EVENT_QUEUE];
|
||||||
int m_head;
|
int m_head;
|
||||||
int m_tail;
|
int m_tail;
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/thread/sdl_cond_wrapper.h"
|
||||||
|
#include "common/thread/sdl_mutex_wrapper.h"
|
||||||
|
|
||||||
|
#include <SDL_thread.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \class ResourceOwningThread
|
||||||
|
* \brief Wrapper around SDL thread allowing passing of resources in safe manner
|
||||||
|
*
|
||||||
|
* This class is a workaround for passing ownership of resources in a safe
|
||||||
|
* manner to newly created threads. It takes a pointer to a function to call
|
||||||
|
* in new thread and a unique_ptr to resource which is to be passed to the new thread.
|
||||||
|
*
|
||||||
|
* This is how it works:
|
||||||
|
* - in main thread: create a new thread passing to it a special temporary context,
|
||||||
|
* - in main thread: wait for synchronization signal that the ownership was passed,
|
||||||
|
* - in new thread: acquire the resource from the context
|
||||||
|
* - in new thread: signal back to main thread that the resource was acquired,
|
||||||
|
* - in main thread: clean up temporary context and exit
|
||||||
|
* - in new thread: run the specified function with the acquired resource.
|
||||||
|
*
|
||||||
|
* It's a bit complicated, but that's the safe (thread-safe and exception-safe)
|
||||||
|
* way of doing this.
|
||||||
|
*/
|
||||||
|
template<typename Resource>
|
||||||
|
class ResourceOwningThread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using ResourceUPtr = std::unique_ptr<Resource>;
|
||||||
|
using ThreadFunctionPtr = void(*)(ResourceUPtr);
|
||||||
|
|
||||||
|
ResourceOwningThread(ThreadFunctionPtr threadFunction, ResourceUPtr resource)
|
||||||
|
: m_threadFunction(threadFunction),
|
||||||
|
m_resource(std::move(resource))
|
||||||
|
{}
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
SDLMutexWrapper mutex;
|
||||||
|
SDLCondWrapper cond;
|
||||||
|
bool condition = false;
|
||||||
|
|
||||||
|
ThreadData data;
|
||||||
|
data.resource = std::move(m_resource);
|
||||||
|
data.threadFunction = m_threadFunction;
|
||||||
|
data.mutex = &mutex;
|
||||||
|
data.cond = &cond;
|
||||||
|
data.condition = &condition;
|
||||||
|
|
||||||
|
SDL_LockMutex(*mutex);
|
||||||
|
|
||||||
|
SDL_CreateThread(Run, reinterpret_cast<void*>(&data));
|
||||||
|
|
||||||
|
while (!condition)
|
||||||
|
{
|
||||||
|
SDL_CondWait(*cond, *mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_UnlockMutex(*mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static int Run(void* data)
|
||||||
|
{
|
||||||
|
ThreadFunctionPtr threadFunction = nullptr;
|
||||||
|
ResourceUPtr resource;
|
||||||
|
|
||||||
|
ThreadData* threadData = reinterpret_cast<ThreadData*>(data);
|
||||||
|
SDL_LockMutex(**threadData->mutex);
|
||||||
|
|
||||||
|
threadFunction = threadData->threadFunction;
|
||||||
|
resource = std::move(threadData->resource);
|
||||||
|
|
||||||
|
*threadData->condition = true;
|
||||||
|
SDL_CondSignal(**threadData->cond);
|
||||||
|
SDL_UnlockMutex(**threadData->mutex);
|
||||||
|
|
||||||
|
threadFunction(std::move(resource));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ThreadData
|
||||||
|
{
|
||||||
|
ResourceUPtr resource;
|
||||||
|
SDLMutexWrapper* mutex = nullptr;
|
||||||
|
SDLCondWrapper* cond = nullptr;
|
||||||
|
bool* condition = nullptr;
|
||||||
|
ThreadFunctionPtr threadFunction = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
ThreadFunctionPtr m_threadFunction;
|
||||||
|
ResourceUPtr m_resource;
|
||||||
|
};
|
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL_thread.h>
|
||||||
|
|
||||||
|
class SDLCondWrapper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SDLCondWrapper()
|
||||||
|
: m_cond(SDL_CreateCond())
|
||||||
|
{}
|
||||||
|
|
||||||
|
~SDLCondWrapper()
|
||||||
|
{
|
||||||
|
SDL_DestroyCond(m_cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDLCondWrapper(const SDLCondWrapper&) = delete;
|
||||||
|
SDLCondWrapper& operator=(const SDLCondWrapper&) = delete;
|
||||||
|
|
||||||
|
SDL_cond* operator*()
|
||||||
|
{
|
||||||
|
return m_cond;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SDL_cond* m_cond;
|
||||||
|
};
|
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL_thread.h>
|
||||||
|
|
||||||
|
class SDLMutexWrapper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SDLMutexWrapper()
|
||||||
|
: m_mutex(SDL_CreateMutex())
|
||||||
|
{}
|
||||||
|
|
||||||
|
~SDLMutexWrapper()
|
||||||
|
{
|
||||||
|
SDL_DestroyMutex(m_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDLMutexWrapper(const SDLMutexWrapper&) = delete;
|
||||||
|
SDLMutexWrapper& operator=(const SDLMutexWrapper&) = delete;
|
||||||
|
|
||||||
|
SDL_mutex* operator*()
|
||||||
|
{
|
||||||
|
return m_mutex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SDL_mutex* m_mutex;
|
||||||
|
};
|
|
@ -28,6 +28,8 @@
|
||||||
#include "common/logger.h"
|
#include "common/logger.h"
|
||||||
#include "common/make_unique.h"
|
#include "common/make_unique.h"
|
||||||
|
|
||||||
|
#include "common/thread/resource_owning_thread.h"
|
||||||
|
|
||||||
#include "graphics/core/device.h"
|
#include "graphics/core/device.h"
|
||||||
|
|
||||||
#include "graphics/engine/camera.h"
|
#include "graphics/engine/camera.h"
|
||||||
|
@ -484,29 +486,22 @@ void CEngine::FrameUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WriteScreenShotData
|
void CEngine::WriteScreenShot(const std::string& fileName, int width, int height)
|
||||||
{
|
{
|
||||||
std::unique_ptr<CImage> img;
|
auto data = MakeUnique<WriteScreenShotData>();
|
||||||
std::string fileName;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool CEngine::WriteScreenShot(const std::string& fileName, int width, int height)
|
|
||||||
{
|
|
||||||
WriteScreenShotData* data = new WriteScreenShotData();
|
|
||||||
data->img = MakeUnique<CImage>(Math::IntPoint(width, height));
|
data->img = MakeUnique<CImage>(Math::IntPoint(width, height));
|
||||||
|
|
||||||
data->img->SetDataPixels(m_device->GetFrameBufferPixels());
|
data->img->SetDataPixels(m_device->GetFrameBufferPixels());
|
||||||
data->img->FlipVertically();
|
data->img->FlipVertically();
|
||||||
|
|
||||||
data->fileName = fileName;
|
data->fileName = fileName;
|
||||||
SDL_CreateThread(CEngine::WriteScreenShotThread, data);
|
|
||||||
return true;
|
ResourceOwningThread<WriteScreenShotData> thread(CEngine::WriteScreenShotThread, std::move(data));
|
||||||
|
thread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
int CEngine::WriteScreenShotThread(void* data_ptr)
|
void CEngine::WriteScreenShotThread(std::unique_ptr<WriteScreenShotData> data)
|
||||||
{
|
{
|
||||||
WriteScreenShotData* data = static_cast<WriteScreenShotData*>(data_ptr);
|
|
||||||
|
|
||||||
if ( data->img->SavePNG(data->fileName.c_str()) )
|
if ( data->img->SavePNG(data->fileName.c_str()) )
|
||||||
{
|
{
|
||||||
GetLogger()->Debug("Save screenshot saved successfully\n");
|
GetLogger()->Debug("Save screenshot saved successfully\n");
|
||||||
|
@ -516,10 +511,8 @@ int CEngine::WriteScreenShotThread(void* data_ptr)
|
||||||
GetLogger()->Error("%s!\n", data->img->GetError().c_str());
|
GetLogger()->Error("%s!\n", data->img->GetError().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
delete data;
|
Event event(EVENT_WRITE_SCENE_FINISHED);
|
||||||
|
CApplication::GetInstancePointer()->GetEventQueue()->AddEvent(event);
|
||||||
CRobotMain::GetInstancePointer()->IOWriteSceneFinished();
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEngine::GetPause()
|
bool CEngine::GetPause()
|
||||||
|
|
|
@ -726,7 +726,7 @@ public:
|
||||||
|
|
||||||
|
|
||||||
//! Writes a screenshot containing the current frame
|
//! Writes a screenshot containing the current frame
|
||||||
bool WriteScreenShot(const std::string& fileName, int width, int height);
|
void WriteScreenShot(const std::string& fileName, int width, int height);
|
||||||
|
|
||||||
|
|
||||||
//! Get pause mode
|
//! Get pause mode
|
||||||
|
@ -1348,7 +1348,12 @@ protected:
|
||||||
|
|
||||||
int GetEngineState(const ModelTriangle& triangle);
|
int GetEngineState(const ModelTriangle& triangle);
|
||||||
|
|
||||||
static int WriteScreenShotThread(void* data_ptr);
|
struct WriteScreenShotData
|
||||||
|
{
|
||||||
|
std::unique_ptr<CImage> img;
|
||||||
|
std::string fileName;
|
||||||
|
};
|
||||||
|
static void WriteScreenShotThread(std::unique_ptr<WriteScreenShotData> data);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
CApplication* m_app;
|
CApplication* m_app;
|
||||||
|
|
|
@ -660,6 +660,12 @@ bool CRobotMain::ProcessEvent(Event &event)
|
||||||
return EventFrame(event);
|
return EventFrame(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.type == EVENT_WRITE_SCENE_FINISHED)
|
||||||
|
{
|
||||||
|
IOWriteSceneFinished();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Management of the console.
|
// Management of the console.
|
||||||
if (event.type == EVENT_KEY_DOWN &&
|
if (event.type == EVENT_KEY_DOWN &&
|
||||||
event.key.key == KEY(BACKQUOTE)) // Pause ?
|
event.key.key == KEY(BACKQUOTE)) // Pause ?
|
||||||
|
|
Loading…
Reference in New Issue