Creating external modules for Swift Playground Books

Exploring iOS 10 (dev beta 7)

Being able to compile code on an iPad with Swift Playgrounds in the beta of iOS 10 is a complete joy. With certain coding tasks however, the performance of the playground code isn’t all that great. It might run a little sluggishly, or you might find it refuses to run when you start to increase the number of objects in your code. I’d noticed this while playing with SceneKit. I found I could only add around 40 cubes to a scene before the playground refused to run. Yet I knew that Apple’s Learn to Code playground books had significantly more complex scenes running in their liveviews, and that these were powered by Swift/ SceneKit code being compiled live on-device. So why wasn’t my playground performing better?

By default playgrounds capture every object your code creates, making them available for you to inspect. The logged objects are displayed in the margin to the right of your code as your project is running, and can be viewed or have an inspector added to them. Although this logging can give you all kinds of insights into your code, and can be useful for debugging, it does come with a memory overhead.

In this post I’m going to explore three ways of sidestepping this logging process to give the code a significant performance boost (check out the “before / after” image at the top of this post). All of them involve stepping outside of the Swift Playgrounds app itself and manipulating the Playground Book package. The easiest way to do this is to start with the blank Playgrounds book that you can download from Apple’s developer portal. The best way to learn about the book format is to export all the playground books out of the app using the share pane, and dig into them with a text editor. The books created by the “Answers”, “Graphing”, and “Shapes” templates are worth exporting too.

You don’t necessarily have to move over to a Mac to starting editing the book package though. With the excellent iOS Git client Working Copy you can import zipped playground books, make edits, and share them back to the Playgrounds app. If you want to create complex playgrounds with auxiliary source files (method 3 below), Xcode is still the way to go, but for smaller edits to the additional files in a playground book, Working Copy is a good way to do this on iOS (and can handle source control too of course). I will eventually have to do a dedicated post on Working Copy, as I think it’s an essential tool for coding on iOS.

Method 1: Turn off playground logging

If you’ve used Apple’s Learn to Code Playground books, you might have noticed that many of the pages don’t log the playground code. You don’t see a counter incrementing as loops iterate, and objects aren’t logged for inspection on the right-hand margin. You can implement this behaviour with a plist key. Open up the empty playgroundbook template you downloaded and navigate to one of the page’s Manifest.plist. Add this key:

    <key>PlaygroundLoggingMode</key>
    <string>Off</string>

Now that playground objects aren’t being logged you’ll be able to add a lot more objects to your view. Here’s the code I used for this profiling experiment:

import PlaygroundSupport
import SceneKit

let scene = SCNScene()

let box = SCNBox(width: 0.8, height: 0.8, length: 0.8, chamferRadius: 0.1)

func addBoxes(count: Int){
    let length = Int(ceil(cbrt(Float(count)))) // ceilinged cube root
    for i in 0..<count {
        let node = SCNNode(geometry: box)
        node.position = SCNVector3(i % length,  i / (length * length) , (i / length) % length)
        scene.rootNode.addChildNode(node)
    }
}
addBoxes(count: 38)

let view = SCNView()
view.allowsCameraControl = true
view.autoenablesDefaultLighting = true
view.showsStatistics = true
view.scene = scene
PlaygroundPage.current.liveView = view

With logging left on, on an iPad Air 1, the maximum number of nodes I could add was just 38. If you look at the SceneKit statistics bar at the bottom of the liveview, the number next to the diamond shows us that Metal has been able to draw this scene with 2 draw calls, because the nodes share the same geometry.

With logging off however, you can add, well, quite a lot more:

That’s over 100,000 cubes, plus some physics-based lighting environment goodness. Metal is still able to batch 1.25 million triangles into 100-odd draw calls, which isn’t too bad.

Once you have your playground-logging-off template saved in the Playgrounds app, it’s very easy to create a new non-logging playground. You just duplicate the playground, and then reset it to its initial state by selecting Tools > Reset Page.

Method 2: Move performance-intensive code to LiveView.swift

As an alternative to turning off logging, you could move performance-intensive code outside of the main Contents.swift playground file. One option is to move it to the Liveview.swift file for the page. Like the Contents.swift file, you are allowed to have code that executes at the top level in LiveView.swift, which will run automatically when the playground executes. However, entities you define here won’t be accessible from the main playground coding window, even if you mark them as public. Which brings us to…

Method 3: Move performance-intensive code to an external module

The slightly more involved approach for more complex projects is to move performance-intensive code out of the playground page (the Contents.swift file) and into an auxiliary source file that you can place in one of the Sources folders. This code will be compiled once and made available to the main playground coding window as an external module.

Files in the Sources folder are subject to the same access controls as a module, so you’ll need to explicitly mark as public any class/struct, method, property and so on that you want to be able to access from the main playground coding window. You won’t however have to add an import statement to use these modules in the playground.

Navigating a playground
book in Xcode

You can add Sources folders for each page, chapter, and for the whole book. The modules contained in them are scoped accordingly, so if you only wanted a module to be available in a given chapter, you’d place it in the Sources folder at the root level of that chapter.

This gives you the option of authoring your own engines and APIs. If you add method calls to your API that don’t return objects (eg an addGeometry method that adds a node to a scene graph without returning the node to the caller), then those objects will be excluded from playground logging, and even though logging can be left on, the end-user will see the same performance boost as when logging is turned off.

To author playgrounds in this way, you almost certainly do want to be using Xcode. Currently, when you open a playgroundbook package in Xcode beta 8, although you can navigate the folder structure and edit the files (see image), you can’t run the code and you don’t get code completion and compiler checks. Your best bet is to author your playground as a good old Xcode playground (see Apple’s documentation for how to add auxiliary source files to an Xcode playground), and then drag and drop the Sources folder across from the Xcode playground into the relevant part of the playgroundbook hierarchy, before you airdrop the playground book back to the iPad for testing.

Presumably at some point the Xcode playground format and the iPad playgroundbook format will merge and this workflow will become less circuitous. I would also guess that at some stage Apple will announce some kind of distribution method for playground books, perhaps via the in-app “store front” (though if the books remain open source, as the current selection is, perhaps it won’t be a monetized store?), so this kind of side-loading might not be necessary for long.

Currently these auxiliary files can be viewed from within the Playgrounds app (but not edited), by selecting Tools > Advanced > View Auxiliary Source Files. I wouldn’t be surprised if a future version of the Playgrounds app also allowed us to edit these files. That would be a significant step toward Playgrounds on iPad being an IDE. Being able to code and distribute a complete Playground Book using only an iPad is something I expect to be possible before too long.

Authoring Playgrounds books in Xcode is also a good opportunity to try out Xcode 8’s new “Add Documentation” feature. Put the cursor in a method name you want to document and hit ⎇⌘/ to get a documentation template above the method, which you can add descriptions to. You can view these in-app using the “help” context menu button (see image).

Joining it all up

So you have your awesome engine with a public interface with which the end user can call your methods. But what if your engine requires some setup first, how do you ensure this code is run before the user’s playground code? External modules in the Sources folders can’t call methods at the top level, so your engine won’t be able to autorun its own setup method. You could call your setup method from LiveView.swift. A better option though might be to dispense with LiveView.swift altogether and call your setup method from a hidden code block in Contents.swift. You also have the option of concealing your setup method from code completion, making it unlikely that the end user calls the setup method twice. This is the method that Apple use in a number of their playground books.

//#-code-completion(identifier, hide, _SCNSetup())
//#-hidden-code
_SCNSetup()
//#-end-hidden-code

So that’s three ways of improving performance-intensive areas of your code. There’s real potential here I think for creating powerful engines that reside in the external source files, and using the in-app playground coding environment in effect to “script” these engines. This reminds me of data driven design methods, or games that do the heavy lifting in a C++ library, but try to speed up the development process by running game logic in a scripting language such as Lua. Here though, the Swift code running in the playground isn’t technically a script or data: it’s also being compiled.

There is a lot of scope here for creating some really intriguing development environments, both for your own use, and for sharing via whatever distribution method Apple eventually announces for Playground books.

Built with Jekyll      © Salt Pig Media