From 38d531ca3127a6d5f8f247e7dfedbe6def917401 Mon Sep 17 00:00:00 2001 From: Aryadev Chavali Date: Fri, 12 Dec 2025 04:33:54 +0000 Subject: [PATCH] feat! Multithreading commit a763bff53283dfe43c31a8b53019ef1e875ae52b Author: Aryadev Chavali Date: Fri Dec 12 04:33:13 2025 +0000 Added release mode building Now the build script enables you to: - Build in debug mode (default no arguments) - Build in debug mode then run (`run` argument) - Build in release mode (`release` argument) commit 7112937b0b23a3504dd2331fabafd40192477b95 Author: Aryadev Chavali Date: Fri Dec 12 04:30:50 2025 +0000 Better thread pool constructor statically define a number of threads, then setup the necessary machinery to make it work. commit a5666328b703d12a6856470a7c1346860b654a2b Author: Aryadev Chavali Date: Fri Dec 12 04:29:32 2025 +0000 Fixed the heap-use-after-free issue. Just standard multithreading stuff; access to the allocator while hot threads are running means stuff can change underneath us even /during/ a read. I've mutex locked state for stuff in the drawing domain which stops this issue. commit 5d78cb20dfdee4ddbead11817c5316f1f067f984 Author: Aryadev Chavali Date: Fri Dec 12 04:28:21 2025 +0000 Adjust TODOs commit 9d3a202c27ebf817520cb73ec3a869b820c45656 Author: Aryadev Chavali Date: Fri Dec 12 04:09:50 2025 +0000 Implement stepping via KEY_SPACE commit 424fab2e40db52d8f24604e6dfd11ad880c930c5 Author: Aryadev Chavali Date: Fri Dec 12 04:08:01 2025 +0000 Weird bug in draw_tree Seems near the 100,000 node mark, the vector craps itself? I keep getting "heap use after free" errors here when trying to access the allocator vector. Disabling fsan just leads to a segment fault near the same point. I think we might need to introduce our own vector :) commit 6ffa63f7ac7324ca72080229c3135d00389eaa3b Author: Aryadev Chavali Date: Fri Dec 12 04:07:43 2025 +0000 Make general delay 1ms Just for my puny eyes to see. commit 2dbe3d0a585a7d186e7b5468799d856df620d2d3 Author: Aryadev Chavali Date: Fri Dec 12 03:54:30 2025 +0000 merge generate_children into do_iteration Also fix this really weird slow down we get from doing direct mutation to a state.allocator.get_ref (i.e. a Node by reference). Maybe the compiler just can't inline that instruction? Regardless, this patch fixes the slow downs! commit 7e801df2809f1d1605ccc5631e45345655d8803e Author: Aryadev Chavali Date: Fri Dec 12 03:53:51 2025 +0000 no more numerics commit f70517cf4174a94708180e8767233ac11bc60430 Author: Aryadev Chavali Date: Fri Dec 12 03:08:56 2025 +0000 Simplify mutex locking scheme (lock at start, unlock at end) commit 00858cf74f2d1a92d1a1e911d26350919d7f6103 Author: Aryadev Chavali Date: Fri Nov 28 17:24:33 2025 +0000 Rewrite main to draw based on our new state/draw_state Arranges a thread set, draws based on draw_state. No need to lock the mutex since drawing should only require reading. Still has a timer in case we need to do timed checks. commit 3e065e50a94678c1178f5090345ce4f3af32e58f Author: Aryadev Chavali Date: Fri Nov 28 17:24:12 2025 +0000 Disable previous work We're going to rewrite this commit ab0f152742b0e064994e56bab9790184a5c90cfe Author: Aryadev Chavali Date: Fri Nov 28 17:23:05 2025 +0000 Define `worker` function which a thread should run Pauses until state.pause_work is false (checking every THREAD_PAUSE_DELAY), then performs an iteration. Quits when state.quit_work is true (based on loop). Generally delays itself by THREAD_GENERAL_DELAY. commit 014821ceb5bf6eeb87e649703051948dbde80448 Author: Aryadev Chavali Date: Fri Nov 28 17:22:39 2025 +0000 Added booleans for thread workers to look at when running pause_work temporarily stops work, but should keep the thread alive. stop_work should kill the thread. commit 1bd8a9f7b2b258c2bbe65b01cda96855173d50a3 Author: Aryadev Chavali Date: Fri Nov 28 17:21:21 2025 +0000 Add run component to build script, activated by passing argument `run` commit 6f620644bf481b100ddf10289a31086c07d833b9 Author: Aryadev Chavali Date: Thu Nov 27 01:57:10 2025 +0000 Define constructors for state.hpp explicitly commit 0008c31f53628d048408efc6a86eee554e3a625d Author: Aryadev Chavali Date: Thu Nov 27 01:53:10 2025 +0000 Add src/worker.cpp to build pipeline commit f8068c4a158fb9b4a7dc687dd5f2327e3d05d3b4 Author: Aryadev Chavali Date: Thu Nov 27 01:52:52 2025 +0000 Implement the two worker functions commit 66c56f2c155bc381e0d81ec6c4ed7d497296eb45 Author: Aryadev Chavali Date: Thu Nov 27 01:51:41 2025 +0000 Define thread safe worker functions for generating new children on the tree commit 6247f2af492488f2f70ab151527d03ba825abf0b Author: Aryadev Chavali Date: Thu Nov 27 01:44:23 2025 +0000 Add src/state.cpp to the build pipeline commit 40e07e03f23c279dea1f13cacccff8eb79475fd1 Author: Aryadev Chavali Date: Thu Nov 27 01:42:24 2025 +0000 Add mutexes to state for the queue and the allocator Our threads need to negotiate access and mutation to the two resources - we don't want them treading on each others toes when generating new child nodes /or/ when trying to remove/add work to the queue. The former situation is particularly worrisome due to how weak the indexing system is with the allocator. commit 0abd353368f339a5ecfa0f1d0104388d1e2c90b0 Author: Aryadev Chavali Date: Thu Nov 27 01:30:35 2025 +0000 Implement cw::state::DrawState::compute_bounds commit 0ac316ada473dbe1b347c32950d09ddb61f44c4b Author: Aryadev Chavali Date: Thu Nov 27 01:30:21 2025 +0000 Define the general state of the sim (extract from main.cpp) commit a03fa13a0769b0ad5d7338fb0428d5f5070a17a3 Author: Aryadev Chavali Date: Thu Nov 27 01:19:52 2025 +0000 Implementation (copied from numerics) commit ff9a2851d46033c0f2595ae899c899536c8052a0 Author: Aryadev Chavali Date: Thu Nov 27 01:19:41 2025 +0000 Switch to using i64's instead of optional u64 in Node commit 6c2bc93874ff10b63a0e0b832dfa5340f0abad73 Author: Aryadev Chavali Date: Thu Nov 27 01:12:35 2025 +0000 remove index_t definition conflicts with prev code base. Also std::optional doubles the size of the underlying type. horrifying! I don't want to have to give 16 bytes over for something that could be embedded in the 8 bytes of the u64 directly. I'm thinking we could just use i64's instead, sacrificing that top bit to indicate if the value is present or not. commit 7a4d158d2faf6b1982f98c298fbf3ce141ac8af5 Author: Aryadev Chavali Date: Thu Nov 27 01:02:31 2025 +0000 Add gcd to base.hpp commit e032303773e19992d43cb5c25042d888abc4e06c Author: Aryadev Chavali Date: Thu Nov 27 00:56:45 2025 +0000 Define node.hpp (move definitions away from numerics.hpp) commit 9821a2ab15e27a0e0a9cc4de909251a7d4366374 Author: Aryadev Chavali Date: Thu Nov 27 00:55:44 2025 +0000 Define basic type aliases and useful functions/macros for use throughout the system --- README.org | 39 ++++++++---- build.sh | 17 +++++- src/base.hpp | 57 ++++++++++++++++++ src/main.cpp | 149 +++++++++++++++++++++++++++++++++++++++++++-- src/node.cpp | 133 ++++++++++++++++++++++++++++++++++++++++ src/node.hpp | 66 ++++++++++++++++++++ src/numerics.cpp | 153 ----------------------------------------------- src/numerics.hpp | 72 ---------------------- src/state.cpp | 41 +++++++++++++ src/state.hpp | 58 ++++++++++++++++++ src/worker.cpp | 81 +++++++++++++++++++++++++ src/worker.hpp | 49 +++++++++++++++ 12 files changed, 672 insertions(+), 243 deletions(-) create mode 100644 src/base.hpp create mode 100644 src/node.cpp create mode 100644 src/node.hpp delete mode 100644 src/numerics.cpp delete mode 100644 src/numerics.hpp create mode 100644 src/state.cpp create mode 100644 src/state.hpp create mode 100644 src/worker.cpp create mode 100644 src/worker.hpp diff --git a/README.org b/README.org index 3c7f0eb..268e5f8 100644 --- a/README.org +++ b/README.org @@ -20,8 +20,28 @@ the generator fraction is in green. This was done just for fun really, but it's quite fun to see it generate a dense number line over many iterations. * TODOs -** TODO Multithreading -SCHEDULED: <2025-11-18 Tue> +** TODO Tree visualisation +Instead of a number line, how about visualising the actual tree at +work as a graph of nodes? Maybe colouring nodes based on where it is +on the number line. +** TODO Don't walk the tree everytime we compute_bounds +[[file:src/state.cpp::void DrawState::compute_bounds()][location]] + +We already have the latest bound nodes so we're part-way through the +tree. Just keep going down what we have so far surely? Even better, +don't use nodes _at all_. Run with an index! +** DONE Fix weird issue at past 100K nodes +std::vector seems to crap itself past 100K nodes - we keep getting +heap-use-after-free issues when trying to access the allocator nodes +at that point. Seemingly random. What's going on? + +Solution: _everytime_ we want to access the allocator for nontrivial +(like memory reads) we need to lock the mutex. No two ways about it. +All draw functions were causing the issue. +** DONE Prettify code base +It's a big blob of code currently in the graphics portion. Not very +pretty but it gets the job done. Try modularisation. +** DONE Multithreading Currently single threaded. A multithreaded implementation could have multiple nodes generated at once, which would speed up the implementation. @@ -29,7 +49,7 @@ implementation. Might need to study my current implementation to see if it could be done better. -*** TODO Formalise state structure (separate drawing functions) +*** DONE Formalise state structure (separate drawing functions) Make a dedicated header and fit the necessary functions to our state structure: [[file:src/main.cpp::struct State][state structure]]. @@ -40,14 +60,14 @@ A good working name would be ~cw_state~. We could then have a separate structure for the drawing context (~cw_draw~) which can update itself by reading the ~cw_state~. -*** TODO Setup a queue and allocator mutex on state +*** DONE Setup a queue and allocator mutex on state We need one for the queue so we can make clean pushes and pops, and one for the allocator to ensure we're not messing up our indices in the nodes. We could just set these up in the state structure itself to make lookup easier. -*** TODO Make iterate use the state structure and mutexes +*** DONE Make iterate use the state structure and mutexes [[file:src/numerics.cpp::std::tuple iterate(std::queue &queue,]] @@ -64,11 +84,4 @@ iterate(std::queue &queue,]] I think this scheme minimises the any mutex is locked to just the bare minimum, ensuring other threads can get in on the action. -*** TODO Setup a thread pool utilising state and iterate -** TODO Prettify code base -It's a big blob of code currently in the graphics portion. Not very -pretty but it gets the job done. Try modularisation. -** TODO Tree visualisation -Instead of a number line, how about visualising the actual tree at -work as a graph of nodes? Maybe colouring nodes based on where it is -on the number line. +*** DONE Setup a thread pool utilising state and iterate diff --git a/build.sh b/build.sh index b591865..f9ed541 100644 --- a/build.sh +++ b/build.sh @@ -2,9 +2,24 @@ set -xe +OUT="cw_tree.out" GFLAGS="-Wall -Wextra -Wswitch-enum -std=c++17" DFLAGS="-ggdb -fsanitize=address -fsanitize=undefined" +RFLAGS="-O2" CFLAGS="$GFLAGS $DFLAGS" LIBS="-lraylib -lm" -c++ $CFLAGS -o cw_tree.out src/numerics.cpp src/main.cpp $LIBS +build() { + c++ $CFLAGS -o $OUT src/node.cpp src/state.cpp src/worker.cpp src/main.cpp $LIBS +} + +if [ "$1" = "run" ] +then + build && ./$OUT +elif [ "$1" = "release" ] +then + CFLAGS="$GFLAGS $RFLAGS" + build; +else + build; +fi diff --git a/src/base.hpp b/src/base.hpp new file mode 100644 index 0000000..4544661 --- /dev/null +++ b/src/base.hpp @@ -0,0 +1,57 @@ +/* base.hpp: Basic definitions + * Created: 2025-11-27 + * Author: Aryadev Chavali + * License: See end of file + * Commentary: + */ + +#ifndef BASE_HPP +#define BASE_HPP + +#include +#include +#include + +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define MAX(A, B) ((B) < (A) ? (A) : (B)) + +using u8 = uint8_t; +using u16 = uint16_t; +using u32 = uint32_t; +using u64 = uint64_t; + +using i8 = int8_t; +using i16 = int16_t; +using i32 = int32_t; +using i64 = int64_t; + +static_assert(sizeof(float) == 4, "f32 requires 4 byte floats"); +static_assert(sizeof(double) == 8, "f64 requires 8 byte doubles"); +using f32 = float; +using f64 = double; + +inline u64 gcd(u64 a, u64 b) +{ + if (a == b) + return a; + else if (a <= 1 || b <= 1) + return 1; + for (u64 r = b % a; r != 0; b = a, a = r, r = b % a) + continue; + return a; +} + +#endif + +/* Copyright (C) 2025 Aryadev Chavali + + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for + * details. + + * You may distribute and modify this code under the terms of the GNU General + * Public License Version 2, which you should have received a copy of along with + * this program. If not, please go to . + + */ diff --git a/src/main.cpp b/src/main.cpp index 5b24395..23f6e9c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,18 +5,21 @@ * Commentary: 2024-07-25 */ -#include "./numerics.hpp" - #include #include #include #include #include #include +#include #include #include +#include "base.hpp" +#include "node.hpp" +#include "worker.hpp" + #define WIDTH 1024 #define HEIGHT 1024 #define FONT_SIZE 20 @@ -24,14 +27,17 @@ #define LINE_TOP (7 * HEIGHT / 16) #define LINE_BOTTOM (9 * HEIGHT / 16) -std::pair fraction_to_string(Fraction f) +using cw::state::DrawState; +using cw::state::State; + +std::pair fraction_to_string(cw::node::Fraction f) { std::string s{to_string(f)}; int width = MeasureText(s.c_str(), FONT_SIZE); return std::make_pair(s, width); } -void draw_fraction(Fraction f, word_t x, word_t y) +void draw_fraction(cw::node::Fraction f, u64 x, u64 y) { std::string s; int width; @@ -40,6 +46,140 @@ void draw_fraction(Fraction f, word_t x, word_t y) DrawText(s.c_str(), x - width / 2, y - FONT_SIZE, FONT_SIZE, WHITE); } +constexpr u64 clamp_to_width(const DrawState &ds, f64 val) +{ + return (WIDTH / (ds.bounds.upper_val - ds.bounds.lower_val)) * + (val - ds.bounds.lower_val); +} + +void draw_tree(DrawState &ds, State &state) +{ + // Number line + DrawLine(0, HEIGHT / 2, WIDTH, HEIGHT / 2, WHITE); + + // Bounds + u64 lower_x = clamp_to_width(ds, ds.bounds.leftmost.value.norm); + u64 upper_x = clamp_to_width(ds, ds.bounds.rightmost.value.norm); + DrawLine(lower_x, LINE_TOP, lower_x, LINE_BOTTOM, WHITE); + DrawLine(upper_x, LINE_TOP, upper_x, LINE_BOTTOM, WHITE); + + state.mutex.lock(); + std::stack> stack; + cw::node::Node n = state.allocator.get_val(0); + stack.push(std::make_pair(0, n.value.norm)); + while (!stack.empty()) + { + u64 node_index; + f64 norm; + std::tie(node_index, norm) = stack.top(); + stack.pop(); + u64 x = clamp_to_width(ds, norm); + DrawLine(x, LINE_TOP, x, LINE_BOTTOM, RED); + + cw::node::Node n = state.allocator.get_val(node_index); + if (n.left >= 0) + { + cw::node::Node left = state.allocator.get_val(n.left); + stack.push(std::make_pair(n.left, left.value.norm)); + } + if (n.right >= 0) + { + cw::node::Node right = state.allocator.get_val(n.right); + stack.push(std::make_pair(n.right, right.value.norm)); + } + } + state.mutex.unlock(); +} + +using Clock = std::chrono::steady_clock; +using Ms = std::chrono::milliseconds; + +int main(void) +{ + // Init timer + auto time_current = Clock::now(); + auto time_previous = time_current; + constexpr auto time_delta = 0; + + // Init general state + cw::state::State state; + state.stop_work = false; + state.pause_work = false; + state.allocator.alloc(cw::node::Node{{1, 1}, -1, -1}); + state.queue.push(0); + + cw::state::DrawState draw_state{state}; + + // Init meta text (counter, iterations, etc) + u64 count = 1, prev_count = 0; + std::stringstream format_stream; + std::string format_str; + u64 format_str_width = 0; + +// Init threads +#define THREADS 15 + std::thread threads[THREADS]; + for (auto i = 0; i < THREADS; ++i) + { + threads[i] = std::move(std::thread(cw::worker::worker, std::ref(state))); + } + + // Setup raylib window + InitWindow(WIDTH, HEIGHT, "Calkin-Wilf tree"); + SetTargetFPS(60); + + while (!WindowShouldClose()) + { + // Update + time_current = Clock::now(); + if (!state.pause_work && + std::chrono::duration_cast(time_current - time_previous).count() >= + time_delta) + { + time_previous = time_current; + count = state.allocator.vec.size(); + } + + if (prev_count != count) + { + draw_state.compute_bounds(); + prev_count = count; + format_stream << "Count=" << count << "\n\n" + << "Iterations=" << (count - 1) / 2 << "\n\n" + << "Lower=" << to_string(draw_state.bounds.leftmost.value) + << "\n\n" + << "Upper=" << to_string(draw_state.bounds.rightmost.value); + format_str = format_stream.str(); + format_stream.str(""); + format_str_width = MeasureText(format_str.c_str(), FONT_SIZE * 2); + } + + if (IsKeyPressed(KEY_SPACE)) + { + state.pause_work = !state.pause_work; + } + + // Draw + + ClearBackground(BLACK); + BeginDrawing(); + draw_tree(draw_state, state); + DrawText(format_str.c_str(), WIDTH / 2 - format_str_width / 2, HEIGHT / 8, + FONT_SIZE, WHITE); + EndDrawing(); + } + + CloseWindow(); + + state.stop_work = true; + for (auto &thread : threads) + { + thread.join(); + } + return 0; +} + +#if 0 struct State { NodeAllocator allocator; @@ -209,6 +349,7 @@ int main(void) CloseWindow(); return 0; } +#endif /* Copyright (C) 2024, 2025 Aryadev Chavali diff --git a/src/node.cpp b/src/node.cpp new file mode 100644 index 0000000..36473b9 --- /dev/null +++ b/src/node.cpp @@ -0,0 +1,133 @@ +/* node.cpp: Implementations of various functions for fractions and nodes + * Created: 2025-11-27 + * Author: Aryadev Chavali + * License: See end of file + * Commentary: + */ + +#include + +#include "node.hpp" + +namespace cw::node +{ + /**************************************/ + /* ___ _ _ */ + /* | __| _ __ _ __| |_(_)___ _ _ ___ */ + /* | _| '_/ _` / _| _| / _ \ ' \(_-< */ + /* |_||_| \__,_\__|\__|_\___/_||_/__/ */ + /* */ + /**************************************/ + Fraction::Fraction(u64 numerator, u64 denominator) + : numerator{numerator}, denominator{denominator}, + norm{numerator / ((f64)denominator)} + { + u64 hcf = gcd(MIN(numerator, denominator), MAX(numerator, denominator)); + numerator /= hcf; + denominator /= hcf; + } + + bool Fraction::operator<(const Fraction other) + { + if (other.denominator == denominator) + return numerator < other.numerator; + return (numerator * other.denominator) < (other.numerator * denominator); + } + + bool Fraction::operator==(const Fraction &other) + { + return numerator == other.numerator && denominator == other.denominator; + } + + std::string to_string(const Fraction &f) + { + std::stringstream ss; + ss << f.numerator << "/" << f.denominator; + return ss.str(); + } + + /***************************/ + /* _ _ _ */ + /* | \| |___ __| |___ ___ */ + /* | .` / _ \/ _` / -_|_-< */ + /* |_|\_\___/\__,_\___/__/ */ + /* */ + /***************************/ + Node::Node(const Fraction &&val, i64 left, i64 right) + : value{val}, left{left}, right{right} + { + } + + NodeAllocator::NodeAllocator(u64 capacity) + { + vec.reserve(capacity); + } + + u64 NodeAllocator::alloc(Node n) + { + u64 ind = vec.size(); + vec.push_back(n); + return ind; + } + + // FIXME: This is annoying. DRY? + Node &NodeAllocator::get_ref(u64 n) + { + if (n >= vec.size()) + return vec[0]; + return vec[n]; + } + + Node NodeAllocator::get_val(u64 n) const + { + if (n >= vec.size()) + return vec[0]; + return vec[n]; + } + + void indent_depth(int depth, std::stringstream &ss) + { + for (int i = 0; i < depth; ++i) + ss << " "; + } + + std::string to_string(const NodeAllocator &allocator, const i64 n, int depth) + { + if (n < 0) + return "NIL"; + + std::stringstream ss; + Node x = allocator.get_val(n); + ss << "(" << to_string(x.value) << "\n"; + + indent_depth(depth, ss); + if (x.left == -1) + ss << "NIL"; + else + ss << to_string(allocator, x.left, depth + 1); + ss << "\n"; + + indent_depth(depth, ss); + if (x.right == -1) + ss << "NIL"; + else + ss << to_string(allocator, x.right, depth + 1); + + ss << ")"; + return ss.str(); + } + +} // namespace cw::node + +/* Copyright (C) 2025 Aryadev Chavali + + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for + * details. + + * You may distribute and modify this code under the terms of the GNU General + * Public License Version 2, which you should have received a copy of along with + * this program. If not, please go to . + + */ diff --git a/src/node.hpp b/src/node.hpp new file mode 100644 index 0000000..d221372 --- /dev/null +++ b/src/node.hpp @@ -0,0 +1,66 @@ +/* node.hpp: Definition of fractions and nodes on the Calkin-Wilf tree + * Created: 2025-11-27 + * Author: Aryadev Chavali + * License: See end of file + * Commentary: + */ + +#ifndef NODE_HPP +#define NODE_HPP + +#include +#include + +#include "base.hpp" + +namespace cw::node +{ + struct Fraction + { + u64 numerator, denominator; // always simplified + f64 norm; + + Fraction(u64 numerator = 0, u64 denominator = 1); + + // Complete ordering on Fractions + bool operator<(const Fraction other); + bool operator==(const Fraction &other); + }; + + std::string to_string(const Fraction &); + + struct Node + { + Fraction value; + i64 left, right; + + Node(const Fraction &&val = {}, i64 left = -1, i64 right = -1); + }; + + struct NodeAllocator + { + std::vector vec; + + NodeAllocator(u64 capacity = 256); + u64 alloc(Node n); + Node get_val(u64 n) const; + Node &get_ref(u64 n); + }; + + std::string to_string(const NodeAllocator &, const i64, int depth = 1); +} // namespace cw::node + +#endif + +/* Copyright (C) 2025 Aryadev Chavali + + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for + * details. + + * You may distribute and modify this code under the terms of the GNU General + * Public License Version 2, which you should have received a copy of along with + * this program. If not, please go to . + + */ diff --git a/src/numerics.cpp b/src/numerics.cpp deleted file mode 100644 index 2405f1f..0000000 --- a/src/numerics.cpp +++ /dev/null @@ -1,153 +0,0 @@ -/* numerics.cpp: Implementation of numerics - * Created: 2024-07-26 - * Author: Aryadev Chavali - * License: See end of file - * Commentary: - */ - -#include "./numerics.hpp" - -#include - -Fraction::Fraction(word_t numerator, word_t denominator) - : numerator{numerator}, denominator{denominator}, - norm{numerator / ((long double)denominator)} -{ - word_t hcf = gcd(MIN(numerator, denominator), MAX(numerator, denominator)); - numerator /= hcf; - denominator /= hcf; -} - -// floating point arithmetic inaccuracies blah blah blah better to use -// simplified fractions here - -bool Fraction::operator<(const Fraction other) -{ - if (other.denominator == denominator) - return numerator < other.numerator; - // TODO: Is it better to use the GCD? - return (numerator * other.denominator) < (other.numerator * denominator); -} - -bool Fraction::operator==(const Fraction &other) -{ - return numerator == other.numerator && denominator == other.denominator; -} - -Node::Node(Fraction val, index_t left, index_t right) - : value{val}, left{left}, right{right} -{ -} - -NodeAllocator::NodeAllocator(word_t capacity) -{ - vec.reserve(capacity); -} - -word_t NodeAllocator::alloc(Node n) -{ - word_t ind = vec.size(); - vec.push_back(n); - return ind; -} - -// WHY DO I NEED TO DO IT TWICE REEEEEEE -Node &NodeAllocator::getRef(word_t n) -{ - if (n >= vec.size()) - return vec[0]; - return vec[n]; -} - -Node NodeAllocator::getVal(word_t n) const -{ - if (n >= vec.size()) - return vec[0]; - return vec[n]; -} - -word_t gcd(word_t a, word_t b) -{ - if (a == b) - return a; - else if (a <= 1 || b <= 1) - return 1; - for (word_t r = b % a; r != 0; b = a, a = r, r = b % a) - continue; - return a; -} - -std::tuple iterate(std::queue &queue, - NodeAllocator &allocator) -{ - if (queue.empty()) - return {}; - word_t index = queue.front(); - Node node = allocator.getVal(index); - if (!node.left.has_value()) - { - allocator.getRef(index).left = allocator.alloc(Fraction{ - node.value.numerator, node.value.numerator + node.value.denominator}); - } - if (!node.right.has_value()) - { - allocator.getRef(index).right = allocator.alloc(Fraction{ - node.value.numerator + node.value.denominator, node.value.denominator}); - } - queue.pop(); - queue.push(allocator.getVal(index).left.value()); - queue.push(allocator.getVal(index).right.value()); - node = allocator.getVal(index); - // NOTE: We can be assured that left and right DO have values - return std::tuple(allocator.getVal(node.left.value()).value, node.value, - allocator.getVal(node.right.value()).value); -} - -std::string to_string(const Fraction &f) -{ - std::stringstream ss; - ss << f.numerator << "/" << f.denominator; - return ss.str(); -} - -void indent_depth(int depth, std::stringstream &ss) -{ - for (int i = 0; i < depth; ++i) - ss << " "; -} - -std::string to_string(const NodeAllocator &allocator, const index_t n, - int depth) -{ - if (!n.has_value()) - return "NIL"; - std::stringstream ss; - Node x = allocator.getVal(n.value()); - ss << "(" << to_string(x.value) << "\n"; - indent_depth(depth, ss); - if (x.left == -1) - ss << "NIL"; - else - ss << to_string(allocator, x.left, depth + 1); - ss << "\n"; - indent_depth(depth, ss); - if (x.right == -1) - ss << "NIL"; - else - ss << to_string(allocator, x.right, depth + 1); - ss << ")"; - return ss.str(); -} - -/* Copyright (C) 2024, 2025 Aryadev Chavali - - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for - * details. - - * You may distribute and modify this code under the terms of the GNU General - * Public License Version 2, which you should have received a copy of along with - * this program. If not, please go to . - - */ diff --git a/src/numerics.hpp b/src/numerics.hpp deleted file mode 100644 index 59d969e..0000000 --- a/src/numerics.hpp +++ /dev/null @@ -1,72 +0,0 @@ -/* numerics.hpp: Computation necessary for generating the tree - * Created: 2024-07-26 - * Author: Aryadev Chavali - * License: See end of file - * Commentary: - */ - -#ifndef NUMERICS_HPP -#define NUMERICS_HPP - -#include - -#include -#include -#include -#include -#include - -#define MIN(A, B) ((A) < (B) ? (A) : (B)) -#define MAX(A, B) ((B) < (A) ? (A) : (B)) -typedef uint64_t word_t; -typedef std::optional index_t; - -struct Fraction -{ - word_t numerator, denominator; - long double norm; - - Fraction(word_t numerator = 0, word_t denominator = 1); - bool operator<(const Fraction other); - bool operator==(const Fraction &other); -}; - -struct Node -{ - Fraction value; - index_t left, right; - - Node(Fraction val = {}, index_t left = std::nullopt, - index_t right = std::nullopt); -}; - -struct NodeAllocator -{ - std::vector vec; - - NodeAllocator(word_t capacity = 0); - word_t alloc(Node n); - Node getVal(word_t n) const; - Node &getRef(word_t n); -}; - -word_t gcd(word_t a, word_t b); -std::tuple iterate(std::queue &queue, - NodeAllocator &allocator); - -std::string to_string(const Fraction &); -std::string to_string(const NodeAllocator &, const index_t, int depth = 1); - -#endif - -/* Copyright (C) 2024, 2025 Aryadev Chavali - - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for - * details. - - * You may distribute and modify this code under the terms of the GNU General - * Public License Version 2, which you should have received a copy of along with - * this program. If not, please go to . - */ diff --git a/src/state.cpp b/src/state.cpp new file mode 100644 index 0000000..3cd6e0a --- /dev/null +++ b/src/state.cpp @@ -0,0 +1,41 @@ +/* state.cpp: + * Created: 2025-11-27 + * Author: Aryadev Chavali + * License: See end of file + * Commentary: + */ + +#include "state.hpp" +#include + +namespace cw::state +{ + void DrawState::compute_bounds() + { + state.mutex.lock(); + bounds.leftmost = state.allocator.get_val(0); + while (bounds.leftmost.left >= 0) + bounds.leftmost = state.allocator.get_val(bounds.leftmost.left); + + bounds.rightmost = state.allocator.get_val(0); + while (bounds.rightmost.right >= 0) + bounds.rightmost = state.allocator.get_val(bounds.rightmost.right); + state.mutex.unlock(); + + bounds.lower_val = std::floorl(bounds.leftmost.value.norm); + bounds.upper_val = std::ceill(bounds.rightmost.value.norm); + } +} // namespace cw::state + +/* Copyright (C) 2025 Aryadev Chavali + + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for + * details. + + * You may distribute and modify this code under the terms of the GNU General + * Public License Version 2, which you should have received a copy of along with + * this program. If not, please go to . + + */ diff --git a/src/state.hpp b/src/state.hpp new file mode 100644 index 0000000..5759830 --- /dev/null +++ b/src/state.hpp @@ -0,0 +1,58 @@ +/* state.hpp: General state of the simulation + * Created: 2025-11-27 + * Author: Aryadev Chavali + * License: See end of file + * Commentary: + */ + +#ifndef STATE_HPP +#define STATE_HPP + +#include +#include + +#include "base.hpp" +#include "node.hpp" + +namespace cw::state +{ + struct State + { + cw::node::NodeAllocator allocator; + std::queue queue; + + bool pause_work, stop_work; + std::mutex mutex; + + State(void) {}; + }; + + struct DrawState + { + State &state; + struct Bounds + { + cw::node::Node leftmost, rightmost; + f64 lower_val, upper_val; + } bounds; + + DrawState(State &state) : state{state} {}; + + void compute_bounds(void); + }; +} // namespace cw::state + +#endif + +/* Copyright (C) 2025 Aryadev Chavali + + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for + * details. + + * You may distribute and modify this code under the terms of the GNU General + * Public License Version 2, which you should have received a copy of along with + * this program. If not, please go to . + + */ diff --git a/src/worker.cpp b/src/worker.cpp new file mode 100644 index 0000000..5b3f4b6 --- /dev/null +++ b/src/worker.cpp @@ -0,0 +1,81 @@ +/* worker.cpp: Implementation of workers in our simulation + * Created: 2025-11-27 + * Author: Aryadev Chavali + * License: See end of file + * Commentary: + */ + +#include +#include +#include + +#include "worker.hpp" + +namespace cw::worker +{ + using cw::node::Fraction; + using cw::node::Node; + + void do_iteration(State &state) + { + state.mutex.lock(); + if (state.queue.empty()) + { + // Unlock since there isn't any work to be done. + state.mutex.unlock(); + return; + } + u64 index = state.queue.front(); + state.queue.pop(); + + Node node = state.allocator.get_val(index); + + i64 left = node.left, right = node.right; + if (left < 0) + { + left = state.allocator.alloc(Fraction{ + node.value.numerator, node.value.numerator + node.value.denominator}); + } + if (right < 0) + { + right = state.allocator.alloc( + Fraction{node.value.numerator + node.value.denominator, + node.value.denominator}); + } + + Node &node_ref = state.allocator.get_ref(index); + node_ref.left = left; + node_ref.right = right; + + state.queue.push(left); + state.queue.push(right); + state.mutex.unlock(); + } + + void worker(State &state) + { + while (!state.stop_work) + { + std::this_thread::sleep_for(THREAD_GENERAL_DELAY); + while (state.pause_work) + { + std::this_thread::sleep_for(THREAD_PAUSE_DELAY); + } + + do_iteration(state); + } + } +} // namespace cw::worker + +/* Copyright (C) 2025 Aryadev Chavali + + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for + * details. + + * You may distribute and modify this code under the terms of the GNU General + * Public License Version 2, which you should have received a copy of along with + * this program. If not, please go to . + + */ diff --git a/src/worker.hpp b/src/worker.hpp new file mode 100644 index 0000000..3f6d38a --- /dev/null +++ b/src/worker.hpp @@ -0,0 +1,49 @@ +/* worker.hpp: Definition of "workers" who will generate nodes on the tree + * Created: 2025-11-27 + * Author: Aryadev Chavali + * License: See end of file + * Commentary: + */ + +#ifndef WORKER_HPP +#define WORKER_HPP + +#include +#include + +#include "state.hpp" + +namespace cw::worker +{ + using cw::node::NodeAllocator; + using cw::state::State; + constexpr auto THREAD_PAUSE_DELAY = std::chrono::milliseconds(1000); + constexpr auto THREAD_GENERAL_DELAY = std::chrono::milliseconds(1); + + // Performs a single iteration which consists of the following: + // 1) pop an index off the iteration queue + // 2) generate the children of the node at that index + // 3) push the indices of the children onto the iteration queue + // Each step will block on the relevant mutex for the resource (1,3 will block + // on the queue mutex, 2 will block on the allocator mutex) so is thread safe. + void do_iteration(State &state); + + // Steady living thread worker which performs iterations. If state.pause_work + // is true, thread will pause until otherwise. + void worker(State &state); +} // namespace cw::worker + +#endif + +/* Copyright (C) 2025 Aryadev Chavali + + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for + * details. + + * You may distribute and modify this code under the terms of the GNU General + * Public License Version 2, which you should have received a copy of along with + * this program. If not, please go to . + + */