clojure swing in real world
I’m currently working on a little tool for my work, one that requires a little gui, a menu, a combo box, two tree views and a text field, most of them interconnected. I’m lucky that I can develop this on clojure since to me it’s a great joy to write in this language. Now I said GUI, and fortunately I’d had clj-swing in my back of tricks and this was a great oportunity for me to test it in real world conditions, now I figured that I share the few things I’ve learned during this last days and weeks.
It works
As absurd this sounds, I was positively surprised of how well it worked and that I had to jump trough little loops to get things working. Of cause it wasn’t all smooth and nice, a few times I had to fall back to java interop but I think it was forgivable in the whole picture. Also it showed me a few things that clj-swing could do better and allowed me to improve it, which will continue to happen :).
The REPL is tricky
I am coding in EMACS+slime and I noticed that swing and slime don’t go along to well, all the seperate treads lead to the point where the swing app locks up for no reason – runnig it with java works fine so.
clojuires mutables are great
We all know this already but I mean great in another sense, it is super easy to store the data in them and see how things automatically update the GUI once the data changes, weehee!
And here how the gui code looks, somet things will go away with in the next clj-swing version and the code for actual actions is in extra functions (as it should be) but this is how you can generate a window with two tree vriews a splitter a combo box and a text field all interacting in one way and another over refs:
(defn -main []
(let [matcher-cb (combo-box (keys @matchers)
:maximum-size (java.awt.Dimension. Short/MAX_VALUE 25)
:action ([e] (combo-box-change-action e)))
menubar (make-menubar
[{:name "File"
:mnemonic KeyEvent/VK_F
:items
[{:action (make-action
{:name "Open..."
:mnemonic KeyEvent/VK_O
:handler (fn [_] (open-action matcher-cb))})}
{:action (make-action
{:name "Compile..."
:mnemonic KeyEvent/VK_C
:handler (fn [_] (compile-action (selected-item matcher-cb)))})}]}])
text (text-field :editable false :str-ref tf :maximum-size (java.awt.Dimension. Short/MAX_VALUE 25))]
(frame :show true :menubar menubar
:default-close-operation :EXIT_ON_CLOSE
:title "Config Generator 2.0" :size [600 400]
[_ (split-horizontal
(tree :model (mapref-tree-model raw-data "Data"))
(stack
[_ (panel :name p
:layout (BoxLayout. p BoxLayout/PAGE_AXIS)
[_ matcher-cb
_ text])
tr (tree
:action ([old-path new-path]
(tree-change-action old-path new-path text))
:model (mapref-tree-model
data {:name "Data" :good (good-data? @data)}
:node-wrapper (fn [node path] (Pathed. node (str (:name node)) path)))
:cell-renderer renderer)]
(add-action-listener text ([e] (check-validity)))))])))On Parentheses
Recently I started to hear too often ‘but it has too many Parentheses’, of course I’m talking about LISP here. To be precise, a LISP called clojure. And to be honest, it gets boring. I find this a quite lame and actually no argument at all.
And now you think ‘oh he’ll go on arguing why they are good’. No I won’t. I’ll just say they have their place. But mostly I will now rant about why this is such a boring argument, that it is stereotypical. It’s not arguing about a language feature, not even really a syntax (since people who scream ‘ewwww Parentheses’ more often than not don’t take the time to actually explore the syntax.)
But it is different – so it must be bad right? Just like black people must be bad because they are different! There is no reason to actually get to know a black person and judge them on their character because, after all they already look different: ewwww! Now if I’d had said that without the tone of sarcasm and without the context you’d likely call me a racist wouldn’t you? But why do we apply these moral guidelines to people and not to language?
You can pick the one icky characteristic from every language and go scream ‘ewww *’ as a good reason never to take a real look at it. Let me humor you and list a few that just come to mind to help you avoid learning any language ever:
- C – Ewwww pointers!, Ewwww buffer overflows!
- C++ – Ewww not C!
- LISP – Ewww parentheses.
- Ruby – Ewwww sloooooow!
- Python – Ewwww indentation!
- Java – Ewwww too much Objects!
- ASM – Ewwww low level!
- JS – Ewwww browsery!
- PHP – Ewwww PHP! (too much here so I just summarize it)
- VB – Ewwww Microsoft!
- C# – Ewwww even more Microsoft!
- Perl – Ewwww unreadable!
- obj-c – Ewww Apple!
- Delphi – Ewwww Microsoft and dead!
- Pascal – Ewwww old!
- Lua – Ewww not for serious things!
- MATLAB – Ewww too mathy!
Now we’re through nearly all of the top 20 languages, it is clear now that they are all are horrible. I think people should just stop programming altogether! (Note: Those of the top 20 I do not know at all I omitted for the sake of not giving a weak argument.)
Okay now back to being a bit more serious: Do you see where I’m coming from? No language is perfect. Just because there is one feature that strikes you odd, or even just different, stop. Don’t just judge the language when you’ve never used it.
But now I’ve got to clean up my mailbox so I can receive all the complaints from the C, C++, LISP, Ruby, Python, Java, ASM, JS, PHP, VB, C#, Perl, Obj-C, Delphi, Pascal, Lua, and MATLAB community about how unfair I am to pass judgement on their languages.
Not so stupid db & and how / why IO works with concurrency in clojure
Murphy, a friend of mine, asked in a comment to my last post ‘does stupiddb support the clojure concurrency model out of the box’. Sadly my first answer had to be ‘perhaps’, since I honestly wasn’t sure, most of it should but the logging caused me headache – since in the worst case it might happen that two threads log at the same time. I wasn’t sure how the writer handles this, and my fear was it might come out mixed.
So I did some research, mainly I addressed it in the #clojure channel – a place where many smart people and me hang around and talk about clojure. My first idea was ‘lets use agents’. Agents are clojures answer to asynchronous but ordered state. Pretty much what logs are.
How do they work? A agent has a state or value, in the case of stupid db this would be a java.io.SomeKindOfWriter. Now we can send this agent functions to execute, and take the new value. This means something like this:
(def log (agent (io/writer log-file)))
; ...
(defn- write-log [out action key value]
(binding [*out* out]
(prn [action key value])
(flush))
out)
; ...
(send log write-log :assoc 1 1)This will write the log [:assoc 1 1] if at the same time someone wants to write the log [:assoc 1 2] the one that is send later, will be sent last. So in the case we’ve a transaction that the [:assoc 1 1] transaction fails and needs to rollback to do it again, we would have something like [:assoc 1 1] [:assoc 1 2] [:assoc 1 1] right? That would still be correct since even we’ve [:assoc 1 1] in there twice, the correct value would be the last one in the log.
BUT, big but here, clojure is smarter some smart person in the channel pointed this line out to me:
Agents are integrated with the STM – any dispatches made in a transaction are held until it commits, and are discarded if it is retried or aborted.
What does this mean? Actually it means clojures STM takes care of the problem for us, the send is only fired if the transaction succeeds, that is pretty darn cool! So the log would actually look like [:assoc 1 2] [:assoc 1 1] which is awesome!
So the essence of this is: when doing IO in a dosync, or in a concurrent situation where you can’t guarantee that the io is only fired once, use agents since they ‘just work’. It is very neat and a good solution to the IO issue, I’m really surprised it isn’t promoted more.
Is everything perfect now? Not exactly, since agents are asynchronous the log might be delayed slightly, which isn’t nice :( but well it is a small tradeoff :) for things just working perfectly. If I figure out how to do it right when it happens, or at the end of the transaction I’ll give an update.
Stupid DB
No, no rant here. Just a little update. The other day I pushed version 0.2.0 of stupiddb a very simple database for clojure. The main goal was it to make a database that is very easy to add to a project and very easy to maintain.
Often I saw code like this:
(with-open [w (io/writer "some-file")]
(binding [*out* w]
(pr my-map-with-data)))That is called either at the end of the program or after every change of the map. Neither are god since it either can lead to data loss when the program isn’t terminated properly or it causes a lot of file IO when every change is written.
Now one could go for some kind of DB, MySQL, haddop, mongodb. All work great with clojure but at times it isn’t really what people want. It means that you’ve to deploy another kind of software and some drivers for the database.
So I figured to make a stupidly simple solution. A minimal database that abstracts the code earlier in a nice way. So stupid db has 6 db commands and 2 ‘maintenance’ commands to open and close the database.
The commands are simple:
- (db-get [db key]) – gets a value from a key.
- (db-get-in [db [& k]]) – gets a key from nested data in a db.
- (db-assoc [db k v]) – associates a value with a key.
- (db-dissoc [db k]) – dissociates a key with a value.
- (db-assoc-in [db [& ks] v]) – associates a key within a nested db.
- (db-update-in [db [& ks] f]) – updates a nested value in the db.
What happens in the background? Stupidb store the data in a map that is hold in a ref to keep data up to date. It attempts to give a good compromise out of IO performance and data security.Every time a db-* function called a log is written, it is written directly and the file so even if the program crashes every change will be logged.
Every n seconds the entire db is written and the log emptied, if the db-init function is passed :gzip true the db file is written is gzipped to save space and increase IO performance.
The db can be closed to make the auto save stop and force a db write, but it is not necessary since every action is logged and when the db is opened while there is a non empty the DB file will be loaded and the logs are ‘replayed’ to get the DB back to the latest stand.
That said stupiddb isn’t a replacement for a real database if big data amounts are handled but if it is just to save some data in a client side it is likely better then reinventing the wheel :).
Have fun!
The ease of gui's.
So clj-swing gets more and more interesting. There are some nice new features like text boxes that are directly cupeled to the content with a ref of a string. So I want to take this as an opportunity to show off a little. As a example I picked the Temperature converter from Stuart Sierras blog . Why? Since it is simple and since it allows a nice comparison of ‘plain’ swing coding and clj-swing coding.
Lets start with the basics. First we need some helper functions.
Converting the temperatures forth and back, This is more then less directly taken from Stuarts blog.
(defn c-to-f [c]
(+ (* c 9/5) 32))
(defn f-to-c [f]
(* (- f 32) 5/9))And some helper that we will need later to make it easily to update the text boxes. We use constantly to return a function so we can just alter the ref with this.
(defn convert-temp [converter temp]
(constantly (str (Math/round (float (converter (Double/parseDouble (.trim temp))))))))Now it gets interesting, first we need to set up two refs for the content of the text box. Since we need them nowhere else we put this in a let.
(defn converter-example []
(let [c (ref "")
f (ref "")]Now c and f will hold the string for Celsius and Fahrenheit. We initialize them with empty strings. Next up is creating the gui itself, we start with a frame form, this takes some arguments. You see :layout :grid-bag here, this is a short form of :layout (GridBagLayout.) :constrains (GridBagConstrains.). Also since we want to pack the frame and show it right after the call we add :show true and :pack true.
(frame :layout :grid-bag :show true :pack truefNow we go to set up the layout, this look a lot like Stuart’s code since I adopted his idea. The setup is somewhat like a let with added support for constrains.
[:gridx 0 :gridy 0 :anchor :LINE_END
_ (label "C")
:gridy 1
_ (label "F")
:gridx 1 :anchor :LINE_STARTThe two labels are pretty boring and straight forward, you pass it a string and it makes a label. Now here comes the interesting part, the text fields. Lets first summarize what we want. The user types a temperature in the field and presses return, the change gets validated and the other field updated. Now lets see how we do this:
_ (text-field :str-ref f :columns 10
:action ([_]
(if (re-find #"^\d+$" @f)
(dosync (alter c (convert-temp f-to-c @f))))))Lets take the arguments apart, :columns 10 This tells the text field that it should be 10 chars wide. Now the interesting part: :str-ref f tells the text field to keep up to date with the ref of f we defined earlier in the let. This means if the text field changes f changes and if f changes the text field changes.
Now to :action, this is a function that gets called when the ‘action’ happens, in the case of a text field this means when return is pressed. It takes one parameter, the event, we don’t care about this right now so we just ignore it. Then we use a regexp to see if the content is a number and if it is we just alter c with convert-temp. Done, the updating in the GUI happens neat isn’t it.
So here to finish up the rest of the code just to finish it up:
:gridy 0
_ (text-field :str-ref c :columns 10
:action ([_]
(if (re-find #"^\d+$" @c)
(dosync (alter f (convert-temp c-to-f @c))))))])))Here we go and that is how it looks:

GUI's in clojure
There are some nice posts in the interwebs about how to use swing in clojure, it seems the syntax of clojure and the good java interop makes this pretty nice. Especially Stuart Sierra’s series has inspired me a lot since it is pretty details.
But in the end every posts leaves you with the feeling that you’re left alone with java when you really want to do something more then a tutorial. Creating objects, calling setters, getters using static fields. It is ugly to be frank, in his example Stuart did a great job to hide the javaness of grid layout from you, it’s a start but I think everything should be like that.
This is how it looks in the example:
(defn temp-app []
(let [celsius (JTextField. 3)
fahrenheit (JTextField. 3)
panel (doto (JPanel. (GridBagLayout.))
(grid-bag-layout
:gridx 0, :gridy 0, :anchor :LINE_END
:insets (Insets. 5 5 5 5)
(JLabel. "Degrees Celsius:")
:gridy 1
(JLabel. "Degrees Fahrenheit:")
:gridx 1, :gridy 0, :anchor :LINE_START
celsius
:gridy 1
fahrenheit))]
(listen-temp celsius fahrenheit c-to-f)
(listen-temp fahrenheit celsius f-to-c)
(doto (JFrame. "Temperature Converter")
(.setContentPane panel)
(.pack)
(.setVisible true))))There is another nice post about the topic on Jeff Fosters blog showing this example:
(import '(javax.swing JFrame JLabel JTextField JButton JComboBox JPanel Timer)
'(java.awt.event ActionListener)
'(java.awt GridLayout))
(defn draw-sort [canvas alg]
(prn alg))
(defn sortapp []
(let [frame (JFrame. "Sort Visualizer")
canvas (JPanel. true)
algorithm-chooser (JComboBox.)
run-button (JButton. "Run Algorithm")]
(.addActionListener run-button
(proxy [ActionListener] []
(actionPerformed [evt]
(draw-sort canvas (.toString (.getSelectedItem algorithm-chooser))))))
(doto algorithm-chooser
(.addItem "Quick sort")
(.addItem "Bubble sort"))
(doto frame
(.setLayout (GridLayout. 2 2 3 3))
(.add algorithm-chooser)
(.add canvas)
(.add run-button)
(.setSize 300 300)
(.setVisible true))))So why am I showing this examples, someone has already? Because they inspired me to write clj-swing . Especially the second one, while being a good introduction to swing in clojure, is in my eyes very ugly. Taking all that above together adding a few macro mojo and some reading into swing you could get something like this instead:
(def sr (ref '["Quick sort" "Bubble Sort"]))
(def lm (ref '[]))
(defn grid-bag-sort-app []
(frame :title "Sort Visualizer" :layout (GridBagLayout.) :constrains (java.awt.GridBagConstraints.) :name fr
:show true :pack true
[:gridx 0 :gridy 0 :anchor :LINE_END
_ (label "Algorithms")
:gridy 1
_ (label "Button")
:gridx 1 :gridy 0 :anchor :LINE_START
algorithm-chooser (combo-box [] :model (seq-ref-combobox-model sr))
:gridy 1
_ (button "Run Algorithm"
:action ([event]
(dosync (alter lm conj (selected-item algorithm-chooser)))))
:gridx 3 :gridy 0 :gridheight 2 :anchor :CENTER
_ (scroll-panel (jlist :model (seq-ref-list-model lm)) :preferred-size [150 100])]))Why is that better? Well in my opinion there are some reasons. First of all There are only 2 java objects you really need to handle, aside of that all is clojury. that means you can further macrofy this if you want. Also it embraces clojures nice STM to swings MVC as a model.
(seq-ref-combobox-model sr)
;; and
(seq-ref-list-model lm)wrap a ref that contains a sequence into a ugly java proxy so the list and combo box automatically reflect the content – all done for you in one call.
It’s by far not done but I hope it is a good start to make clojure a language that can be used nicely in GUI apps, something that sadly never happened for ruby…
On clojure libs and versions.
Between clojure 1.1.0 and 1.2.0 a lot will change and every library maintainer has to make the decision how or what to support. Basically there are three general options.
- Make it 1.1.0 compatible
- Make it 1.2.0 compatible
- Make the effort of making the library compatible for both versions.
Option 1 and two are trivial and will not change much in the development process so for the third, and in my eyes best option some work is needed. I’ve went through this for one of my own libraries and with the great help of the mailing list and the #clojure channel succeeded. Now I figure I share my experience to help others make their libraries more compatible.
Namespaces
The first issue is changing namespaces, as an example in clojure 1.1 the namespace clojure.contrib.duck-streams holds io utility functions, in clojure 1.2.0 it is renamed to clojure.contrib.io. So if you have code like (I take the worst case here):
(:ns my.cool.app
(:use clojure.contrib.duck-streams))it will stop to work with 1.2.0, the trick are require and aliases:
(:ns my.cool.app)
(try
(require '[clojure.contrib.duck-streams :as io])
(catch Exception e (require '[clojure.contrib.io :as io])))So what we basically do is trying to require the clojure.contrib.duck-streams namespace and try to alias it to io, if it does not exist (since we are in clojure 1.2) a exception is thrown, the catch clausal now catches this exception and requires clojure.contrib.io instead aliasing this to io. In the end either one or the other will be aliased to io to the remaining program it is transparent where io comes from when calling (io/reader …) for example.
Classes
Another topic are classes, this is very unlikely to hit you but since it might happen I’ll talk about it. Clojure 1.2.0 deprecates a few classes, removing them entirely. So say you, for some reason need clojure.lang.Stream for some reason, this does not exist in 1.2.0 and will throw ugly exceptions when someone tries to use your library with 1.2.0. Ugh!
What to do? Well I can’t tell you how to replace clojure.lang.Stream but I can tell you how not to get an exception and gracefully handle the situation.
The first problem is that, the second the reader sees clojure.lang.Stream it will scream explode and leave ugly smudges on the floor. And that will happen at compile time nothing you can do about that, no try catch no if nothing. So what to do? Simple don’t write it!
(try
(let [StreamClass (resolve 'clojure.lang.Stream)]
(do stuff with StreamClass))
(catch Exception e (do whatever you do when there is no 'clojure.lang.Stream)))So why does this work? The trick is resolve, notice the ’ in front of clojure.lang.Stream makes it a symbol instead of a class the reader and compiler don’t care of a class with this name exists then, you could write ‘i.want.cookies and probably you won’t get any from the compiler, neither cookies nor exceptions.
Resolve will – at runtime – try to make ’clojure.lang.Stream the class clojure.lang.Stream, if it fails it throws an exception but one you can catch and handle! How you handle it is your problem so, sorry ;).
Being nice to the users (making the right dependencies!)
So now you have a library that runs in clojure 1.1.0 and 1.2.0, great! But I’ll bet you a cookie that your leiningen project.clj (or maven pom) says [clojure “1.1.0”] or [clojure “1.2.0”] that is bad bad bad!
Why is this bad? It in the end binds your library to exactly this version, so if you have [clojure “1.1.0”] and someone wants to use it with 1.2.0 leiningen (or maven) will whine and do odd and scary things! Same if you have 1.2.0 and someone wants to use it with 1.1.0.
How to handle this? Again it is pretty simple, maven (and leiningen uses maven for dependencies) has version ranges so if you want to tell maven ‘I want clojure 1.2.0 or 1.1.0 or something in between’ you use [clojure “[1.1,1.2]”]. And there you go, now your library won’t cause a fuss!
As a note: don’t use [clojure “[1.1.0,1.2.0]”] this will break horribly!
Well this is all, I hope it helped.
leiningen - the clojure build tool
So I worked a bit with clojures build tool - I think that is how that kind of software is called - leiningen. It is a great tool even so it has some rough edges (most of them should be fixed with the next releases). A big advantage of it is that it is quite simple most of the tasks you do with lein 'just work' which is something I like a lot :P.
So I leiningen also alows more complex stuff, namely extending it with plugins. There are some very nice plugins but often people don't know about them so I figured, I go and try them out to see how they work. I will not be able to give detailed infos for the more specific plugins since I have no usage case for them.
lein-nailgun
From the webpage
A leiningen plugin to launch a vimclojure nailgun server.
Sadly I don't use thlein-nailgun adds the lein nailgun command.
lein-javac
From the webpage:
A javac plugin for Leiningen. It allows you to compile .java files in the src directory of the project. The compiled classes will be outputed to the classes directory of the project.
lein-gae
This plugin is used to create a war file that can be deployed to the Google App Engine. You get the command lein appengine-setup which creates a war directory with all the compiled classes and things.
I dind't deploy it but it seems easy enough to me.
WARNING
the appengine-setup will change your project.clj add some dependencies and aditional keys.
leiningen-war
lein war seems to give you the usual lein jar and lein uberjar commands with war files (java web archive) So adding this plugin you get lein war and lein uberwar. The webside gives a very detailed tutorual and from what I can tell this is a very nice pice of work. It also seems to allow you to do the same as lein-gae by creating appengine compatible war's.
Note:
Install the dep from uk.org.alienscience/leiningen-war not leiningen-war.
lein-scripts
This plugin lets you run scripts. It takes all files in the scripts directory and executes them. Simple and working. So one warning make sure to give them executable rights or lein scripts will throw ugly java arrays.
lein-cuke
I cancled after the jruby jar took over 10 minutes to download :(. Regarding to the webpage you can use the cocumber test framework with this lein extension. For those who don't know cucumber is a ruby framework to run specs.
Note:
Install the dep from org.clojars.wilkes/lein-cuke not line-cuke.
Also this depends heaviely on other jars, that includes 11MB of jruby.
lein-remote-swank
From the webpage:
This Leiningen plugin lets you launch a swank server on a remote machine via Leiningen.
Sadly I could not test it since it failed to find rsync but the documentation on the webpage seems quite well done and extensive.
lein-diagnostics
This plugin is quite simple but very nice. It installs without any further dependencies and gives you the lein diagnostics command that will show you basic diagnostics info about your current project and leiningen setup. This includes versions pathes and information about the java VM.
lein-clojars
lein-clojars is a plugin for leiningen that alows easy deployment of jars to the clojars maven repository. Ewww there I sayed the M-Word. But jokes aside it is a very simple tool that hides about everything I dislike about maven behind a few inches of steel. If you plan to release jars to the wield this is your friend.
The usage is simple, you've to create a account on clojars and then simply use lein push to push your jar to the. Done!
lein-swank
From the webpage:
This plugin lets you launch a swank server via Leiningen. Simply add [leiningen/lein-swank "1.1.0"] to your :dev-dependencies in project.clj and run "lein swank" to start the server.| Then from Emacs run M-x slime-connect to connect to your project. It is very helpful for the ECLIPS people out there.
lein-eclipse
Did not found much about this plugin but the jar describs it as:
A leiningen plugin to create Eclipse project descriptor files.lein-search
Hah finally I can tell you a lot about this one - hence it's mine :P - Well the idea is to make handling dependencies easiely. For example for all the test above I used it to get the latest versions of the librariy.
I think the best example is how I used it in this article. I careted a new lein project with lein new then I added [lein-serach "0.3.0-SNAPSHOT] to the project.clj (that was the only time I had to touch the project.clj!).
lein deps got the lein-search jar from clojars. I could skip the lein update-repo (which fetches the newest index from clojars.org) since I ran it earlyer today and it is user local. After that it was quite easy.
lein search -v <string> - allowed me to figure that some of the lein plugins are newer under org.clojars.* group id.
lein add <artifact> or lein add <artifact> - allowed me to add the newest stable version (or the unstable one) to leins project.clj.
Well so much about leiningen for now. It is a very good tool in my eyes and nice to use.
Playing wiht parenthethes
In the last weeks I've played a lot with a new language, clojure. I really like it even so I never thought I'd be a lisp person I got convinced otherwise, I love the language it's clean and beautiful. Aside of that the community is just great, you can ask questions without being told RTFM or equally non helpful things. And best of it all, there is a quite large German presence, even an own IRC channel #clojure.de. There are at least three books upcoming I heard from the IRC channels, which is quite cool since it seems that a lot of the community opinion/questions/ideas are taken into account here and they won't be purely theoretical, there is even a German book :D.
It is great fun to juggle with functions and pass them around and I finished my first library the other day: clj-sandbox. It's a result of a talk in the #clojure channel the idea is to provide an easy way to sandbox and it seems to work.
In related news, it looks as a first tech beta of the game a few friends and me are working on will be online soon. It will be a kind of a tournament game where each user can build their ships, fleets and AI's to battle each other and see who wins. The engine is entirely written in clojure and the AI scripts will work on clojure as well. In the start there will be limited accounts and they will be handed out in a first come first server manner, so if you're interested in trying our game out just drop a comment or send me a mail and I'll put you on the list.
EPIC news
So playing with clojure has spawned some useful results :). Namely EPIC, an engine for game combat. This is part of a huge secret project some friends and me are working on.
It works nicely so far, speed is - even so not at all optimized yet, quite good. Clojures STM and concurrency methods do a good job in allowing some stuff to run in paralell. So after a meeting on wednesday we decided to make this part of our project open source and publically available.
But what does EPIC do exactly? It calculates the outcome of fights mostly. You can define modules with certain propaties to them - a good set of them are predefined but you are free to extend them as you see fit if you want to do something else. Then once you've a set of modules they assamble to units, as in the unit is build from a certain cet of modules. Finally you toss in some AI for the units, stirr on medium heat and voila, you are ready for a fight.
We have set up a little demo if you want to see what EPIC can do. The fights on this page are all pre calculated and played back only (not live). But the calculation is done in EPIC with the current master branch from github with some custom modules.
So we don"t yet have a way to post combats yourself, but I can offer to add your own combats if you drop me a line and would like to see something. An example can be found here.
Have fun and let me know if you have any questions/suggestions.
Oh and you can find the source on github ;)/
