Skip to content

Programming in Arcadia

Ramsey Nasser edited this page Apr 23, 2017 · 6 revisions

NOTE Until our documentation settles down, USAGE.md is a better source of up to date information

Arcadia is different from both Unity and Clojure in important ways. Knowledge of both is important, but so is understanding how Arcadia itself works.

Clojure CLR

Most Clojure programmers are familiar with the JVM-based version of the language, but Arcadia does not use that. Instead, it is built on the official port to the Common Language Runtime that David Miller maintains. We maintain our own fork of the compiler so that we can introduce Unity specific fixes.

As an Arcadia programmer you should be aware of the differences between ClojureCLR and ClojureJVM, and the ClojureCLR wiki is a good place to start, in particular the pages on interop and types.

Unity Interop

Arcadia does not go out of its way to "wrap" the Unity API in Clojure functions. Instead, a lot of Arcadia programming bottoms out in interoperating directly with Unity. For a function to point the camera at a point in space could look like

(import UnityEngine.Camera)

(defn point-camera [p]
  (.. Camera/main transform (LookAt p)))

This uses Clojure's dot special form to access the static main field of the Camera class, which is a Camera instance and has a transform property of type Transform that has a LookAt method that takes a Vector3. It is the equivalent of Camera.main.transform.LookAt(p) in C#.

Note the (import UnityEngine.Camera) form. Unlike C#, Clojure does not import entire host namespaces, but rather individual classes. Without the import we would have to refer to Camera as UnityEngine.Camera, which works but is somewhat cumbersome.

Unity is a highly mutable, stateful system. The above function will mutate the main camera's rotation to look at the new point. Furthermore, the reference to Camera/main could be changed by some other bit of code. Unity's API is single-threaded by design, so memory corruption is avoided. Your own Clojure code can still be be as functional and multi-threaded as you like, but keep in mind that talking to Unity side effecting and impure.

There are parts of the Unity API that we have wrapped in Clojure functions, however. These are usually very commonly used methods that would be clumsy to use without wrapping, or would benefit from protocolization. The scope of what does and does not get wrapped is an on going design exercise of the framework, but in general we plan to be conservative about how much of our own ideas we impose on top of the Unity API.

Multithreading

While Unity's API is single threaded, the Mono VM that it runs on is not. That means that you can write multithreaded Clojure code with all the advantages that has as long as you do not call Unity API methods from anywhere but the main thread (they will throw an exception otherwise). The exception to this is the linear algebra types and associated methods are callable from non-main threads.

Namespace Roots

The Assets folder is the root of your namespaces. So a file at Assets/game/logic/hard_ai.clj should correspond to the namespace game.logic.hard-ai. Arcadia internally manages other namespace roots as well for its own operation, but Assets is where your own logic should go.

VM Restarting

Unity will restart the Mono VM at specific times

  • Whenever the editor compiles a file (e.g. when a .cs file under Assets is updated or a new .dll file is added)
  • Whenever the editor enters play mode

When this happens, any state you had set up in the REPL will be lost and you will have to re-require any namespaces you were working with. This is a deep part of Unity's architecture and not something we can meaningfully affect.