Code Samples

Here’s a recent sample of my frame code from my Bourbon game engine.
It’s a frame loop using a job-scheduler I built underneath.
I promise I didn’t add any comments for the website. For complex procedures such as this, I tend to block out the stages in comments before actually writing anything.

#include <concurrent_queue.h>
#include <ChunkBuffer.h>
#include "frame.h"
#include <scheduler.h>
#include "session.h"
#include <ISystem.h>

frame::frame() { }
frame::frame(const frame& o) : scene(o.scene) { }

void frame::Process(session* session)
{
	// time for dt
	auto frame_start = std::chrono::high_resolution_clock::now();
	auto* frame_scene = &this->scene;

	// Do addition of new components
	this->scene.ProcessModifications();

	// step 1
	// branch systems
	std::vector<std::function<void()>> systemJobs;
	auto stubs = session->GetSystemStubs();
	std::vector<scenedesc> subScenes(stubs.size());
	for (unsigned i = 0; i < stubs.size(); ++i)
	{
		auto stub = stubs[i];
		scenedesc* subScene = &subScenes[i];
		// fill relevant scene info
		subScene->dt = frame_scene->DT();
		systemJobs.push_back([stub, frame_scene, subScene] {
			// Clone request components and pack them
			frame_scene->FillRequest(*subScene, stub);
			// Send to the system
			stub.system->Update(*subScene);
		});
	}
	// add gameplay as last stub, give it the entire scene
	systemJobs.push_back([session, frame_scene] { session->session_config.gameplaystub.system->Update(*frame_scene); });
	// block til all the systems are done
	session->Scheduler()->Do("Systems", systemJobs, scheduler::PRIORITY::HIGH);

	// step 2
	// merge returned packs 
	for (auto& subScene : subScenes)
		this->Merge(subScene);

	// no modification allowed beyond this point

	// step 3
	// pack minimal subset to render queue
	MinimalSubsetPack pack(*frame_scene, session->session_config.renderer_requested_comps);
	pack.IntegrateTransforms();
	for (auto renderConnection : session->RenderConnections())
		renderConnection->Receive(pack);

	// compute dt
	this->DoDT(session, frame_start);

	// record stats
	session->Scheduler()->DoAsync("OutputStats", [session, frame_scene] {
		++session->stats.frames;
		std::cout << "fps:\t" << session->stats.fps << std::endl;
		std::cout << "dt:\t" << frame_scene->DT() << std::endl;
		std::cout << "frame:\t" << session->stats.frames << std::endl;
	}, scheduler::PRIORITY::LOW);


	// step 4
	// begin next frame
	// copy frame (n-1 frame guaranteed)
	frame* currentFrame = this;
	frame* newFrame = new frame(*currentFrame);
	auto nextFrameJob = session->Scheduler()->DoAsync("ProcessNextFrame", [session, newFrame, currentFrame] {
		session->Scheduler()->BeginSampling();
		// begin to process frame
		newFrame->Process(session);
		delete currentFrame;
		session->Scheduler()->EndSampling();
	}, scheduler::PRIORITY::MEDIUM);

	// done!
	// frame dies
}

void frame::Merge(scenedesc & subScene)
{
	// step 1 - merge modifications
	this->scene.components_to_add.reserve(this->scene.components_to_add.size() + subScene.components_to_add.size());
	this->scene.components_to_add.insert(this->scene.components_to_add.end(), subScene.components_to_add.begin(), subScene.components_to_add.end());

	// step 2 - merge delta vars
	this->scene.MergeComponents(subScene);
}

void frame::DoDT(session * session, std::chrono::time_point<std::chrono::steady_clock>& frame_start)
{
	auto frame_end = std::chrono::high_resolution_clock::now();
	auto dt = std::chrono::duration_cast<std::chrono::milliseconds>(frame_end - frame_start).count();
	this->scene.dt = (float)dt * .001f;
	// limit framerate
	auto wantedDt = 1.0f / (float)session->session_config.fpsLimit;
	if (this->scene.dt < wantedDt)
	{
		float timeToWaste = wantedDt - this->scene.dt;
		Sleep((DWORD)(timeToWaste * 1000.0f));
		// recurse to redo calculations and check wakeup
		DoDT(session, frame_start);
	}
	session->stats.fps = 1.0f / this->scene.dt;
}