Introduction
In this post, I will discuss about how did I implement double threading in my game engine to do game logic update and rendering at the same time. The following picture shows how my structure of my game engine is.

After all modules have been initialized, data have been loaded, I created an another thread to handle game play logic update and use the main thread to do the render job.
Why we need two threads to handle gameplay and render separately?
Two threads allow two progress that spends a lot of time to be executed synchronously. The two progress is the gameplay update and the frame rendering. Additionally, it ensures the application thread to run normally even when the render thread has a hard time rendering stuff. In this case, the FPS is very low, but the input query, physics update, and other modules are still having healthy behaviors in a correct time scale.
Data formats
The following two blocks shows my data formats for rendering a frame.
// Data required to render a frame
struct sDataRequiredToRenderAFrame
{
// Clip plane data
UniformBufferFormats::sClipPlane s_ClipPlane;
// A list to store all lists
std::vector<sPass> s_renderPasses;
// Lighting data
std::vector<cPointLight> s_pointLights;
std::vector<cSpotLight> s_spotLights;
cAmbientLight s_ambientLight;
cDirectionalLight s_directionalLight;
};
// Data required to go through a pass (a rendering pipeline)
struct sPass
{
// projection * view matrix
UniformBufferFormats::sFrame FrameData;
// Modle handle and transform information list
std::vector<std::pair<Graphics::cModel::HANDLE, cTransform>> ModelToTransform_map;
// Pass execution function
void(*RenderPassFunction) ();
sPass() {}
};
In Graphic.cpp, I stored an array of sDataRequiredToRenderAFrame to handle data swaping between application thread and render thread.
sDataRequiredToRenderAFrame s_dataRequiredToRenderAFrame[2];
auto* s_dataSubmittedByApplicationThread = &s_dataRequiredToRenderAFrame[0];
auto* s_dateRenderingByGraphicThread = &s_dataRequiredToRenderAFrame[1];
And I use two contional variables ( std::condition_variable in <condition_variable>) to do event handling between threads.
std::condition_variable s_whenAllDataHasBeenSubmittedFromApplicationThread;
std::condition_variable s_whenDataHasBeenSwappedInRenderThread;
std::mutex s_graphicMutex;
s_whenAllDataHasBeenSubmittedFromApplicationThread will be notifyed at the end of the while loop of the application thread.

s_whenAllDataHasBeenSubmittedFromApplicationThread will be notifyed after data is swapped in the render frame while loop in Graphic.cpp.

Submission functions
There are a lot of data that is required to be submitted to the render thread.
/** Submit function*/
void SubmitClipPlaneData(const glm::vec4& i_plane0, const glm::vec4& i_plane1 = glm::vec4(0, 0, 0, 0), const glm::vec4& i_plane2 = glm::vec4(0, 0, 0, 0), const glm::vec4& i_plane3 = glm::vec4(0, 0, 0, 0));
void SubmitLightingData(const std::vector<cPointLight>& i_pointLights, const std::vector<cSpotLight>& i_spotLights, const cAmbientLight& i_ambientLight, const cDirectionalLight& i_directionalLight);
void SubmitDataToBeRendered(const UniformBufferFormats::sFrame& i_frameData, const std::vector<std::pair<Graphics::cModel::HANDLE, cTransform>>& i_modelToTransform_map, void(*func_ptr)());
void SubmitTransformToBeDisplayedWithTransformGizmo(const std::vector< cTransform*>& i_transforms);
void ClearApplicationThreadData();