After the so-called "avalanche", where the fractal has finished forming as there are no more candidates for toppling, we no longer need the threads to be working. As an intensive operation, it makes no sense to continue processing when we are sure there is no further dynamic behaviour possible. Using a tick based solution, every DELTA ticks we check if the avalanche is completed. If so, we clean up all the threads AND ensure that we no longer need to be checking for avalanches.
182 lines
5.2 KiB
C
182 lines
5.2 KiB
C
/* main.c
|
|
* Created: 2023-08-25
|
|
* Author: Aryadev Chavali
|
|
* Description: Entry point of program
|
|
*/
|
|
#include <malloc.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
|
|
#include <pthread.h>
|
|
#include <unistd.h>
|
|
|
|
#include <raylib.h>
|
|
#include <raymath.h>
|
|
|
|
#include "./lib.h"
|
|
|
|
struct StepArg
|
|
{
|
|
state_t *state;
|
|
size_t x_min, x_max, y_min, y_max;
|
|
};
|
|
|
|
pthread_mutex_t mutex;
|
|
|
|
void step(struct StepArg arg)
|
|
{
|
|
state_t *state = arg.state;
|
|
for (size_t i = arg.x_min; i < arg.x_max; ++i)
|
|
for (size_t j = arg.y_min; j < arg.y_max; ++j)
|
|
if (state->data[(i * state->dwidth) + j] >= 4)
|
|
{
|
|
pthread_mutex_lock(&mutex);
|
|
uint64_t *references[] = {
|
|
(j == 0) ? NULL : &state->data[((i)*state->dwidth) + j - 1],
|
|
(i == state->dwidth - 1)
|
|
? NULL
|
|
: &state->data[((i + 1) * state->dwidth) + j],
|
|
(j == state->dwidth - 1)
|
|
? NULL
|
|
: &state->data[(i * state->dwidth) + j + 1],
|
|
(i == 0) ? NULL : &state->data[((i - 1) * state->dwidth) + j]};
|
|
for (size_t k = 0; k < 4; ++k)
|
|
if (references[k])
|
|
*references[k] += state->data[(i * state->dwidth) + j] / 4;
|
|
state->data[(i * state->dwidth) + j] %= 4;
|
|
pthread_mutex_unlock(&mutex);
|
|
}
|
|
}
|
|
|
|
void *compute_thread(void *input)
|
|
{
|
|
struct StepArg *arg = input;
|
|
while (arg->state->thread_alive)
|
|
step(*arg);
|
|
return NULL;
|
|
}
|
|
|
|
bool completed_avalanche(state_t *state)
|
|
{
|
|
for (size_t i = 0; i < state->dwidth; ++i)
|
|
for (size_t j = 0; j < state->dwidth; ++j)
|
|
if (state->data[(i * state->dwidth) + j] >= 4)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
// Setup "default" state
|
|
state_t state = {NULL, 512, 512, 0, true, pow(2, 16)};
|
|
state.data = calloc(state.dwidth * state.dwidth, sizeof(*state.data));
|
|
state.multiplier = state.window_len / state.dwidth;
|
|
state.data[(state.dwidth * state.dwidth / 2) + (state.dwidth / 2)] =
|
|
state.payload;
|
|
|
|
const float zoom = 0.125f;
|
|
Camera2D camera = {0};
|
|
camera.zoom = 1.0f;
|
|
|
|
pthread_mutex_init(&mutex, NULL);
|
|
struct StepArg a = {&state, 0, state.dwidth / 2, 0, state.dwidth / 2};
|
|
struct StepArg b = {&state, 0, state.dwidth / 2, state.dwidth / 2,
|
|
state.dwidth};
|
|
struct StepArg c = {&state, state.dwidth / 2, state.dwidth, 0,
|
|
state.dwidth / 2};
|
|
struct StepArg d = {&state, state.dwidth / 2, state.dwidth, state.dwidth / 2,
|
|
state.dwidth};
|
|
pthread_t thread_a, thread_b, thread_c, thread_d;
|
|
pthread_create(&thread_a, NULL, &compute_thread, &a);
|
|
pthread_create(&thread_b, NULL, &compute_thread, &b);
|
|
pthread_create(&thread_c, NULL, &compute_thread, &c);
|
|
pthread_create(&thread_d, NULL, &compute_thread, &d);
|
|
|
|
InitWindow(state.window_len, state.window_len, "Abelian sand pile");
|
|
SetTargetFPS(60);
|
|
|
|
const int DELTA = 100;
|
|
bool done = false;
|
|
for (uint64_t ticks = 0, prev_ticks = 0; !WindowShouldClose(); ++ticks)
|
|
{
|
|
if (IsKeyPressed(KEY_UP) || IsKeyDown(KEY_UP))
|
|
{
|
|
Vector2 centre = {state.window_len / 2, state.window_len / 2};
|
|
Vector2 world_pos = GetScreenToWorld2D(centre, camera);
|
|
camera.offset = centre;
|
|
camera.target = world_pos;
|
|
camera.zoom += zoom;
|
|
if (camera.zoom < zoom)
|
|
camera.zoom = zoom;
|
|
}
|
|
if (IsKeyPressed(KEY_DOWN) || IsKeyDown(KEY_DOWN))
|
|
{
|
|
Vector2 centre = {state.window_len / 2, state.window_len / 2};
|
|
Vector2 world_pos = GetScreenToWorld2D(centre, camera);
|
|
camera.offset = centre;
|
|
camera.target = world_pos;
|
|
camera.zoom -= zoom;
|
|
if (camera.zoom < zoom)
|
|
camera.zoom = zoom;
|
|
}
|
|
|
|
if (ticks - prev_ticks > DELTA && !done)
|
|
{
|
|
printf("Checking if avalanche is complete!\n");
|
|
if (completed_avalanche(&state))
|
|
{
|
|
state.thread_alive = false;
|
|
pthread_join(thread_a, NULL);
|
|
pthread_join(thread_b, NULL);
|
|
pthread_join(thread_c, NULL);
|
|
pthread_join(thread_d, NULL);
|
|
done = true;
|
|
}
|
|
prev_ticks = ticks;
|
|
}
|
|
|
|
if (IsMouseButtonDown(MOUSE_BUTTON_LEFT))
|
|
{
|
|
Vector2 delta = Vector2Scale(GetMouseDelta(), -1.0f / camera.zoom);
|
|
camera.target = Vector2Add(camera.target, delta);
|
|
}
|
|
|
|
BeginDrawing();
|
|
ClearBackground(BLACK);
|
|
BeginMode2D(camera);
|
|
for (size_t i = 0; i < state.dwidth; ++i)
|
|
for (size_t j = 0; j < state.dwidth; ++j)
|
|
{
|
|
Color c = {0};
|
|
uint64_t sandpile = state.data[(i * state.dwidth) + j];
|
|
if (sandpile == 0)
|
|
c = BLACK;
|
|
else if (sandpile == 1)
|
|
c = MAGENTA;
|
|
else if (sandpile == 2)
|
|
c = RED;
|
|
else if (sandpile == 3)
|
|
c = BLUE;
|
|
|
|
DrawRectangle(i * state.multiplier, j * state.multiplier,
|
|
state.multiplier, state.multiplier, c);
|
|
}
|
|
EndMode2D();
|
|
EndDrawing();
|
|
}
|
|
|
|
if (state.thread_alive)
|
|
{
|
|
state.thread_alive = false;
|
|
pthread_join(thread_a, NULL);
|
|
pthread_join(thread_b, NULL);
|
|
pthread_join(thread_c, NULL);
|
|
pthread_join(thread_d, NULL);
|
|
}
|
|
|
|
CloseWindow();
|
|
write_to_png(&state, "data.png");
|
|
free(state.data);
|
|
return 0;
|
|
}
|