What Apple might do with distributed computing

I’m excited to see not only async-await making it thoroughly into the language (in Swift 5.6), but also the extensions that enable actors and distributed actors with this general sweep of the Swift language embracing concurrency. It’s been several years in the making, and the past year has been building much of these base pieces through the open-source side of Swift, which has been fantastic to watch. The next version of Swift (version 5.7 I presume) is likely coming next week, but even that doesn’t yet contain all of the outlined goals for concurrency within it. There’s clearly more to come – so where will it go from here? And how will it get used?

I have some background with distributed computing, back from the early days when OpenStack was starting to become a thing, several years before Kubernetes rose up to provide (in my opinion) even better abstractions, even if it stopped short in just the right way to bring with it a hell-scape of complexity. I’ve been programming using the Swift language recently, primarily to be delivered on Apple platforms, but I keep my fingers in the linux distribution, and a close eye on “server-side swift” in general. I see a huge amount of opportunity in distributed computing and the capabilities that it could provide.

With more hope than any real knowledge, I’d love to see a replacement API structure that’s swift focused and distributed across devices as a successor for XPC. Expanding the cross-process communication capabilities to cross-device, and the supporting infrastructure from Apple operating systems to identify and broadcast to each other, could enable some interesting scenarios. XPC has long been critical in terms of sandboxing processes, and generally enabling cross-process communications. It’s used all over the place in macOS and iOS, in the background where we don’t see it. Enable that across devices… and you’ve made the cluster of Apple devices that I have in the house more powerful because they could leverage each other and what they can individually do.

To me, the obvious starting point is Siri interactions. I have a HomePod mini in my kitchen, and usually more than one other device nearby. When I say “Hey Siri”, everything lights up and tries to respond, and one of them usually runs with the request – but sometimes several make the same attempt. I’ll ask Siri on my phone to activate a shortcut, but the HomePod grabs it – and because it doesn’t have the installed apps or intents, it says it can’t fulfill my request. Wouldn’t it be nice if Siri could know all the intents of all the active devices (where you have permissions, naturally) in the area where the request was made? That kind of thing is only possible when you have a robust and dynamic clustering solution that can let devices come and go, and share information about what’s nearby and what their capabilities are. In short: distributed computing.

Another place that would be useful is data collection and presentation with HomeKit. It feels dumb to me that each device and some iOS app has to manage the history of data for the in-house sensors I have installed. It’s making a silo of every sensor, and restricting my ability to even see, let alone use, the information together. The most effective solution to this today is cloud service integration, but the “advertising freemium” model that has dominance is primarily focused on extracting value from me, not providing it – so no thank you! Instead, I’d love to have a device (such as a HomePod with a bit of storage) stashed in the house that just collected the data from those sensors and made it available. A little, local persistent cache that all the HomeKit things could send to, and iOS apps could read from. A collection center akin to what Apple developed for health, and that doesn’t need to be stashed in a secure enclave. Imagine that as a HomeKit update!

Those two ideas are just based on what’s available today with a bit of evolution. My wildest dreams go full 2001: A Space Odyssey and imagine a rack where you can plug in (or remove) additional modules to add compute, storage, and memory capability. Make that available to other devices with something distributed, authenticated, and XPC like. It could expand the capability of small devices by offering progressively larger amounts of offloaded compute and storage available to devices that are getting lighter, smaller, and more invisible. In short, it’s a place where you could do the offload/tether that used to exist for iPhones to Macs, or watches to iPhones. If the current equivalent of those little white boxes in 2001 had the internals of the low-end current iPhones, it’s a truly amazing amount of compute that you could made available.

Yeah, I know that last one is getting pretty out there, but I love the idea of it anyway. I know it’s possible – maybe not practical – but definitely possible. I helped create something akin to that idea, based on the lessons from NASA’s Nebula Compute (but with a LOT less integration of hardware and software about a decade ago – we were trying to ride it all over commodity gear). That company failed, but the similar vision was replicated later by CoreOS, and a variation included within Canonical’s Ubuntu. I haven’t seen it really come to fruition, but maybe…

In any case, I’m looking forward to WWDC starting next week, the announcements, and mostly seeing the work a number of friends have been quietly working on for a while.

What being an Open Source Developer means to me

I’ve periodically described myself as an “open source developer” in those pithy biography blocks that you sometimes get (or have) to create. Developing and providing open source software means something specific to me, and I’d like to share how I think about it.

At it’s core, it’s only a little about solving problems with code – it’s as much about the community for me. When I provide open source software, it means I am both willing and want to contribute to knowledge that others can use, with few constraints. I’m not seeking payment, nor am I seeking to become a servant. I build and share open source software because I enjoy having, being apart of, and building a community that helps each other. The software solution itself is useful, but almost secondary. I enjoy, and search for, the benefits of having a community with diverse skills. People that I can go to with questions to get answers, or at least opinions on directions to search, and warnings about steep cliffs or ravines that may lie along the path – at least if they know of them.

I’ll happily host software on Github or Gitlab, take the time to make it into a package that others can use, and submit it to a relevant package index. I’m often do that as a side effect of solving a problem for myself. Sometimes I like to share how I tackled it for anyone else coming along with questions. I get a huge amount of value from reading other people’s work solving problems, so I try to share my thinking as well. The solutions may not be optimal, or even correct in all cases, but it’s shared – and that’s the main thing. Because it isn’t easy to find solutions just looking at source (or reading Github discussions), I also write about some of my searching for solutions in this blog. It helps to get at least some of the details in the global search indices. A blog isn’t a great place to share code, but it’s a decent place to talk about a problem or solution – and point to the code.

I’ve run into a number of folks who don’t share my opinion of what’s suitable to open source. I’ve heard them reference their reasons for not publishing as open source, as if they’re feeling guilty for not doing so. I’m not sure that’s what they’re feeling, but I kind of read that into some of what’s said. I’d really like to tell them there no guilt or shame in not contributing, or in keeping your learning and explorations to yourself.

I think some of that guilt thing has evolved from technical recruiters using (and sometimes expecting) a person’s public GitHub repository to be a reflection of their capabilities or a kind of portfolio or work. I’ve looked myself when I’ve hired people, if there’s something there – in the past, I thought it could show interests that can be useful to know about or just a starting point for a conversation. The downside of this practice is that people end up thinking that whatever they might “open source” has to speak with perfection about their cleverness, clarity, or robustness of a solution. I get it, that concern for what others might think based on what I’ve done. While I personally value the sharing, I respect the desire to not want to be put in that position.

There’s also plenty of people who legally can’t share. I think it sucks that corporate policies with these legal constrains exist, and from a corporate policy perspective I think it’s brutally short sighted. That said, I’m also someone who believes that ideas are nearly free – that it is execution that matters in building and running a company. Still it’s common, especially for technology companies, to legally constrain their employees from participating in open source without their explicit, prior approval – or in some cases to deny it all together, shitty as that can be.

I like to learn and I like to teach – or more specifically, I like to share what I’ve learned and learn from others in the process. (I’ve never learned so much as when I taught.) I get a lot of value out of being a part of a community – more so one that shares, sympathizes during the rough spots, and celebrates the successes. I’ve been repeatedly fortunate to find and be involved in great communities, and I’ve learned to seek them out – or just help foster and build them. I’ve also run from “so-called” communities that had individuals that delighted in mocking others, or attacked an opinion because it was different from theirs (I’m looking at you, orange hell-site).

While I love sharing, I’ve learned (the hard way) to set boundaries. Just because I like and want to share doesn’t mean I’m beholden to someone else’s opinion of what I should do, how I solve a problem, or that I have any obligation to them beyond what I choose to give. I’ve had people demand support from me, or from a team I was a part of collaboratively developing some open source software. I’ve even had a few experiences where I was targeted in personal, verbal attacks. It’s unfortunate that this comes with the territory of sharing with the world, but it’s also been (fortunately) rare. Most of the time the people being abusive, or selfish jerks, aren’t part of the community group that that I value. In my experience, they often stumbled onto code I helped create while looking for a solution to some problem they have, and thought because I shared, they were owed something. Sometimes they’re just people having a bad day and relieving their frustrations in the wrong place. There was a time that I’d offer to help those folks who’d been abbrasive – but only for money as a contract or consulting fees. These days I often shunt such ravings into a trash can, shut the lid, and don’t look back. It’s not worth the time, or the money, to deal with assholes.

The most valuable thing is the people you can connect with. These may be folks you’ve known for years, or they might be people you’ve never met or heard from. One of the best parts of “being an open source developer” is putting solutions out there that someone can pick up and use, and along the side infer “hey – there’s a place for you here. Drop in, have a seat, and chat with us.” They might stop in for a few minutes, a couple of days, or maybe longer. I expect everyone moves on at some point, and my hope is that they take away a spark of being a part of positive community. A seed of how it could work that they can grow.

Back at the beginning of this, I said open source meant something specific to me – sharing knowledge with “few constraints”. That’s not the same as no constraints. The formal mechanism in software around these constraints is licensing. I watched the legal battles from the 80’s and 90s, felt (and dealt with) their fallout, and avidly followed the evolution of a formal definition of open source and the blooming of various licenses that provided a legal backing. I get the licensing and I take it seriously. While I understand the foundational desires behind the free-software movement and its various licenses – such as GPL and LGPL – that’s not for me; I’m not a fan. I’m not going to, and don’t want to, take that hard a line and impose those additional legal constraints for the product that represents the knowledge I’m sharing. About the only constraint I want to put on something is attribution. The gist of which is “don’t be a jerk and take credit for my work”, but really it boils down “Recognize that you, like I, stand on the shoulders of our peers and those that came before us.”

Language and framing

Quite a few years ago, I was fortunate to sit in a presentation where Yukihiro Matsumoto talked about his inspirations when making the Ruby programming language. One of the references he cited was the 1966 science fiction novel from Samuel Delaney Babel-17. I was in Portland at the time, and was able to get a copy of the novel from Powell’s books, and spent an evening reading it. The book explores how language structures our thinking, which I thought was a super interesting insight when it comes to making your own programming language. I think about that insight frequently as I watch (and sometimes interact) with the evolution of the Swift programming language.

One of the topics I struggle with is how to frame a programming task to usefully solve it – be it with protocols and generic programming, or simpler techniques. It’s a shift in thinking for me, as I’m bringing a background built up using a lot of dynamic languages, and swift is NOT that. Sometimes, it feels like you start to think that way – what’s possible with some of the limited protocol types (also known as “existential types”), but as I’m learning (and have learned the hard way), it is definitely not the same. The strongly and strictly typed features of the language can make it a real challenge for me to navigate. In hindsight, I’ve found myself reaching for what are fundamentally features of dynamic languages to solve problems, only to run afoul. I’ve found it tricky to know when to use which features of the language to solve the problem at hand.

I’m currently working on taking another stab at trying to use the advanced swift language feature of result builders to build some declarative content that layers into SwiftUI. In terms of Swift, it builds in the deep end of the advanced features of Swift – generics, protocols with associated types, and (I think) the need for at least some type erasure. It’s proving to be a very back and forth learning process, and it feels like maybe the light-bulb is starting to come on about design techniques that leverage these pieces, but I’m still working through all the moving parts.

If you find yourself wanting to learn about result builders, I highly recommend watching, taking some time away, and then going back and re-watching Becca’s WWDC’21 presentation Write a DSL in Swift using result builders.

One kind of documentation that Apple used to do — that I dearly miss and is hard to write well — is a programming guide. I think it’s a huge loss in the broader Apple ecosystem. Blogs and some books get close to it. In years past, the Big Nerd Ranch guides were one of my go to books – and more recently that’s expanded to include the longer-form work from Paul Hudson, also an excellent teacher, and I build on the sweep of individuals that write about Swift, how to use features of it, or APIs within it. When I started learning to program on the Mac, the Cocoa Programming Guide (now retired, but still 100% relevant) introduced me to a lot of the core concepts that the Apple frameworks used – structured delegates, the MVC design pattern, target-action linkages, and the responder chain. Most importantly, these guides showed how to “work with the framework” and how to consider framing a programming task or problem so that the framework could more easily help you achieve you goals. There is still information in those guides that I’ve not found elsewhere – up to and including the reference documentation.

While I’d love to have such a guide for more in-depth examples and framing for how to build applications that use SwiftUI, there are other topics that might be even more valuable. Anything to do with Metal, and quite a bit about RealityKit, could use the help. The topics are just deeply complex, and the piece-meal, short-form content that is more common and popular these days makes it easy to learn a small piece, but (for me, at least) much harder to tie the whole process together. The best resource I’ve found for Metal is Metal Programming Guide by Janie Clayton – and no, that’s not written by Apple. The RealityKit framework makes heavy use of an architectural pattern that’s wholly different from those used in AppKit, UIKit, or SwiftUI – the Entity Component System (ECS). It is familiar to folks who have done game programming, but it’s a whole different way of looking at things for folks used to MVC and Cocoa, or the functional styles commonly seen in javascript programming these days.

The lack of programming guides for the expanding surface area of Apple platforms feels like a huge gap today – and not one I think that will soon close. Having written technical books myself, the longer works — such as these guides — are very hard to justify in terms of the time to write it. The lifespan of a book about technology, and how to use it, is brutally short. Maybe there’s something shorter form than a comprehensive bible style guide that would be useful. I’m still searching to figure out what that might be. Fortunately that’s something great about the broader swift community, there’s a lot to learn from others who are taking the time to share.

Chart

I’m starting, or more specifically re-starting, a project that I envisioned a couple years ago. In some of the apps I’ve created, I’ve found it useful – sometimes critical to what I want – to provide small charts (visualizations) of data from the app. One example of this is a series of histograms that show the network latencies to each step in a route from my machine to a location on the internet. There’s some great CLI tools that display the raw data in a terminal window (mtr, if you’re curious), but I think it’s easier to understand viewing the data as a series histograms. In the case of mtr, you can see where network traffic slows down or doesn’t get past.

work in progress – histogram sequence from traceroute data

Looking at the example above, you can see a fairly consistent latency through to the end point, with the majority of the variability at that first step (in this example, it is my wifi router hub). The down side is that the code to display just that – not even getting to labels, axis, etc – was pretty heavy.

A year ago, I tried coming up with drawing this kind of graph in SwiftUI, including displaying axis and labels. Turns out that with SwiftUI’s constraints, and the “every expanding” side effect of using GeometryReader in a SwiftUI layout, it was darned tricky to get it working. I got something working, on which I could overlay SwiftUI shape objects with explicit offsets. The downside was that it wasn’t flexible and small changes could break the layout dramatically. Last year, Apple updated SwiftUI with Canvas, which I used when generating the series of histograms above. That really opened the doors for me. Now I’d like to do the same using a SwiftUI like syntax. It seems like it would be a natural fit, and it would make creating those little sequences of visualizations a hell of a lot easier.

I know there’s other charting libraries out there – and good ones. The “ole standby” that many people use is Daniel Gindi’s Charts, but even a quick search on Swift Package Index shows other folks offering their own takes on some of these libraries, several of which are interesting for the number of stars they’ve collected. (Majid’s SwiftUICharts in particular looks interesting to me, fits in well with SwiftUI, and appears to be stable).

I’ve cobbled previous charts and plots with anything from Excel to using the D3.js library in javascript (which is a really amazing library). That background had me following a trend towards describing charts more declaratively, which fits in well with how SwiftUI works. I dug into Observable’s Plot and the Vega-lite project, and the research papers behind them. Both of have a very declarative style of describing a chart, the style of which fits in wonderfully well with the kind of declarative syntax that SwiftUI uses. Seeing what I can do to enable that kind of functionality, but using Swift, is what I’m after, and what I’d really like to be able to use.

Part of my goal in setting this up is to learn the “ins and outs” of what it takes to enable this kind of thing using result builders. I don’t feel like have a solid understanding right now, and the way I learn best is by doing – so I’m aiming to learn and do in public. I’d like to work out how to tell someone else how they could solve this kind of problem, and right now I’m far from it. I’m guessing the solution likely includes not only result builders, but careful use and design with generics and protocols as well. I’m planning on writing about my development process (and my learning!) as I tackle this. Rather than waiting for when it’s all “good and done” (assuming I get there), I have set this up to develop completely in the open. I do expect it’ll get messy, and it may never come to anything, but I figured it was worth a shot.

If you think tackling something like this might be fun, or just want to poke your head in to watch how I tackle it, you’re welcome to join in. I would love to have others to bounce ideas off of, or help with the coding if you’re so inclined. If you’re interested in following along, I’m starting with some of Github’s community tools on a repository. I set up an organization called SwiftViz, and made the repository Chart to house this project. I’ve enabled Chart Discussions – no idea how well or crappy that will work out – as a starting point. I do expect to evolve as it goes. I thought about just writing about the efforts on this blog, but a blog doesn’t really end up being very interactive – can’t have much of a discussion on it – and I’d like to at least entertain the possibility that someone else might want to collaborate with me on this.

It’s early days, and it will undoubtably get messy, but if you’re interested, drop into the repository discussions and say hello, or reach out to me on twitter if that’s easier.

RealityKit on macOS

Guessing which frameworks are going to be updated, and which aren’t, is — I think — a part of developing software on Apple platforms. Sometimes it’s clear based on what’s been updated over the past three or fours, as is the case with RealityKit. I started my experiments with SceneKit, another lovely high-level API to 3D graphics, but wanted to utilize more of RealityKit for some of my procedural graphics work (Lindenmayer trees).

RealityKit has a huge API surface, only some of it directly related to 3D rendering. Other parts include physics simulations, including rigid body collisions, an ECS, and — as of WWDC’21 — methods for procedural geometry/mesh creation. RealityKit is fundamentally an API that’s meant to mix of 3D rendered content into live images from the real world. Because of that, the camera for rendering 3D content is tightly controlled to match the physical position and location of the actual camera on a device. So it isn’t surprising that you might think, “Hmmm.. how’s that going to work on macOS – which only has a forward-facing camera, and few to none of the fancy motion and depth sensors an iOS device has.”

The good news is that RealityKit, with macOS 10.15, added the capability to run on macOS. It includes an ARView for macOS (even though there’s no ARKit) inside RealityKit. It’s more constrained than the UIKit version, but it’s there. After a comment from James Thomson on a podcast last year talking about getting that view working on macOS, I wanted to do the same. It’s relatively easy to get an ARView showing up – but figuring out what do to with it from there is not as obvious. It turns out that the macOS-only ARView doesn’t respond to mouse, touch, or trackpad input – so user interactions don’t influence the camera position. You can’t appear to move around or look at different things without code to explicitly move the camera. If you use the UIKit ARView on macOS built with mac Catalyst, it’s a different story. This bit is about mixing AppKit and RealityKit together without using MacCatalyst and the UIKit Apis.

Once I’d convinced myself this wasn’t entirely a fools errand, I wrangled up the code to enable user interaction to move the camera. I made my own subclass of ARView and added in keyboard, trackpad, and mouse support to move the camera. It isn’t identical to how SceneKit works, but it’s sufficient to display content and look around the environment. I wanted something where I could load up and view content using RealityKit, but running on macOS. In particular, I wanted to load up a USD file and get sufficient screen captures to render the set of images into an animated gif. As a side note, there’s not an easy and consistently clean way to describe a 3D object in documentation – but a lot of the HTML based mechanisms (include DocC, which I’ve been writing about lately) have no trouble displaying an animated gif.

The end result took a bit, but I got there (the fish in question is from WWDC’21 sample code):

While trying to get this working, I learned that the ARView implementation for macOS that comes with RealityKit appears to swallow up mouse events, which meant there wasn’t an easy way to control it from SwiftUI views using gesture recognizers. I switched to the older mouse and keyboard interaction apis that AppKit supports, making a subclass to add in the various overrides to provide mouse and keyboard event handling. After I got it all working, I extracted the SwiftUI wrapper. Finally, I put the subclass into a it’s own swift package, available for anyone to use:

CameraControlARView (API Docs).

If you’re interested in using RealityKit on macOS, you’re welcome to use it – or to pull it apart to see how it works and use that to do something you prefer. My goal in making this tiny open-source package is to help the next soul who’s stumbling through figuring out how to get started with RealityKit and wanting to experiment a bit on macOS to see how things work.

Design decisions and technical choices

I made a (perhaps) odd choice when I assembled the SwiftUI container for it – I set it up so that you create the instance of my subclass either in a SwiftUI view, or hold it outside that and pass it through. The point was to have a reference to the ARView (and its properties) available from the declarative space of SwiftUI so that you could interact with the 3D scene and entities within it from the SwiftUI control declarations. I didn’t see another straight-forward path to creating the scene (such as a SceneKit Scene) externally and passing it down and in, and in order to do on-the-fly file loading (such as when you drop a USDZ file into the view) I needed to have access to that scene.

The other design choice I made was to make the camera motion support two different modes of motion. The first mode is what’s called “arc-ball” – it orbits and looking at a specific point in 3D space from a variety of rotations. You can imagine it as following arcs around a sphere, centered on a specific point. I also wanted to have a mode where I could move freely move about, which I called “first person” mode. In first person mode, you can move the camera forward, back, side to side, as well as turn to look elsewhere. There are a couple other popular 3D movement modes out there (turn-table being one I considered, but decided to not attempt), but I stuck to these two modes.

The rest is all pretty straight-forward. I’d fortunately become more comfortable with the gems in the Accelerate/SIMD framework, which provide lovely quick tools for creating quaternions and matrices. I still ended up embedding a few “build this rotation matrix from this angle around that axis” kinds of methods – stuff that’s under the covers in SceneKit, but I’m not aware of a “central” implementation in the platform tooling otherwise. It’s not “hard” per se, just tricky to make sure you get correct.

Generating Animated GIF from 3D models

I went ahead and put the macOS App project up publicly on Github as well: Film3D. It’s far from a complete app, it doesn’t even have a placeholder icon (as I’m writing this) and the user interface is a ludicrous “just get it working” sort of thing, but it works to load a USDZ file, set the camera, and capture images into an animated gif.

There’s more I’d like to do with it, but I’m not planning on selling it – I just wanted something that does what it does so that I can use it to create images for documentation.

DocC plugin PSA

In my last two posts about using DocC, I’ve been implicitly encouraging the use of the new docc-plugin for the Swift package manager. I did so knowing that it is in beta and the underlying APIs are evolving – and in my example scripts I include how to do the same thing with commands that directly invoke the swift compiler and DocC to generate documentation.

With the release of Xcode 13.3 beta 3 today, the API for package manager command plugins (of which the swift-docc-plugin is a lovely example) has changed slightly – and as such, what’s currently on the main branch isn’t compatible. There’s a pending pull request which I believe will resolve and update things back to working, but if you’re using the plugin, you should be aware that there’s an issue.

UPDATE

The pull request was merged on March 1st, which resolves using the plugin with Xcode 13.3 beta 3, and Ethan posted a bit of nice detail on the Swift Forums showing what’s changed when you use the plugin after the Swift Package Manager API updates. The big piece is that the --target option moved from in front of generate-documentation to after it, and there’s additional options for specifying a specific product, useful if you have a swift package that generates multiple products.

In the meantime, using docc on the command line directly does the job well, and hopefully soon we will have a fix added to the plugin for the updated API. I also hope that a release will be available before too long, but that will only happen when all the moving parts have stabilized. It is a lovely tool, and we’re in a beta period – so I’m not surprised that there are a few bumps along the road. I’m sure the end result will be better for the iteration.

In case you need the earlier steps laid out, here’s a script for generating documentation into a directory for static hosting:

#!/bin/bash

set -e  # exit on a non-zero return code from a command
set -x  # print a trace of commands as they execute

rm -rf .build
mkdir -p .build/symbol-graphs

$(xcrun --find swift) build --target RSTree \
    -Xswiftc -emit-symbol-graph \
    -Xswiftc -emit-symbol-graph-dir -Xswiftc .build/symbol-graphs

# Enables deterministic output
# - useful when you're committing the results to host on github pages
export DOCC_JSON_PRETTYPRINT=YES

$(xcrun --find docc) convert Sources/RSTree/Documentation.docc \
    --output-path ./docs \
    --fallback-display-name RSTree \
    --fallback-bundle-identifier com.github.heckj.RSTree \
    --fallback-bundle-version 0.1.0 \
    --additional-symbol-graph-dir .build/symbol-graphs \
    --emit-digest \
    --transform-for-static-hosting \
    --hosting-base-path 'RSTree'

Tips for getting the most out of DocC

1 – Start by adding doc comments to your types.

The starting point is adding a single short summary sentence as comment (using the ///) for each public type in your library or app. Feel free to add more: if you add a “blank line” (meaning include another line with /// but nothing else in it) followed by additional content, that content appears as the “Discussion” or “Overview” for the type. This is a great place to drop in a code snippet showing how to use that type alongside the discussion of what it is and how to use it.

The reason I picked this as the starting point is that everything you add immediately becomes available through Xcode’s quick-help. You get maximum immediate benefit, as it provides content to the panel that opens when you option-click on a type.

The initial single line doc comment is referred to as the abstract. When a symbol (whether it’s a class, struct, protocol, property, or method) doesn’t have an abstract, DocC generates one for you. And that is where the much-commented-on No Overview Available comes from.

2 – Add doc comments to your public methods and properties.

The important bit here isn’t a default initializers or functions that enable conformance to common standard library protocols. This is about the the elements on your type (struct, class, enum, or whatever) that make it unique and special. Aim to get to the core of what the symbol does – and most importantly, why you’d use it and what you need to use it. Sometimes its simple – and a single line abstract describing what it represents is more than sufficient. For those times, a single line summary (the first line with ///) does wonders. If it’s more complex, add that after a break (a line with /// and nothing else as a doc comment), then talk to the details on how to use it, or what to expect.

If you’re writing the doc comments using Xcode, it provides a handy “generate a doc comment” command (command-option-/ being the default key mapping to that Xcode gem) that creates a stub. If you do this for a function or method, it includes the parameters formatted correctly for DocC and placeholders for you to fill in.

If you stop right after doing this, you have the basic, effective documentation that is available to anyone loading your framework or package. But there is still a lot you can do to make it easier to use your library.

3 – Add a documentation catalog and a library overview.

Adding a documentation catalog is the 3rd thing on this list – that’s not a mistake. Everything above provides immediate benefit while you (and any collaborators) work on, or with, the source code. I think of the Documentation Catalog as being akin to the idea that you should include a README file with a project. It provides an introduction and framing to your library, and is the heart of where to learn more.

The documentation catalog is a directory with a collection of goodies. It houses the top-level overview of your library, and any assets (images, articles, and more — should you go that far). When you add a catalog and then use Product > Build Documentation within Xcode, those docs appear in the Xcode documentation window. You can export an archive for others to use from that window. There is also terminal commands to generate documentation that’s suitable for static hosting. I created an earlier post (Hosting Your Swift Library Docs on Github Pages) that goes into detail on the steps of doing just that.

When you create a documentation catalog using Xcode, its template includes a markdown file with a loose structure for what to add. It is intentionally, and specifically, structured for DocC. I recommend writing the content under the Overview heading first, and then going back and writing the summary later. The bottom section of the template (this text that starts with ## Topics and has a placeholder 3rd level heading with a generic symbol underneath) is the placeholder for you to organize the symbols of your project – the next tip.

4 – Add the top-level curation for your library.

In the world of DocC, curation is a term that means to create organization for a set of symbols. When I suggest adding top-level curation, I’m referring to adding organization to that top-page in your library, that includes the overview. This is the text beneath the overview that starts with ## Topics, followed by one or more 3rd level headers (###) with various grouping names.

DocC started with collating only public types, methods, protocols, and so on for libraries and frameworks. As it’s evolving, the project is starting to add in the capability for documenting apps as well. In the latest Xcode 13.3 beta, when you add a catalog and build docs for an app, it includes internal types as well as public.

How you organize the symbols is subtle, but important. We have a tendency to scan down from the top – so organize the most important, or frequently used, elements near the top. Arrange symbols that have less importance beneath. DocC always builds a structure for you, even if you don’t provide one. When you provide some structure, it tries to take that into account first, and fills in any missing gaps. Docc places the symbols you don’t include in your structure beneath what you provide using a generic organization, grouped based on the type of symbol.

That generic structure isn’t too bad. It’s certainly understandable for many developers, so if you don’t get to this point, you still have useful docs due to the abstracts and overviews — that’s a win!

5 – Add a walk-through article.

Add an article that provides a walk-through of the most common, basic usage of library. The Documentation article template from Xcode looks suspiciously like the top-level organization page, but the intention of an article is quite different. It still has a structural pattern of a one-line abstract at the top (the placeholder that reads summary in the template). Beneath that a ## Overview heading, and then a placeholder for a summary of the article. When I write an article, I leave that all alone and jump down into the content.

A single ### heading after the overview section with content underneath it is where to start. Group what you’re showing, frequently I’ve seen this listed by task or step, by the 3rd level headings, and include the detail underneath. Intermix text and code snippets – and images if you can make the time and it makes sense. The point of what you’re making is to provide a quick overview of how to use the library, or at least one way of getting started with it. You may find there’s a lot you could say – too much for one article. That’s fine — in fact, that’s wonderful — just make more articles and add them in.

As you add an article, curate it into your documentation. By which I mean go back to that top-level document and add a link to your article. The article links look like <doc:Your_Article's_File_Name>. When you build the documentation, the rendered content that’s dropped in place there is the top-level heading in your article, and it makes a link over to your content. An article is only useful when you get easily get to it.

I debated if this should be above the curation suggestion or not. It’s more work, certainly, but there is a ton of value. If you can’t, or don’t want, to spend a lot of time on this – focus on getting out something that shows someone wanting to use your library how to do it. There’s so much more that can be said about articles and organization, and opinions on what’s good and bad – but focus first on showing the key elements of your library, and you can build from there.

6 – Add strategic code snippets.

Hopefully your article had useful, direct code snippets in it. The next step to making that kind of directly usable content easier to find is to add code snippets into the overviews for some of your symbols. Take the time to pick the most important, or perhaps the most common, ways your library is used. I like to scribble that down on the side, and build my own little stack-rank of what I think would be most interesting to someone wanting to use my library. Then go through and add code snippets showing how to use that type, method, or function.

A pattern that I’m using at the moment is to create a unit test function that doesn’t actually test anything other than the code I just typed compiles correctly. When I’m making code snippets — both for articles and overviews in the reference section — I add a test case and put the code snippet in there. It keeps me honest with parameters and explicit formatting, and most importantly I’ve found – when the library evolves and something changes, you’ve got an immediate notice in the tests that something in the documentation needs an update there as well. I often put a comment in those “unit tests” that indicate where I used the snippet – for those times when a test fails and then I need to sort of what needs a tweak or update.

7 – Add curation to the rest of your types.

That same organization you provided for the top level of your library? Yeah – this is doing it for pieces below that top level. If you have a large class or struct, or types that include other types, this is hitting at the heart of making the list of symbols within them readily understandable.

While you can recursively do this through all your types, and a person with completion-is tendencies (which very much describes me) might want to, focus on the top level items and biggest collections. It is most useful to get all of your library’s types that include other symbols. When I’m doing this – I work down the types in the same order I added them into top-level structure. That’s assuming you ordered it most-important-first, so as long as you got that pretty close, you’re getting the most valuable pieces done first.

For types with a small number of sub-symbols (properties, enumeration cases, etc), I’ll include the organization of that symbol directly in the source code. But for a symbol with more than 4 or 5 symbols beneath it, I find it easier to manage by making an extension page in the Documentation catalog and including the curation/organization content there.

The process of adding an extension page is straightforward: in your documentation catalog (the directory ending in .docc) add a directory named Extensions, and then add markdown files into it. Xcode includes an extension template file type you can use. I name each of the files for the symbol that it provides organization for. Inside the extension file, include the curation. The content will look something like the following (example borrowed from in-the-works documentation updates for Nick Lockwood’s Euclid library):

# ``Euclid/Angle``

## Topics

### Creating Angles

- ``Angle/init(degrees:)``
- ``Angle/init(radians:)``
- ``Angle/degrees(_:)``
- ``Angle/radians(_:)``

### Inspecting Angles

- ``Angle/degrees``
- ``Angle/radians``

### Common Angles

- ``Angle/zero``
- ``Angle/halfPi``
- ``Angle/pi``

The reason I like using an extension file is that it doesn’t consume a lot of vertical space in the source code. I think it’s useful to have the abstract and overview as doc comments in the source, next to what they describe. I also think it’s less valuable for the organization to be there, and there is a cost to the readability of the source code. The organization isn’t the kind of thing you need to update when you’re tweaking a method’s signature or internals and how it works.

What are the symbols for my project?

When I’m tackling this curation process in adding documentation, I find it super-helpful to know the available symbols and their links. A symbol link is one of those items that represent a symbol that in text start and end with double back-ticks. For example, “init(_:)“ is a symbol link that references to an initializer. When you have two functions that have the same signature, but different types, then DocC adds a little hash value on to the end to disambiguate them.

Unfortunately, getting a complete list is kind of pain right now. Xcode auto-completion does a pretty good job of auto-completing symbols, so you can explore with auto-completion, but I recently ran into some issues where it missed a few.

One way you can find the values is by running xcrun docc preview on the command line, and navigating to through the rendered docs to the symbol you’re trying to find. The URL of that page has the symbol details at the end of the URL. If the symbol has a disambiguating hash code, it’s also is reflected in the URL. But that’s a lot of clicking around to get to a single value.

Another way to get the values is generating the documentation with the --emit-digest option, and extracting what you want from there. I learned this from the DocC team members on the Swift Forums. The emit digest option adds a file to the generated content named docs/linkable-entities.json. It’s a JSON file that includes a documentation reference for every symbol published in your docs as a documentation link. And a substring of the documentation link is what DocC uses as symbol links.

I knocked together a shell script snippet that uses the command line tools jq and other shell commands to generate a sorted list of all the possible symbols:

cat docs/linkable-entities.json \
  | jq '.[].referenceURL' -r > all_identifiers.txt

sort all_identifiers.txt \
  | sed -e 's/doc:\/\/SceneKitDebugTools\/documentation\///g' \
  | sed -e 's/^/- ``/g' \
  | sed -e 's/$/``/g' > all_symbols.txt

The sed commands are regular-expression replacing the lines that convert the referenceURLs into symbols. It strips off the doc://YourModuleName/documentation (the example above being pulled from a tiny library I have) from the list of URLs and adds into the double back-ticks.

I’ve started using the DocC plugin for generating documentation suitable for hosting on Github pages, but you don’t need to use that for getting this file generated. Running docc convert with the --emit-digest will get the same file generated for you. It’s placed in the output directory you specify for the conversion. If you’re lost on how to do all that, take look at one of the scripts that I’m using: docbuild.bash. I started doing this before the DocC plugin tool became available, so it includes all the steps from building the source, to converting the symbol graphs using DocC.

Hosting your Swift Library Docs on Github Pages

The beta for Xcode 13.3 dropped yesterday. With it came a released version of Swift 5.6 and a bunch of neat additions that the 5.6 release enables. A feature I was watching closely was two-fold: the capability for plugins to extend the commands available within swift’s package manager, and a static hosting option that was added to the documentation compiler tool (DocC) that Apple announced and open sourced this past year.

The two elements came together with the initial release of swift-docc-plugin, a plugin to swift package manager that adds the ability to generate documentation on the command line with the command swift package generate-documentation. Before I go any farther, I should note that when the DocC team put this together, they knew a lot of folks wanted to host their content as if it were static HTML – so they took the time to document HOW to do that, and best of all – they hosted _that_ documentation on github pages (I’m assuming using their own tools to do it). The articles, all of which are sourced from the content in the git repository, are hosted on GitHub pages:

This works, and quite nicely – I pushed up documentation for the Lindenmayer library I’ve been working on using the following command:

swift package \
    --allow-writing-to-directory ./docs \
    --target Lindenmayer \
    generate-documentation \
    --output-path ./docs \
    --emit-digest \
    --disable-indexing \
    --transform-for-static-hosting \
    --hosting-base-path 'Lindenmayer'

BUT… there are some quirks you should be aware of.

First, the ability to statically host the content does NOT mean that it is static content. DocC is a different take on managing documentation from almost all of the similar, prior tools. Where Doxygen, JavaDoc, Sphinx, Hugo, AsciiDoc and others read through the source and generate HTML, that is not what DocC is doing. While the content dumped out for static hosting ultimately includes HTML, DocC relies on two other critical components to do its work. It starts with the compiler generating what’s called a Symbol Graph. That’s a file that contains all the symbols – types, properties, type aliases, etc – in your source code, and the relationships between them. DocC then tweaks and adjusts that graph of symbols and more specifically mixes it with additional (authored, not automatic) markdown files from a directory – which is called the documentation catalog. If the markdown files in the documentation catalog, or the original source, don’t provide content or structure for the relationships in the symbol graph, then DocC builds up a default set, and attempts to provide a default structure. For what it’s worth, this default content set is where the dreaded “No Overview Available” comes from. The resulting combination is serialized into a bunch of JSON files, stored in the filesystem. Those JSON files contain the information needed to render the content – but the core of that content rendering happens from another project, A Vue-based JavaScript single page app: swift-docc-render.

The JSON files allows this documentation content to be more easily consumed by more than just browsers. I think it’s a reasonable assumption to presume this is what drives and enables the Xcode quick-help summary information.

Back to DocC and static hosting – what this means is that the content that gets dumped into the “docs” directory isn’t just plain HTML – it’s the Javascript and all the associated content as well. The effect this has is that while it LOOKS like you could just pop open the index.html in that directory and see your content, it’s not going to work. Instead you’ll just get a blank page, and if you happen to look at the JavaScript console, you’ll see errors reporting that the requested URL wasn’t found on this server.

It also means that the content isn’t available at the root of the GitHub pages you pushed. The root for my Lindenmayer project is https://heckj.github.io/Lindenmayer/ – but going there directly doesn’t show anything. Instead you need to go a couple directories down: https://heckj.github.io/Lindenmayer/documentation/lindenmayer/. Also note that the name of the library is lower-cased. The first thing I tried was https://heckj.github.io/Lindenmayer/documentation/Lindenmayer/, which didn’t work either.

The key thing is to be aware that the URL you want to point people to has that documentation/your_library_name_lowercased extended on it. Oh – and that first repetition of the name is the github repo, in case you don’t have the benefit of having them the same. For example, for the swift automerge library, the reposity is automerge-swift, while the package name is automerge. The URL for the hosted pages on github then becomes: https://automerge.github.io/automerge-swift/documentation/automerge/.

SIDE NOTE:

The example generate-documentation command I provided above has extra bits in it that you probably don’t need, in particular the --emit-digest option. This option generates an additional JSON file at the top level of the content (linkable-entities.json) which contains a list of all of the (public) symbols within the library. I’ve intentionally chosen to include this file in the content I’m hosting on Github pages (at http://heckj.github.io/Lindenmayer/linkable-entities.json)
, although it’s not (to my knowledge) used by the single-page app that displays the HTML content. The formal content definition (written as an OpenAPI spec) is defined in the DocC repository at https://github.com/apple/swift-docc/blob/main/Sources/SwiftDocC/SwiftDocC.docc/Resources/LinkableEntities.json. The short form is that it provides a list of all the symbols that I can use to reference symbols within that content, which otherwise proved of hard to get.

If you’re curious what this looks like, try the following command (assuming you have curl and jq installed):

curl -sL http://heckj.github.io/Lindenmayer/linkable-entities.json | jq '.[].referenceURL' -r.

The output looks something like:

doc://Lindenmayer/documentation/Lindenmayer/RewriteRuleLeftDirectRightRNG/description
doc://Lindenmayer/documentation/Lindenmayer/SceneKitRenderer/generateScene(lsystem:)-5b948
doc://Lindenmayer/documentation/Lindenmayer/RenderCommand/draw-swift.type.property
doc://Lindenmayer/documentation/Lindenmayer/RewriteRuleLeftDirectDefines/CustomStringConvertible-Implementations

These are the full reference links used within the DocC, and a portion of these reference links are what can be used as symbols within the markdown files in the documentation catalog.

<doc://Lindenmayer/documentation/Lindenmayer/SceneKitRenderer/generateScene(lsystem:)-5b948>

maps to the symbol:

``Lindenmayer/SceneKitRenderer/generateScene(lsystem:)-5b948``

In a few cases, those little hash extensions (the -5b948 bit in the example) are tricky to find. Xcode does a reasonable job of dropping them in using code completion, but I’ve found a few bugs where they were hard to ascertain. This JSON file with all the symbols is exactly what I needed to get a full list.

I haven’t (yet?) figured out a means to transform these doc:// resource URLs into web URLs, but I’ve got a notion there’s a means to enable that for the ability to cross-link documentation when libraries build, or depend, on other libraries. Maybe that’s ultimately what this digest is for, but if so – it’s not a feature that’s easily usable yet from DocC. I’m still exploring the internals of DocC, but there’s an idea of a resolver – which can be an external process or service – that provides this mapping for DocC to build the links (I think?) it needs to enable that functionality.

Looking for help to solve a specific 3D math/trig problem

I’ve been working on a Swift library that implements Lindenmayer systems, but the past week has me deeply stuck on a specific 3D math problem. There’s a specific 3D rendering command that’s described in  The Algorithmic Beauty of Plants – the ‘$’ character in that work, that has a special meaning that’s taken me quite a bit to sort out. The idea is that as you progress through rendering, this particular command rotates around the axis that you’re “growing in” such that the “up” vector (which is 90° pitched up from the “forward” vector) is as close to vertical as possible.

It turns out this little critter is critical to getting tree representations that match the examples of what’s in the book. I haven’t solved it – I thought I had earlier, but I managed to delude myself, so now I’m back to trying to sort the problem. The course of solving this had let to some nice side effects – such I know have a nice 3D debugging view for my rendered trees – but I haven’t yet finalized on HOW to solve the problem. In years past, if I saw someone stuck this badly on a problem, I’d advise them to ask for help – so that’s what I’m doing.

I’m using a 3D affine transform (a 4×4 matrix of floats) to represent a combination of translations and rotations in 3D space doing a sort of 3D turtle graphics kind of thing. From this state I know the heading that I’m going, and what I’d like to do is roll around this particular axis. The problem that I’m trying to solve is determining the angle (let’s call it θ) to roll that results in one of the heading vectors being as close to vertical (+Y) as possible while still on the plane of rotation that’s constrained to the “heading” axis.

My starting points for the “forward” heading is +1 on the Y axis, and a local “up” heading as +1 on the Z axis.

The way I was trying to tackle this problem was applying the built-up set of translations and rotations using the 3D Affine transform and then figuring out if there was trigonometry I could use to solve for the angle. Since I know the axis around which I want to roll (or can compute it by applying the transform to the unit vector that represents it – the (0,0,1) vector), I was looking at the Rodrigues rotational formula, but my linear algebra (matrix math) skills and understanding are fairly weak – and I couldn’t see a path to solving that equation for θ given known vectors for the heading, or vectors on the plane that can be used to define a cross-product that is the heading, as well as knowing the world vector.

I’m heading towards trying to solve this by iteratively applying various values of θ and homing in on the solution based on the the resulting value that has the Y component value. I can apply the roll as an affine transform that I multiply onto the current transform, and then test the result of a unit “up” vector – rinse and repeat to find the one that gives me the best “Y” component value.

I’d like to know If there’s a way to solve this directly – to compute the value of θ that I can use to directly do the rotation, rather than numerically iterate/solve into the solution. I wasn’t sure how active (or if I’d get a response) on the GameDev stack exchange but I tried asking:

If you’re familiar with 3D graphics, rotations, transforms, or the mathematics of solving this sort of thing, I’d love to have any input or advice on how to solve this problem, even just some concrete knowledge of if this problem is amenable to a direct solution – or if it’s the kind of thing that requires an iterative numerical solution like I’m considering.

UPDATE: Solved!

I got an answer from a friend in slack who saw these, and I’ve mostly implemented it. There’s a few numerical instability points I need to sort out, but the gist is: Yes, there’s definitely a way to explicitly calculate the angle of rotation needed. The summary of the answer that put me onto the right path is: “Look at this problem as projecting the vector you want to roll towards as a vector on the plane that’s the base of the heading. Once its projected, you can compute the angle between two vectors, and that should be what you need.”

The flow of the process is as follows:

  • start with a unit vector that represents the ‘up’ vector that I want to compare (+Z in my case)
  • pull the 3×3 translation matrix from the affine matrix, and use that to rotate the ‘up’ vector. We’ll be comparing against this later to get the angle. Because it’s explicitly set as a unit vector in the ‘up’ direction, we already know it’s on the plane that can be represented by the normal of that plane – which is our heading vector.
  • Use a similar technique to rotate the ‘heading’ vector (what starts as +Y for me) by the rotation matrix.

(a quick double check here I did in my testing was that these two remained 90° apart after rotation – primarily to verify that I didn’t screw up the rotation calculation)

  • Now that we have the heading, we use that as a normal vector for the plane upon which we want to project our two vectors, and from which we can get the angle desired. The vector (the ‘rotated up’ vector) is already on this plane. The other vector is the +Y world vector – the “as vertical as possible” component of this.

The formula for projecting a vector on a plane is:

vec_projected = vector - ( ( vector • plane_normal ) / plane_normal.length^2 ) * plane_normal

You can look at this conceptually as taking the vector you want to project and subtracting from it the portion of the vector that corresponds to the normal vector, which leaves you with just the component that’s aligned on the plane.

🎩-tip to Greg Titus, who referred me to ProjectionOfVectorOntoPlan at MapleSoft.

  • Once both the vectors are projected onto that base plane, then use the equation to calculate the angle between two vectors:
acos(
    dot(rotated_up_vector, projected_vector) /
            ( length(projected_vector) * length(rotated_up_vector) 
    )
 )

There’s some numerical instability points I need to work out where my current code is returning ‘NaN’ from the final comparison, and the directional component isn’t included in that – so I need to sort out a way to determine which direction to rotate, but the fundamentals part of it is at least sorted.

Solved, even better

After I’d implemented most of the above details to prove to myself that it worked, I received a suggestion from DMGregory with a significantly better answer. His solution uses Vector cross-products to get define the plane that’s the base upon which we’ll rotate, and then uses the Arctangent function with two parameters to compute the angle from the dot products from those two angles.

It’s a denser solution, but he provided a helpful way to think about it that made a lot of sense to me after I sketched it all out in a notebook so I could understand it:

planeRight = normalize(cross(worldUp, heading));

planeUp = cross(heading, planeRight);

angle = atan2(dot(currentUp, planeRight), dot(currentUp, planeUp));

You can think of the dot products as getting us the x & ycoordinates of our current up vector in this plane, and from that the two-argument arctangent gets us the angle of the vector from the positive x-axis in that plane.

DMGregory

Since I’m using SceneKit, I did have to fiddle around with the cross-products to make them legit for a right-handed coordinate scheme, but the resulting solution helpfully provides a proper direction to rotation as well as the value, which is something that the original solution didn’t have.

API Design decisions behind Lindenmayer in Swift

Procedural generation of art is fascinating to me. The scope of efforts that fall into the bucket of procedural generation is huge. Quite a lot of what you find is either focused on art or video games. Within procedural generation, there is a topic that really caught my eye, I think primarily because it wasn’t just about art or games, but rather botany.

I learned of L-systems, also known as Lindenmayer systems, quite some time ago, and knew that you could use them to generate interesting fractal images. L-systems were devised by Aristid Lindenmayer as a formal means to describe and model plant growth. Much of the background was printed in 1990 in the book The Algorithmic Beauty of Plants. The book is currently available from the site Algorithmic Botany in PDF form (which I linked above). I find it fascinating, and after skimming through it a couple of times, I started to look for what code might be available that I could use to play with it. I pretty quickly found the Swift Playground ‘lindenmayer‘, created by Henri Normak. That was neat, but I wanted to go beyond what it could do to re-generate some of the more complex examples from the book.

Fast forward a number of months, and I’ve now published an early release of a swift library that you can use to generate, and render, Lindenmayer systems. The library is available as a swift package – meaning it is intended to be used on Apple platforms (iOS macOS, etc) and quite a lot of it (the core) could be used by swift on Linux without a lot of trouble. I’m hosting the project, and the source for it, on Github at https://github.com/heckj/lindenmayer. The current release (0.7.0) is not at all finalized, but has a lot of the features that I wanted to use: contextual rules, parametric symbols, as well as image and 3D model representations of the L-systems.

To tease a bit of what it can do, let me share some examples from the book, and then my examples of the same L-systems, ported to using the library I created:

Rendered trees from The Algorithmic Beauty of Plants, page 59
The ported L-systems using the original parameters as the book, rendered in 3D

The rendered result is really close – but not quite there. Part of it stems from a the representation of a specific symbol in the original (‘$’), and my interpretation of what that does when render the state of an evolved L-System in three dimensions. It could just as easily be a mistake in how I interpreted and ported the original L-systems that were published in the book. You can really see the differences in the second set of trees that I tried to create from the book:

I debated how I wanted to create this library, and in the end landed on doing something was would push me as a developer, forcing me to more deeply understand the swift programming language as well as how to design APIs with it. The result isn’t an interpreted thing, but something that closely uses the swift compiler and tries to match to leveraging the type safety. If you want to make an L-system with this library, you’re doing it in the swift programming language.

The core of an L-system is a set of modules – an abstract representation – and a set of rules that you apply to these modules. You start with an initial set of modules – maybe just one – and on each ‘iteration’ of an L-system, you go through the entire list of all the existing modules, and apply a set of rules. The rules are set up to match a module, or a specific sequence of modules, and if they do – then you replace the current module in its current location within the sequence, with whatever the set of modules the rule returns. Typically only one rule applies, but in any case I set it up so that tries all the rules in order and only uses the first rule that reports it matches. If no rules match, the symbol is ignored and left in place.

A capability that I wanted to enable was implementing a parametric L-system. This means that the symbols aren’t just one-character things that you interpret, but objects (called modules) that can have parameters. Those parameters can be read, and used to determine if a rule should be chosen, or what the rule produces. I chose to use Swift closures for constructing the rules, the idea being that you could define a module as a Swift struct (with or without properties), evaluate if a rule applied to a module (or set of modules) by either their type, or by their type and properties. If choose, then also making the types with any properties available to compute values and choose what modules should be the replacements. My thinking was anything you could compute using Swift was more immediately available by using a closure, and had the benefit of being type-checked by the compiler.

To make this idea more concrete, take a look through a variation of the system that created the tree images above:

    struct Trunk: Module {
        public var name = "A"

        let growthDistance: Double
        let diameter: Double
    }

    struct MainBranch: Module {
        public var name = "B"

        let growthDistance: Double
        let diameter: Double
    }

    struct SecondaryBranch: Module {
        public var name = "C"

        let growthDistance: Double
        let diameter: Double
    }

    struct StaticTrunk: Module {
        public var name = "A°"
        public var render3D: ThreeDRenderCmd {
            RenderCommand.Cylinder(
                length: growthDistance,
                radius: diameter / 2,
                color: ColorRepresentation(red: 0.7, green: 0.3, blue: 0.1, alpha: 0.95)
            )
        }

        let growthDistance: Double
        let diameter: Double
    }

    public struct Definitions: Codable, Equatable {
        var contractionRatioForTrunk: Double = 0.9 /* Contraction ratio for the trunk */
        var contractionRatioForBranch: Double = 0.6 /* Contraction ratio for branches */
        var branchAngle: Double = 45 /* Branching angle from the trunk */
        var lateralBranchAngle: Double = 45 /* Branching angle for lateral axes */
        var divergence: Double = 137.5 /* Divergence angle */
        var widthContraction: Double = 0.707 /* Width contraction ratio */
        var trunklength: Double = 10.0
        var trunkdiameter: Double = 1.0

        init(r1: Double = 0.9,
             r2: Double = 0.6,
             a0: Double = 45,
             a2: Double = 45)
        {
            contractionRatioForTrunk = r1
            contractionRatioForBranch = r2
            branchAngle = a0
            lateralBranchAngle = a2
        }
    }

    static let defines = Definitions()
    public static let figure2_6A = defines
    public static let figure2_6B = Definitions(r1: 0.9, r2: 0.9, a0: 45, a2: 45)
    public static let figure2_6C = Definitions(r1: 0.9, r2: 0.8, a0: 45, a2: 45)
    public static let figure2_6D = Definitions(r1: 0.9, r2: 0.7, a0: 30, a2: -30)

    public static var monopodialTree = Lindenmayer.withDefines(
        [Trunk(growthDistance: defines.trunklength, diameter: defines.trunkdiameter)],
        prng: PRNG(seed: 42),
        parameters: defines
    )
    .rewriteWithParams(directContext: Trunk.self) { trunk, params in

        // original: !(w) F(s) [ &(a0) B(s * r2, w * wr) ] /(d) A(s * r1, w * wr)
        // Conversion:
        // s -> trunk.growthDistance, w -> trunk.diameter
        // !(w) F(s) => reduce width of pen, then draw the line forward a distance of 's'
        //   this is covered by returning a StaticTrunk that doesn't continue to evolve
        // [ &(a0) B(s * r2, w * wr) ] /(d)
        //   => branch, pitch down by a0 degrees, then grow a B branch (s = s * r2, w = w * wr)
        //      then end the branch, and yaw around by d°
        [
            StaticTrunk(growthDistance: trunk.growthDistance, diameter: trunk.diameter),
            Modules.Branch(),
            Modules.PitchDown(angle: params.branchAngle),
            MainBranch(growthDistance: trunk.growthDistance * params.contractionRatioForBranch,
                       diameter: trunk.diameter * params.widthContraction),
            Modules.EndBranch(),
            Modules.TurnLeft(angle: params.divergence),
            Trunk(growthDistance: trunk.growthDistance * params.contractionRatioForTrunk,
                  diameter: trunk.diameter * params.widthContraction),
        ]
    }
    .rewriteWithParams(directContext: MainBranch.self) { branch, params in
        // Original P2: B(s, w) -> !(w) F(s) [ -(a2) @V C(s * r2, w * wr) ] C(s * r1, w * wr)
        // !(w) F(s) - Static Main Branch
        [
            StaticTrunk(growthDistance: branch.growthDistance, diameter: branch.diameter),
            Modules.Branch(),

            Modules.RollLeft(angle: params.lateralBranchAngle),
            Modules.LevelOut(),
            SecondaryBranch(growthDistance: branch.growthDistance * params.contractionRatioForBranch,
                            diameter: branch.diameter * params.widthContraction),

            Modules.EndBranch(),

            SecondaryBranch(growthDistance: branch.growthDistance * params.contractionRatioForTrunk,
                            diameter: branch.diameter * params.widthContraction),
        ]
    }
    .rewriteWithParams(directContext: SecondaryBranch.self) { branch, params in
        // Original: P3: C(s, w) -> !(w) F(s) [ +(a2) @V B(s * r2, w * wr) ] B(s * r1, w * wr)
        [
            StaticTrunk(growthDistance: branch.growthDistance, diameter: branch.diameter),
            Modules.Branch(),

            Modules.RollRight(angle: params.branchAngle),
            Modules.LevelOut(),

            MainBranch(growthDistance: branch.growthDistance * params.contractionRatioForBranch,
                       diameter: branch.diameter * params.widthContraction),

            Modules.EndBranch(),
            MainBranch(growthDistance: branch.growthDistance * params.contractionRatioForTrunk,
                       diameter: branch.diameter * params.widthContraction),
        ]
    }

As a side note, the library uses both protocols as types (also known as existential types) and generic types – both for generating the L-systems and the rules within an L-system. Enabling that made for some tedious development work, as there’s not a lot of “meta-programming” capabilities with the Swift language today. That said, I’m happy with the results as they stand right now. I’m still debating if I would get any benefit from leveraging the Swift DSL capabilities with result builders. I’ve watched and re-watched Becca Dax’s talk from WWDC 21: Write a DSL in Swift using result builders at least four times, but so far I haven’t convinced myself it’s a win over the factory methods I’ve currently enabled.

The 2D representation draws into SwiftUI’s canvas (which is pretty much a clone of the stuff that draws into a CoreGraphics context that Henri Normak shared in his playground), and the 3D representation work generates up a SceneKit scene, and there’s so much more to go!

I love the idea of being able to use this to generate images or 3D models, and explore the variety of things you can create with L-systems. A huge shout-out to Dr. Kate Compton, who’s writing over many years has enabled me to explore a variety of things within procedural generation. I’m still working up to being able to “generate a 1000 bowls of oatmeal“, at least easily. One of the recent additions I enabled was randomization within the library. I included a seedable pseudo-random number generator, so you can make things deterministic if you want.

This was the first real effort I’ve taken to generating 3D scenes, so I may need to step back and re-think through the whole renderer that generates SceneKit scenes, and I haven’t yet even begun to explore how I might enable the same with RealityKit. The 2D version was relatively straight-forward, but when you get into 3D there’s all sorts of bizarre complications of rotations to deal with – and while I have something basically working, it’s not intuitive to understand – or debug.

I have a number of ideas for how I might continue to grow this – but if you find it interesting, feel free to try it out. I’ve enabled discussions on the Github repo, or you can track me down on twitter pretty easily, if you have questions or suggestions. I’m rather fixated on the issue that what I’m generating doesn’t exactly match the results from the book, and what I interpreted incorrectly, but I think the gist of what this is and does is reasonably solid. If you come to play, don’t be surprised if the examples that I have built into the library change as I iterate on getting the results to match more closely to the originally published work.

I dearly wish that the Swift Playgrounds version 4 (the update that just came this winter) out allowed for Swift packages to be used within a playground. Alas, that doesn’t appear to be the case – but you can still experiment with this library using Swift Playgrounds by including it in an App. I’ll have to explore how to publish that as an example… another thing for the TO-DO list for the project!

%d bloggers like this: