The Artist's Husband: TAHGA Lib

/2024/05/the-artists-husband-tahga-lib/images/top_image.png

Those of you who are paying close attention (all of you, I am sure…) may have noticed that things are getting just a bit unwieldy. Two recent posts, The Return of the Boids and Flying Triangles , both use the Boids algorithm. Likewise, two other posts, Watercolor Stroke and Revisiting the Watercolor Stroke , both rely on the Watercolor Stroke code. What’s more, the Watercolor Stroke is intended to be used in several future projects. This means all that code ends up being copied into a bunch of different programs. This is not very efficient! There must be a better way!!

Unsurprisingly, there is! Most programming languages have way to create a code library where code that is useful more than once can be stored and reused in various projects. Rust is no exception, so I’ve created TAHGA Lib, The Artist’s Husband Generative Art Library. This is where I’ll be putting useful code as I come up with it to make it easier to reuse that code in later projects.

I’ve added the Boids algorithm and the Watercolor Stroke code to the library. Now, for example, if I want to use the Watercolor Stoke, I can import the objects I want from the tahga::stroke::watercolor namespace:

use tahga::stroke::watercolor::{StrokeType, WatercolorStroke};

Then I’m free to use those object as though they were part of my program. For example:

let stroke = WatercolorStroke::new()
    .colors(vec!(
        hsla(0.1, 1., 0.2, 0.001),
        hsla(0.2, 1., 0.2, 0.001),
        hsla(0.3, 1., 0.2, 0.001),
        hsla(0.4, 1., 0.2, 0.001),
    ))
    .load(150.)
    .count(5)
    .stroke_type(StrokeType::Left),

I’ve updated the Watercolor Stroke and Boids examples in Gitlab to use the tahga library. You can see more details there of how to use it. The library itself is also in Gitlab .

This is all well and fine as far s it goes, but there are some things to consider before tossing stuff into a library. The routines should be well-thought-out, useful, generalized and easily configured. The Watercolor Stroke work well in the library, because I planned for it all along to be something external that would be used by future projects. It could use a few more features, which I’ll likely add eventually. The Boids algorithm, however, is not as well-thought-out, as when I was writing it I was not thinking it would be used over and over as much. So to get it into the library, I had to do a little rewriting. It works in there, but it could use some more improvements. For example, many values that should be configurable are hard-coded and cannot be changed. The update() method that causes boids to flock with other boids includes code to keep the boids on the screen, but how I keep then on the screen (and whether I keep them on the screen at all) is really a choice to be made by the user of the library, not something that should be forced on the user by the library. There are other, similar issues, so this code and how it is used will likely change over time.

Another thing to consider when creating a library is documentation. Libraries are created with the expectation that users will want to use them, and they will need to know how. Rust has excellent tools for documenting code! At the top of each file in the library, I’ve added this line:

#![warn(missing_docs)]

This will make the compiler complain when documentation is missing; you’ll see it as warning when you try to compile or run your code. There are specific things the compiler will want to see. First, something at the top of the file explaining what the file is for. For example:

//! # tahga::stroke::watercolor
//!
//! The watercolor module defines WatercolorStroke, which draws a color wash for a line

It will want to see a comment explaining each public structure, including a comment for each public field of the structure. For example:

/// WatercolorStroke defines a watercolor wash
#[derive(Debug)]
pub struct WatercolorStroke {
    /// Base color of stroke
    pub colors: Vec<Alpha<Hsl, f32>>,
    /// Brush load
    pub load: f32,
    /// Number of times to draw stoke
    pub count: u32,
    /// Stroke Type
    pub stroke_type: StrokeType,
}

And it will expect to see comment documenting every public method. For example:

    /// Set the brush load for a [`WatercolorStroke`] and return a new instance
    ///
    /// This is intended to be chained with new() in a builder pattern
    ///
    /// # Arguments
    ///
    /// `amount`: the new load
    ///
    /// # Examples
    ///
    /// ```
    /// use nannou::prelude::*;
    /// use tahga::stroke::watercolor::WatercolorStroke;
    /// let s = WatercolorStroke::new().load(50.);
    /// assert_eq!(s.load, 50.);
    /// ```
    ///
    pub fn load(mut self, amount: f32) -> Self {
        self.load = amount;

        self
    }

Of course, the Rust compiler has no idea whether the comments are correct; it just wants to make sure they exist. An interesting exception to that are examples you put in the comments, like the one above. The example are useful, because they show the user of the library a working example, which beats a wordy explanation any day of the week. But the compiler can also make sure the examples actually run. The code example above includes the necessary libraries and creates a new Watercolor Stroke with its load set to 50. Then it runs a test, asserting that the load is, in fact, equal to 50. This will pass, as things stand. But if it asserted that the load was equal to 500., or more importantly, if the load() method was broken and did not actually set the load that was passed in, the test would fail. If you run the command cargo test, all the tests in the library are run, and the results are reported. This means that, as you make changes, you can find out if your code is working by running cargo test periodically. Testing goes far beyond the examples in the documentation, but that will be a talk for another time. For now, just remember that tests are good, and if you have well-designed and complete tests (and actually remember to run them periodically), code development goes much faster and easier!

Note that all the documentation comments above start with three slashes (///). This marks them as documentation. If you were just writing a note in the code (“this part of the code calculates the velocity”), you’d use two slashes (//), the standard Rust comment delimiter.

Documentation comments use Markdown , a set of rules for marking up text, similar to HTML, but way easier to read. So, for example, when a line of documentation says # Arguments, it actually means just “Arguments” but as a heading, so it is bolder and in a larger font. This brings us to how we view the documentation. You could just read it by looking at the source code, as in the examples above. But it if you run the cargo doc command, Rust will generate a web page containing all your documentation which is much easier to read. Actually, it will generate a lot of web pages, because it will include the documentation for any libraries you used in your code! I have set up a server which will always show the latest version of the tahga documentation. You can see it at https://theartistshusband.netlify.app/tahga/ . You’ll have to click around to see all the pages (and there might be more to the library besides Boids and Watercolor Stroke before you read this) but as an example, there is the actual documentation generated for the load() method from the example above:

/2024/05/the-artists-husband-tahga-lib/images/load_method.png

In the future, I’ll be extending and improving this library to make it more useful to me and to anyone else that wants to use it.