-
Notifications
You must be signed in to change notification settings - Fork 108
Programming in Arcadia
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.
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.
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.
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.
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.
Unity will restart the Mono VM at specific times
- Whenever the editor compiles a file (e.g. when a
.cs
file underAssets
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.