Kilka postów temu wspominałem, że clojure jest łatwo portowalny.
Spróbujmy zatem zportować RL-Engine do środowiska .NET.
W teorii wystarczy tylko wziąć instniejący kod clojure i skompilować go za pomocą ClojureCLR.
Uwierzmy w magię i spróbujmy.
ClojureCLR
ClojureCLR jest to natywna implementacja Clojure dla CLR.
Umożliwia kompilację kodu w clojure do bibliotek (dll) i plików wykonywalnych (exe) obsługiwanych przez .NET framework bądź mono.
Obecnie dostępną wersją jest 1.7 – jeden numerek do tyłu w porównaniu z implementacją originalną (1.8).
Kompilacja do ClojureCLR
Zasysamy sobie ClojureCLR stąd.
Wybieramy sobie wersję:
- Debug 4.0
- Release 4.0
- Debug 3.5
- Release 3.5
Po wypakowaniu możemy odpalić Clojure.Main.exe i naszym oczom powinien pokazać się REPL:
CLR Repl
Zakładając, że mamy już leiningena i trochę kodu w clojure, możemy przystąpić do instalacji lein-clr.
lein-clr
Lein-clr jest pluginem do leiningena który upraszcza kompilację do CLR.
Można go konfigurować na kilka sposobów – spójrzmy na ten bardziej skomplikowany.
Dodajemy zmienne środowiskowe:
CLOJURE_CLR = <ścieżka_do_clojureCLR> CLOJURE_LOAD_PATH = %CLOJURE_CLR% Path = %CLOJURE_CLR%
CLOJURE_CLR może mieć dowolną nazwę np. CLOJURE_CLR_DEBUG_3.5 itp.
Teraz kilka zmian w pliku naszego projektu (project.clj):
:warn-on-reflection true
:min-lein-version "2.0.0"
:plugins [[lein-clr "0.2.2"]]
:clr {:cmd-templates {:clj-exe [[?PATH "mono"] [CLOJURE_CLR %1]]
:clj-dep [[?PATH "mono"] ["target/clr/clj/Debug 3.5" %1]]
:clj-url "https://sourceforge.net/projects/clojureclr/files/clojure-clr-1.7.0-Debug-3.5.zip/download"
:clj-zip "clojure-clr-1.7.0-Debug-3.5.zip"
:curl ["curl" "--insecure" "-f" "-L" "-o" %1 %2]
:nuget-ver [[?PATH "mono"] [*PATH "nuget.exe"] "install" %1 "-Version" %2]
:nuget-any [[?PATH "mono"] [*PATH "nuget.exe"] "install" %1]
:unzip ["unzip" "-d" %1 %2]
:wget ["wget" "--no-check-certificate" "--no-clobber" "-O" %1 %2]}
:main-cmd [:clj-exe "Clojure.Main.exe"]
:compile-cmd [:clj-exe "Clojure.Compile.exe"]})
U mnie klucze :clj-dep, :clj-url, :clj-zip mają wartości ustawione na wersję Debug 3.5.
Kompilacja
Kompilujemy nasz kod wywołaniem komendy “lein clr compile rl-engine.dungeon-generator” (na razie ograniczymy się tylko do generatorów lochów).
Co ciekawe – działa!
I to za pierwszym razem (nie licząc wywołań compile bez argumentów :P).
Popatrzmy co nam to wygenerowało:
RL Engine w kilku bibliotekach
Hmmm, czyli generuje się dll per plik. Trochę to niewygodne – ale z tym sobie poradzimy innym razem.
ClojureCLR w C#
Tworzymy sobie prostą aplikację konsolową, dodajemy referencje do naszego zestawu dllek oraz instalujemy NuGet package z ClojureCLR.
class Program
{
static void Main(string[] args)
{
IFn require = Clojure.var("clojure.core", "require");
require.invoke(Clojure.read("rl-engine.dungeon-generator"));
var genratorFactory = Clojure.var("rl-engine.dungeon-generator", "get-generator");
var generator = genratorFactory.invoke("bsp") as IFn;
var map = generator.invoke(5, 5);
Console.WriteLine(map);
Console.Read();
}
}
Po odpaleniu powinniśmy dostać:
[[1 1 1 1 1] [1 0 0 0 1] [1 0 0 0 1] [1 1 1 1 1]]
Wow! Kolejny punkt dla ClojureCLR – interop działa bez problemów.
Jedyna rzecz jaką trzeba pamiętać to o załadowaniu naszej biblioteki do runtime’u clojure’a:
RT.load("rl_engine.dungeon_generator");
RL-Engine + WinForms
Teraz pobawimy się czymś bardziej interesującym.
Wygenerujemy sobie poziom w clojure a renderować będziemy go w WinFormsach.
Najpierw zrefaktoryzujemy nasza aplikację konsolową aby wyglądała bardziej jak biblioteka opakowująca clojure:
namespace RLEngine
{
public static class DungeonFactory
{
static DungeonFactory()
{
RT.load("rl_engine.dungeon_generator");
}
public static int[,] GenerateDungeon(int height, int width)
{
return To2D(GenerateDungeonFromClojure(height, width));
}
private static int[][] GenerateDungeonFromClojure(int height, int width)
{
var genratorFactory = Clojure.var("rl-engine.dungeon-generator", "get-generator");
var generator = genratorFactory.invoke("bsp") as IFn;
var map = generator.invoke(height, width);
var arrayConverter = Clojure.var("clojure.core", "into-array");
var vectors = arrayConverter.invoke(map) as PersistentVector[];
var array = vectors.Select(x => x.Cast().Select(Convert.ToInt32).ToArray()).ToArray();
return array;
}
private static T[,] To2D<T>(T[][] source) { ... }
}
}
A teraz wykorzystamy ją w projekcie WinForms:
namespace WinFormsDungeon
{
public partial class Form1 : Form
{
private readonly Graphics _graphics;
private readonly Brush _blackBrush;
private readonly Brush _whiteBrush;
private int _size = 30;
public Form1()
{
InitializeComponent();
_graphics = this.pictureBox1.CreateGraphics();
_blackBrush = new SolidBrush(Color.Black);
_whiteBrush = new SolidBrush(Color.Bisque);
}
private void pictureBox1_Click(object sender, EventArgs e)
{
var dungeon = RLEngine.DungeonFactory.GenerateDungeon(20, 20);
for (int height = 0; height < dungeon.GetLength(0); height++)
{
for (int width = 0; width < dungeon.GetLength(1); width++)
{
var pen = dungeon[height, width] == 1 ? _blackBrush : _whiteBrush;
_graphics.FillRectangle(pen, height * _size, width * _size, 30, 30);
}
}
}
}
}
A oto i efekty:
Konkluzja
Naprawdę jestem bardzo pozytywnie zaskoczony, że wszystko tak po prostu działa.
Żadnych dziwnych pierdół czy problemów konfiguracyjnych.
Ludzie od ClojureCLR zrobili kawał porządnej roboty – tylko tak dalej!






Be First to Comment