Swift on Android

Eric Wing blurrrsdk.com

try! Swift Tokyo 2017 Need to be quick… Android dev is a large topic for the uninitiated

• (Obligatory) Why Swift on Android?

• A native language that is cross-platform (like & C++)

• I’ll assume everybody here already here already sees the value

• Note: I put extra things in my slides that, I won’t have time to talk about, but are to help you in the future when you refer back to the talk. Yes, the real Apple Swift My Background: Worn lots of hats

• Prove to you I actually know what I’m talking about

• Not just an Android dev

• Also a seasoned Mac/iOS dev (and other platforms too)

• I will act as your bridge between worlds

• I look at this holistically from the vantage point of computer history & fundamentals Globalstar: Satellites & Rockets

• Global communication system based on constellation of satellites and ground stations

• Launch satellites into space with rockets!

• (Not relevant for this talk, but I’m told it sounds cool) From Cross-platform to Native Cocoa

• Cross-platform, Scientific Visualization

• End of the Unix Wars => domination

• Mac OS X: A Unix with a user-friendly UI

• Meld cross-platform OpenGL sci-viz with native Cocoa UI for best experience Open Projects

• Got involved in some open source projects

• Usually related to improving Apple platform support LuaCocoa

• Wrote world’s first full-featured bridge between Lua & Cocoa

• Obj-C runtime + libffi + Mac OS X 10.5 BridgeSupport

• Complete API coverage including C

• Dual mode: Obj-C garbage collection & traditional

• PowerPC/Intel, 32-bit/64-bit Universal Binaries Beginning iPhone Games Development Commercial Game Engines Corona SDK Platino (Lua) (JavaScript)

• Primary platforms: iOS & Android

• Also: Mac & Windows First “proper” Swift/Android app

https://youtu.be/7fVC0245Io8 https://youtu.be/w6FY_qSi8yY Ouroboros: Eternal cycle of life & death What’s old is new again

• Nothing in this talk is “new”

• Simply re-applying old concepts in different ways

• Compilers / Native code

• C / Application Binary Interface (ABI)

• Unix / / Android

• Recognizing this may make this topic easier to C interoperability

• C is the most portable language

• Every platform has a C compiler (even the web with Emscripten)

• The C ABI is stable and everything is built on it

• Most languages have a way to talk to C

• Swift provides one of the easiest & most direct ways

• Decades of pre-existing

• A lot still useful today Language vs. Libraries

• Helpful to not confuse the two

Swift “standard” libraries

• Swift C • AppKit / UIKit • Darwin • libdispatch • swiftCore • Core Graphics • GlibC • Foundation • Core Audio • Bionic

Minimal Many Language vs. Libraries

• Using Swift on Android (or other platforms) doesn’t mean using Cocoa

Native • Swift C • libdispatch • UIKit iOS • swiftCore library • Foundation • Core Audio App • Darwin

• Swift C Native • Android SDK • swiftCore library Android App • OpenSL ES • Bionic

Cross-platform • SDL • swiftCore Game • OpenAL App Development vs. Server Development (i.e. Extra things that Swift on Linux devs don’t do)

• Binaries must run on end-users native machines

• All dependencies must be bundled with app or provided by the OS

• Should not require installers or users to pre-install additional things

• And never root access!

• Apps have resources that must be bundled

• e.g. images, sounds, data sets

• Ideal: Third-party libraries can also be in binary form (requires stable ABI) We need to real user-facing (Android) apps

(Mostly) Okay Wrong Wrong Android NDK (Native Development Kit)

• All Android apps must be written using the Android SDK which is in

• Android NDK (Native Development Kit) allows you to create dynamic libraries using native code

• All GUI APIs (and almost everything else) are exclusive to the Android SDK, i.e. Java only

• Your Android app must use Java’s loadLibrary to load the dynamic libraries, and then communicate using JNI Swift & the Android NDK

• NDK intended for C & C++, but Swift works too

• Swift itself is written in C++ (which has consequences we’ll get too)

• Your Swift programs are compiled to native code

• So your programs live on the NDK side

• Thus you must use the Android NDK The Android NDK “Really Does Suck”

• John Carmack - “Half-baked”, “Really does suck”

• Second class citizen on Android

• IDE and build systems not well integrated

• Almost no Android libraries are provided in the NDK

• Lots of things are broken, slow to get fixed, if ever

• Word on the street (few years ago): Only 2 full-time Google engineers + a few part time

• Consistent with number of Google employees on NDK mailing list

• No slight intended on those 2 engineers. Valiant effort. Google treats them as the black sheep.

• Google: Among the richest, powerful companies in the world with #1 dominance in mobile, and this is the best effort Google chooses to put in Bionic (Android’s C standard library)

• Android does not use glibc (unlike “real” Linux)

• Wrote their own called “Bionic”

• Doesn’t care about POSIX compliance

• Doesn’t even care about ANSI compliance

• 8 years into Android, still terrible Lua 5.2 (2013) builds with Turbo C 1.0 (1990) for MS- DOS without modification https://youtu.be/-jvLY5pUwic Lua 5.2 (2013) builds with Turbo C 1.0 (1990) for MS- DOS without modification https://youtu.be/-jvLY5pUwic

Android NDK (r11c) fails to compile Lua Insights into Bionic

https://mail-index.netbsd.org/tech-userlevel/2012/07/25/msg006571.html Terrible performance bug for strlcpy

• Wrote a test program to run Test262 suite on JavaScriptCore

• Runs through 11,000+ files

• Used strlcpy

• Mac, iOS so fast that I didn’t think about it

• Android took: 9000 ms

• Switch to strncpy + manual NULL term: 14 ms

• Maybe I should be impressed they provide this function at all? Android SDK/Java isn’t that much better

• Get a list of files (‘ls’) in a directory of an APK is well known to have a serious performance bug

• Get list 11,000+ files: I killed the process after 3 hours of waiting Android file system and the .apk • Files that ship with your app are inside the “.apk” (think .zip)

• Can’t use standard C file family (fopen, fread)

• Needs a “God” object from the Java Android Activity or Context class

• AAsset* AAssetManager_open(AAssetManager *mgr, const char *filename, int mode);

• Existing cross-platform (ANSI) C/C++ libraries won’t work without modification

• Places outside the .apk can use C file family

• “Internal Storage”, but may not have much storage space

• “External Storage”, but may not exist or have correct access permissions Dynamic Library System wonky too

• System.loadLibrary doesn’t automatically load dependencies

• Load must be manually done & in the correct order

• Will silently ignore if a library by the same name is already loaded

• Never use soname versioning and never use symlinks

• (Android further tightened the soname rules in 6.0 and 7.0)

• (Lots of other gotchas I’m omitting for time) Fight Club

• What is the First Rule of Library Programming?

• You do not use C++

• What is the Second Rule of Library Programming?

• ? Swift & C++ (and how it affects you)

• Swift is implemented in C++ and uses the C++ standard library

• Swift has runtime dependencies that use C++ and the C++ standard library

• Swift’s standard libraries are implemented in C++ and use the C++ standard library

• Thus C++ issues are propagated forward to you (inherited) Android NDK and C++

• Android NDK provides 5 different C++ standard libraries you have to choose from

• libstdc++, gabi++, stlport, gnustl, libc++

• All are incompatible with each other

• NDK API level doesn't help either

• C++ standard library does not guarantee a stable ABI so every NDK update potentially breaks

• If you dynamically link

• You must bundle with your app because Android does not ship one with the OS

• In contrast Apple, ships system wide and tries to keep the ABI stable/backwards compatible Android NDK and C++

• In the real world…

• People build binaries of libraries and share them

• People don’t upgrade NDKs at the same time and versions get mixed

• People use multiple libraries, all built under different NDK versions

• So the final application must include a copy all these different C++ library versions

• But Android doesn’t name versions differently

• So files overwrite each other

• And bad things can happen

• In contrast, at least has the sense to put the version number in the file name (e.g. msvcp140.dll) Android NDK and C++

• So we should statically link, right? Android NDK and C++

• So we should statically link, right? Android NDK and C++

• So we should statically link, right?

• Android documentation warnings: Android NDK and C++

• So Lose, Lose

• Thanks (for nothing) Google

• In practice, I personally found static linking to work better Fight Club

• What is the Second Rule of Library Programming?

• You do not use C++

• Unfortunately, Swift uses a ton of it, so we must pay

• Static linking possible, but altering the Swift build system is sooooo hard :( Let’s build Swift for Android: Dependency hell

• Android comes with almost no libraries for the NDK, so we must build all Swift dependencies

• Remember: we build user-facing apps

• All dependencies must be bundled with app (or statically linked) libICU (used by swiftCore): Let’s talk about the “(Mostly)”

(Mostly) Okay libICU: “DLL hell” problem

• Android manufacturers or Android itself may use libICU internally

• If it is used, when we load our library, the attempt silently fails and we call into the internal one

• Bad things can happen if the versions don’t match

• (libICU likes to break the API between versions)

• Static linking can avoid this (but build system hell)

• ICU has switches to modify symbol names differently and rename libraries

• watch out for soname (name and versioning) Good news! You can now write Swift apps

• Once you make it past this, you should be able to build:

• The Swift compiler

• swiftCore

• Swift C library

• This is (more than) enough to write full-fledged, complete Swift apps Foundation dependencies • libxml (C)

• libcurl (C)

• Nice library and very stable ABI

• But depends on OpenSSL which is a headache

• Extremely difficult build system (no Android aarch64/x64 yet)

• In constant need of security updates (Google scans binaries)

• C++ standard library (Bug SR-23 as an example of ABI hell)

• libdispatch How to use Swift in a real Android app

• Instead, we must build a proper Android/Java app, and start from Java

• Follow standard NDK techniques Wrong Wrong Android/Java: Load dynamic libraries in starting Activity public class MyLaunchActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

// Note: Order matters! System.loadLibrary("c++_shared"); // (if we didn't compile statically)

// Notice that I renamed the ICU libraries & symbols System.loadLibrary(“icudataswift"); System.loadLibrary("icuucswift"); System.loadLibrary("icui18nswift");

System.loadLibrary("swiftSwiftOnoneSupport"); // used by swiftCore System.loadLibrary("swiftCore");

System.loadLibrary("swiftGlibc"); // Should be named Bionic

// System.loadLibrary("dispatch"); // System.loadLibrary(“Foundation"); // (may have additional dependencies)

// This is our Swift code compiled into a dynamic library System.loadLibrary("MySwiftMainProgram"); } Android/Java: Call into Native (C) code

package com.blurrrsdk.app; public class MyLaunchActivity extends Activity {

// Tells Java/JNI we promise to implement this function in native code // Activity is unused in this example, but useful as ‘God’ object public native void MyMainEntry(Activity activity);

@Override protected void onStart() { super.onStart(); // Call into native code MyMainEntry(this); } } Android/Java: Call into Native (C) code

package com.blurrrsdk.app; public class MyLaunchActivity extends Activity {

// Tells Java/JNI we promise to implement this function in native code // Activity is unused in this example, but useful as ‘God’ object public native void MyMainEntry(Activity activity);

@Override protected void onStart() { super.onStart(); // Call into native code MyMainEntry(this); } } Android/Java: Call into Native (C) code

package com.blurrrsdk.app; public class MyLaunchActivity extends Activity {

// Tells Java/JNI we promise to implement this function in native code // Activity is unused in this example, but useful as ‘God’ object public native void MyMainEntry(Activity activity);

@Override protected void onStart() { super.onStart(); // Call into native code MyMainEntry(this); } } Android/Java: Call into Native (C) code

package com.blurrrsdk.app; public class MyLaunchActivity extends Activity {

// Tells Java/JNI we promise to implement this function in native code // Activity is unused in this example, but useful as ‘God’ object public native void MyMainEntry(Activity activity);

@Override protected void onStart() { super.onStart(); // Call into native code MyMainEntry(this); } } Android/Java: Call into Native (C) code

package com.blurrrsdk.app; public class MyLaunchActivity extends Activity {

// Tells Java/JNI we promise to implement this function in native code // Activity is unused in this example, but useful as ‘God’ object public native void MyMainEntry(Activity activity);

@Override protected void onStart() { super.onStart(); // Call into native code MyMainEntry(this); } } Android/Java: Call into Native (C) code

package com.blurrrsdk.app; public class MyLaunchActivity extends Activity {

// Tells Java/JNI we promise to implement this function in native code // Activity is unused in this example, but useful as ‘God’ object public native void MyMainEntry(Activity activity);

@Override protected void onStart() { super.onStart(); // Call into native code MyMainEntry(this); } } Android/Java: Call into Native (C) code

package com.blurrrsdk.app; public class MyLaunchActivity extends Activity {

// Tells Java/JNI we promise to implement this function in native code // Activity is unused in this example, but useful as ‘God’ object public native void MyMainEntry(Activity activity);

@Override protected void onStart() { super.onStart(); // Call into native code MyMainEntry(this); } } Now in C: Call into Swift

#include

// Promise C we've implemented this function. extern void MyMain(void);

JNIEXPORT void JNICALL Java_com_blurrrsdk_MyLaunchActivity_MyMainEntry( JNIEnv* jni_env, jobject thiz, jobject activity) { // Call into Swift MyMain(); } Finally in Swift

@_cdecl("MyMain") public func MyMain() { // Now the fun can begin! //

let four = 2 + 2 // TODO/FIXME: print goes to /dev/null on Android print("2 + 2 = \(four)”)

// Remember to not block the } Other details

• Astute viewers may see a shortcut on how to call directly from Java to Swift

• Long-form done for clarity

• The underscore in @_cdecl means it is not finalized. When it is, remove the _

• Do not use @_silgen_name. It was not intended for this purpose and may break

• AndroidManifest. (declare start Activity)

• Package everything properly into .apk

• Java parts + Native parts

• Don’t forget all dependencies Swift Differences on Android

• No Objective-C

• No

• Foundation & libdispatch

• large binary size

• (libICU mostly at fault, ~30MB per arch) Caveats

• Requires Android 5.1+ (thanks to an Android 5.0 deadlock regression bug with stdio)

• Must use Linux to develop (for now)

• armv7 only (for now)

• no main on Android (don’t bother with main.swift)

• print() goes to /dev/null

• adb shell setprop log.redirect-stdio true does not work for NDK side

• Use __android_log_print

• TODO: Put this in Swift implementation directly

• static/global variables Static & Global variables not reinitialized in NDK (actual exit, not background/resume) var g_isInit = false; public func MyEntry() { if(!g_isInit) { DoImportantInitStuff(); Run on g_isInit = true; first launch } else { Now set to true AlreadyInitStuff(); } } Static & Global variables not reinitialized in NDK (actual exit, not background/resume) var g_isInit = false; May not re-initialize on second launch public func MyEntry() { Still set to true on second launch if(!g_isInit) { DoImportantInitStuff(); Does not get run g_isInit = true; on second launch } else { AlreadyInitStuff(); } Run instead of } DoImportantInitStuff() Static & Global variables not reinitialized in NDK (actual exit, not background/resume) var g_isInit = false; public func MyInit() { Try to design a way to force g_isInit = false; re-initialize variables at start } public func MyEntry() { if(!g_isInit) { DoImportantInitStuff(); g_isInit = true; } else { AlreadyInitStuff(); } } Build Systems (Ugh…) Build Systems (Ugh…)

• Languages are standardized, but every platform build system is radically different & changing

• Lots of BS, but important to get right

• Bleeds into user experience if done wrong

• Poor handling leads to installers, admin access, dependency hell, hard coded paths, user hoop jumping, long build times, complexity

• People tend to dismiss or ignore this problem, use band-aids

• (I may be doing additional disservice by speeding past this topic too) Build System Options

• Swift

• Doesn’t really understand cross-platform app development

• Complex C dependencies?

• Do-it-yourself

• hard to scale cross-platform Build System Options

• Used to build LLVM, Clang, & Swift

• Officially being added to Android

• Much better at cross-platform requirements

• But no Swift

• So I started implementing support…

• https://github.com/ewmailing/CMake CMake for Swift example

• It can build executables

• It can build dynamic libraries, which we need for Android

• It can mix with C code and link to C libraries

• It supports bridging headers

• It supports Xcode and Makefiles

• The major thing still to do is linking to other Swift modules cmake_minimum_required(VERSION 3.4) project(MySwiftProject C Swift) if(ANDROID) # Must build library on Android add_library( SwiftApp CCode.c SwiftCode.swift) else() add_executable(SwiftApp CCode.c SwiftCode.swift main.swift) endif() set_property(TARGET SwiftApp PROPERTY SWIFT_BRIDGING_HEADER "MyBridgingHeader.h") Let’s talk about Libraries for writing Android apps

1. Native Android-only development in Swift

2. Cross-platform development (non-native GUI)

3. Cross-platform native GUI Native Android in Swift

• Painful: Must jump back and forth between Java

• Sub-classing Java classes not possible through JNI

• Requires byte code hacks or automatic code generation

• Again: Nothing new here

• Write higher level libraries to encapsulate/hide/reuse/minimize all the JNI code you normally have to write

• Or develop code generation tools Cross-platform (C) libraries • Already solved, especially by the game industry

• Already cross-platform far beyond just Mac/iOS/Android

• SDL (Simple DirectMedia Layer)

• OpenGL, OpenAL

• FreeType

• libpng, libjpeg

• cURL

• Chipmunk Physics FlappyBlurrr Swift

• Flappy Bird contains a lot of subtle details most people overlook

• 60 fps, small footprint

• Real Earth gravity (physics)

• But customized collisions & movement contrary to physics engine

• (e.g. face-plant, no bouncing off pipes)

• Graphics, sound, physics, user input, GUI

• Works on Android, Mac, iOS, Linux, , and pretty much everywhere else where Swift is available

What about (non-native) GUI?

• Many choices, many trade-offs

• My current favorite: Nuklear

• Immediate Mode GUI

• Pure C

• No dependences (header-only)

• Renderer agnostic What about (non-native) GUI?

• Many choices, many trade-offs

• My current favorite: Nuklear

• Immediate Mode GUI

• Pure C

• No dependences (header-only)

• Renderer agnostic Nuklear screenshots Nuklear on Android

https://youtu.be/3-MiceegZlM Nuklear on Android

https://youtu.be/3-MiceegZlM “Sparkle Sparkle” SIMD powered Particle Designer “Sparkle Sparkle” SIMD powered Particle Designer What about cross-platform native GUI?

• Still an “unsolved” problem

• Few solutions, all have major downsides

• Original APIs vs. Cocoa Huge casualty list just in Cocoa…

• OpenStep

• Yellow Box for Windows

• GNUStep

• Cocotron

• Apportable

• Marmalade Juice

• Microsoft Windows Bridge for iOS And truly native list is generally short

• Native: • Not (cross-platform) native: • wxWidgets (C++) • (C++) • IUP (C) • GTK (C) • libui (C) • (Tcl) • Appcelerator Titanium • Electron (JavaScript) (JavaScript) • , AWT (Java) • React Native (JavaScript)

*And remember that only C APIs are callable by Swift IUP (Portable User Interface) A Hidden Gem

• Origins: Academic research project/paper from mid-1990’s

• PUC-Rio (Pontifical Catholic University of Rio de Janeiro, Brazil)

• Same university as Lua language

• Used by certain circles

• Oil industry, Scientific Visualization IUP features • True Native widgets

• Simple & small (library instead of framework)

• Unlike most others, GUI-only (not kitchen sink)

• Foundation + IUP = perfect fit (no redundancy/bloat)

• Pure C (for ultimate portability)

• Designed with language bindings in mind

• LED: Textual layout description language

• Can also be used for per-platform customized layout IUP + Swift?

• Very well thought out API

• Can deal with wide platform variations

• Not trapped in classically Object-Oriented mindset

• Recognized not all native platforms are OO

• Not all languages that want to bind are OO

• Centered around key-value system (e.g. think NSUserDefaults)

• IupSetAttribute(button, "TITLE", “Push Me"); Squint hard and see…

• Basically, this is Protocol-Oriented-Design

• (as best as can be done with weak types & limitations of C)

• Building a Swift layer around this presents fascinating possibilities

• Leverage Swift’s abilities to realize the full potential of POD

• Already lots of elegant solutions in this space

• try! Swift Tokyo 2016: “Swift Eye for the Stringly Typed API” by Andyy Hope IUP drawbacks

• Windows, GTK2, GTK3, backends but…

• No Cocoa backend Of all the problems we could have… this is the best one

• Wait, I’ve been doing Cocoa for over a decade…

• And I’m speaking to a room filled with Cocoa & Cocoa Touch experts

• We can fix this! Linux

Windows Mac (IUP Cocoa, in progress) But what about mobile? We can fix this too!

• IUP’s design/API is well thought out / flexible

• Example: Every other API made the mistake of using a “Window” API

• IUP used “Dialog”, which has fewer pre-conceptions

• Map “Dialog” to

• iOS: NavigationController & ViewControllers

• Android: Activity

• (For more details, see my YouTube presentation: “IUP for iOS & Android”) Demo Program: Create Dialog & Button (recursive) var g_buttonCounter = 0; func BlurrrMain() -> Int32 { IupOpen(nil, nil) IupSetFunction("ENTRY_POINT", IupEntryPoint); IupMainLoop() return 0; } func IupEntryPoint(_ ih:OpaquePointer?) -> Int32 { g_buttonCounter = 0 return ShowNewDialogCallback(nil) } func ShowNewDialogCallback(_ ih:OpaquePointer?) -> Int32 { let button = IupButton(nil, nil) IupSetStrAttribute(button, "TITLE", "Iup Button \(g_buttonCounter)") IupSetCallback(button, "ACTION", ShowNewDialogCallback) let dialog = IupDialog(button) IupSetAttribute(dialog, "SIZE", "QUARTERxQUARTER") IupSetStrAttribute(dialog, "TITLE", "Iup Dialog \(g_buttonCounter)") IupShow(dialog) g_buttonCounter += 1 return IUP_DEFAULT } Ubuntu Linux (amd64) Ubuntu Linux (amd64) Raspberry Pi (Raspbian) Raspberry Pi (Raspbian) Mac Mac iOS iOS Android Android Surprise! Swift on Windows

*Extremely alpha Surprise! Swift on Windows

*Extremely alpha Re-cap of what we just saw

• Native GUI

• Cross-Platform

• Desktop & Mobile

• Single codebase

• In Swift IUP development links • IUP for iOS & Android intro video (with Mac backend cameo):

• https://www.youtube.com/watch?v=UvrEfOg3Nyk

• My repos

• https://github.com/ewmailing/IupCocoa

• https://github.com/ewmailing/IupCocoaTouch

• https://github.com/ewmailing/IupAndroid

• https://github.com/ewmailing/IupEmscripten

• Official IUP website

• http://webserver2.tecgraf.puc-rio.br/iup/ Blurrr SDK (My current endeavor)

• All the things I did in this presentation were done with Blurrr

• No magic: Everything comes from the core ideas I explained

• You can do it too!

• But lots of tedious work & esoteric knowledge

• Want you to focus on just your project code Blurrr SDK (My current endeavor)

• Blurrr deals with a lot of the annoying platform specific issues like build systems and event loop setup

• Provides a bunch of pre-built cross-platform libraries to get you going

• Download and go (no installers, no admin, depends only on native platform build tools)

• “Native” development in all aspects (not a second-class citizen)

• Designed for modularity, customizability, and extensibility Blurrr SDK Workflow Blurrr SDK Workflow

Android Studio Visual Studio Makefiles Xcode Blurrr SDK Workflow

Visual Makefiles Xcode Studio Blurrr SDK

• Starting with the usual suspects, like SDL, cURL, etc.

• Best for games & multimedia apps at the immediate moment

• Looking to IUP next to support general app development Opening up the beta today https://blurrrsdk.com

• Free beta download in celebration of try! Swift

• (Thank you very much for inviting me to speak)

• Again, there’s no magic

• You can do it yourself, but I hope you’ll like Blurrr

• I really could use your support

• Additional Note: I will be at the Hackathon Saturday doing a game dev workshop. Come by and say hi! Links

• Blurrr SDK • BlurrrSDK.com, @BlurrrSDK • Eric Wing (@ewingfighter) • Website: playcontrol.net • YouTube: .com/user/ewmailing • Now playing: “Why we loved Sierra Games” Carlos M. Icaza June 5, 1966 - May 17, 2016

Adobe Macromedia

Corona SDK Platino

@codinginswift

@CodingInSwift Meetup Silicon Valley, August 2014 @CodingInSwift Meetup Silicon Valley, August 2014 Carlos

me Chris Lattner

So…

• Something in his memory

• Also try to inspire you: showing what’s already possible with cross-platform Swift today

• Made with Blurrr

• (Carlos was kicking my butt to launch Blurrr already)

In Memory of Carlos Icaza 1966 - 2016

(He loved the mathematics of splines.) Designed & Programmed by Eric Wing playcontrol.net blurrrsdk.com

Background Art by Judy Rosenwaig www.behance.net/judyrosenwaig

"Late Snows of Winter" (music) by Jeffrey Roberts (jmr) (remix of "The Magic Meadow" by Mark Seibert) ocremix.org/remix/OCR02393 Made with Blurrr SDK blurrrsdk.com Thank you

Eric Wing blurrrsdk.com

(Office hours immediately following)