Unity integration for the MAGIC compiler.
This UPM package ships the Clojure runtime DLLs that Unity loads at play time, plus the Editor-side preprocessors that rewrite MAGIC's IL during an IL2CPP build (iOS, Android, consoles). It does not compile Clojure; that step runs outside Unity via nos dotnet/build (see Nostrand).
Consume as a UPM package via git URL in Packages/manifest.json, pinned to a tag from the releases page. Pick the variant that matches how your Editor runs Clojure (see Package variants):
// Default: MAGIC everywhere, including the Editor's Play mode. For projects
// with no stock ClojureCLR.
"sg.flybot.magic.unity": "https://github.com/flybot-sg/magic.git?path=magic-unity#<tag>"
// Dual: stock ClojureCLR in the Editor, MAGIC in player builds. The runtime
// DLLs are excluded from the Editor, so there is no console noise and no probe
// clash. For projects that keep ClojureCLR for Editor / REPL work.
"sg.flybot.magic.unity.dual": "https://github.com/flybot-sg/magic.git?path=magic-unity-dual#<tag>"
Install exactly one variant. See magic-unity-smoke for a working IL2CPP regression project that uses this integration.
Runtime/Infrastructure/Export/- prebuilt Clojure runtime:Clojure.dll,Magic.Runtime.dll, and the full stdlib as*.clj.dll(e.g.clojure.core.clj.dll,clojure.pprint.clj.dll, ...). Unity loads these as regular .NET assemblies at play time.Runtime/Magic.Unity.cs- theMagic.Unity.ClojureAPI (Boot/Require/GetVar) that C# scripts call to drive the Clojure runtime. Sets the platform-appropriate code-load order (InitTypeonly on IL2CPP,InitType+FileSystemin the Editor).Editor/MagicPreprocessor.cs- anIPreprocessBuildWithReporthook that runs on every build and drives the IL2CPP-specific rewrites below.Editor/IL2CPPWorkarounds.cs- walks each candidate assembly with Mono.Cecil and appliesEliminateUnreachableInstructions(removes dead IL the AOT linker chokes on) andGenerateGenericWorkaroundMethods(synthesises reachable instantiations of generic delegate helpers so IL2CPP's generic-sharing pass can find them).Editor/LinkXmlGenerator.cs- appends MAGIC-required<preserve>entries toAssets/link.xmlso the managed-code stripper does not remove dynamically-referenced types.
- You compile your own Clojure namespaces to
.clj.dlloutside Unity vianos dotnet/build, writing them intoAssets/Plugins/Magic/(see magic-unity-smoke/dotnet.clj for the canonical task definition). The package does not include a compiler. - Unity opens the project. The prebuilt runtime + stdlib from
Runtime/Infrastructure/Export/and your own.clj.dlls are both loaded as plain .NET assemblies.Magic.Unity.Clojure.Boot()initialises the runtime;Require/GetVarlet C# scripts call into Clojure. - On every build,
MagicPreprocessorruns first. When the build target uses IL2CPP, it rewrites the.clj.dllbodies in place so the IL2CPP transpiler can consume them (and writeslink.xmlentries); on a Mono build the preprocessor only sweeps any leftover IL2CPP-only workarounds from a previous build. The runtime DLLs are loaded the same way under either backend. - Coexistence with stock ClojureCLR: if a strong-named
Clojure.dllis found underAssets(projects that keep ClojureCLR for Editor work and MAGIC for shipped builds), every MAGIC-compiled.clj.dllis imported with Editor loading off, because the stock runtime probe-loadsclojure.core.cljat init and a MAGIC DLL answering that probe fails to load. With this default variant the runtime ships Editor-loadable, so on an immutable (PackageCache) install the Editor logsAssembly '...clj.dll' will not be loaded due to errors: Assembly is incompatible with the editorfor the package'sExportDLLs on every domain reload. These lines are benign (Unity reporting the intended exclusion, not a failure), and player builds are unaffected. To silence them, use the.dualvariant, which ships the runtime excluded from the Editor by construction.
The package is shipped in two variants. They are identical except for one thing: whether the runtime Runtime/Infrastructure/Export/*.clj.dll plugins carry a !UNITY_EDITOR define constraint. That single difference decides whether the MAGIC runtime is visible to the Editor.
sg.flybot.magic.unity (default) |
sg.flybot.magic.unity.dual |
|
|---|---|---|
| Runtime in the Editor | yes (loadable) | no (!UNITY_EDITOR) |
| MAGIC in Editor Play mode | works | not available (use stock ClojureCLR) |
| Stock ClojureCLR alongside | probe clash, handled by the coexistence guard, with benign console noise | no clash, no noise (runtime is simply absent from the Editor) |
| Player builds (Mono / IL2CPP) | identical | identical |
Choose the default if your project runs MAGIC in the Editor (Play mode, edit-mode tooling) and has no stock ClojureCLR. Choose .dual if your Editor runs stock ClojureCLR (REPL / hot-reload) and MAGIC only ships in player builds: the runtime is excluded from the Editor by the define constraint, so Unity never attempts to load it there and prints no narration.
magic-unity-dual/ is generated from magic-unity/ by bb gen-unity-dual (the DLLs are byte-identical copies; only the 46 runtime .metas and the package name differ) and is kept in sync by bb check-drift. The in-repo coexistence repro that validates both variants is magic-unity-coexist/ (bb coexist-noise for the dual variant, bb coexist-noise magic-only to reproduce the noise).
Magic.Unity.Clojure static class:
void Require(string ns)- load a Clojure namespace. Must be called before looking up vars in that namespace.clojure.lang.Var GetVar(string ns, string name)- look up a Clojure var. Dereference withderefor invoke withinvoke.T GetVar<T>(string ns, string name)- typed variant.void Boot()- initialize the Clojure runtime. Called automatically by the other methods; rarely needed directly.
Copyright © 2020 Ramsey Nasser and contributors. Licensed under the Apache License, Version 2.0.