Skip to content

Instantly share code, notes, and snippets.

@poseidon4o
Last active June 23, 2022 21:29
Show Gist options
  • Save poseidon4o/3f97c84b2d505682aaa92fcda9b749a1 to your computer and use it in GitHub Desktop.
Save poseidon4o/3f97c84b2d505682aaa92fcda9b749a1 to your computer and use it in GitHub Desktop.
#include <mutex>
#include <vector>
#include <thread>
#include <condition_variable>
/// Size of the render target in console characters
const int WIDTH = 100;
const int HEIGHT = 30;
/// Render target, render threads write next frame here
/// 1 extra in width for terminating '\0'
char canvas[HEIGHT][WIDTH + 1];
/// Context for the "simulation" part of the program
struct AnimationContext {
std::vector<int> lines = {0, 10, 20, 30, 40, 50}; ///< Positions of the lines
bool working = true; ///< Flag used to control until when the animation thread will run
std::mutex animLock; ///< Lock used to protect #working and be used for #cvar
std::condition_variable cvar; ///< Used to signal the thread to when it needs to exit
};
void animationThread(AnimationContext &ctx) {
while (true) {
for (int &pos : ctx.lines) {
pos = (pos + 1) % WIDTH;
}
std::unique_lock<std::mutex> lock(ctx.animLock);
ctx.cvar.wait_for(lock, std::chrono::milliseconds(30), [&ctx] () {
return !ctx.working;
});
if (!ctx.working) {
break;
}
}
puts("animationThread thread exiting");
}
/// Shared context for all render threads
struct RenderContext {
AnimationContext &animationData; ///< Reference to the shared animation data, not null for render threads
bool working = true; ///< Used to signal when render threads need to exit
std::mutex renderLock; ///< The lock protecting the #working flag
};
void renderThread(RenderContext &ctx, int index, int count) {
const int perThread = HEIGHT / count;
const int start = perThread * index;
const int end = index == count - 1 ? HEIGHT : start + perThread;
while (true) {
const std::vector<int> &current = ctx.animationData.lines;
for (int r = start; r < end; r++) {
for (int c = 0; c < WIDTH; c++) {
canvas[r][c] = ' ';
for (int l = 0; l < current.size(); l++) {
if (current[l] == c) {
canvas[r][c] = '|';
}
}
}
canvas[r][WIDTH] = '\0';
}
std::lock_guard<std::mutex> lock(ctx.renderLock);
if (!ctx.working) {
break;
}
}
printf("renderer thread %d exiting\n", index);
}
/// Context for the blitting thread,
struct BlitContext {
bool working = true; ///< Flag used to signal when blit thread needs to exit
std::mutex blitLock; ///< Lock protecting #working flag
};
#include "Windows.h"
void blitThread(BlitContext &ctx) {
const HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);
const COORD topLeft = {0, 0};
while (true) {
/// reset cursor location to top left corner
SetConsoleCursorPosition(console, topLeft);
for (int r = 0; r < HEIGHT; r++) {
puts(canvas[r]);
}
std::lock_guard<std::mutex> lock(ctx.blitLock);
if (!ctx.working) {
break;
}
}
puts("blit thread exiting");
}
int main() {
AnimationContext animData;
RenderContext renderData = {animData};
BlitContext blitData;
std::thread blitWorker(blitThread, std::ref(blitData));
std::thread animationWorker(animationThread, std::ref(animData));
std::vector<std::thread> renderWorkers;
const int renderThreadCount = 4;
for (int c = 0; c < renderThreadCount; c++) {
renderWorkers.push_back(std::thread(renderThread, std::ref(renderData), c, renderThreadCount));
}
// run the for 10 minutes
std::this_thread::sleep_for(std::chrono::minutes(10));
// stop blit thread
{
std::lock_guard<std::mutex> lock(blitData.blitLock);
blitData.working = false;
}
blitWorker.join();
// stop animation thread
{
std::lock_guard<std::mutex> lock(animData.animLock);
animData.working = false;
}
animData.cvar.notify_all();
animationWorker.join();
// stop all renderer threads
{
std::lock_guard<std::mutex> lock(renderData.renderLock);
renderData.working = false;
}
for (int c = 0; c < renderThreadCount; c++) {
renderWorkers[c].join();
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment