Always, when read about "functional language usage in simulation", I check if OpenGL binding used. Other important things are physics engine and collision engine, because these are more than 90% of all code.
Unfortunately, just as I suspected, this project use OpenGL (nearly all implementations are C++), and C++ collision/physics engine.
So, looks like, in this project, Clojure is used just as high level script to orchestrate all C++ parts, may be later we hear about some game scripting, but for simulators they are not as need as for example for RPGs.
I agree, Clojure is better than C++ for orchestrate, but I have seen so tiny number of art persons familiar with functional paradigm, so this looks like beautiful dead end.
Again, this is really beautiful and respectful achievement for author, but people I seen working in gamedev will not accept such approach.
Programming is a tiny part of game development. No programming language would blow anything in the indie game community. It would be nice and welcome, but it wouldn’t revolutionize anything.
> It would be nice and welcome, but it wouldn’t revolutionize anything.
I dunno, it'll certainly revolutionize my world once it's ready. A editor connected REPL, changes to running games on the fly while keeping existing state, using a well-designed language like Clojure but getting the performance (or similar) of C++ and native binaries.
It's pretty much a win-win-win for me, especially if I can replicate the speed of development I get with normal Clojure but for game dev.
I think Jank will find its people, but I don't know how many of those people will be indie game developers. I'm sure some will be, but on the whole I don't think most indie game devs are clamoring for using clojure, if only if it wasn't for the JVM or the performance. I doubt many indie game developers are even aware of what clojure or jank are, or even much about functional programming to be frank.
For indie game devs you're competing against engine ecosystems like Unity, Unreal, Godot. If someone is inclined for more of a DIY route, you're competing against Lua (love2D), C# (monogame), Javascript (...), or for the people who care about performance, C++, Rust, Odin, Zig, and soon even Jai. It's a very crowded competition space and again, I think overwhelmingly the people in this space aren't dreaming of programming in a functional style.
Then there are folks like Notch, that by not following such advices got gold, as they managed a great additive game, regardless of the technology stack being used.
There is also 80% of the mobile phone market available, in the context of JVM like ecosystem to target, and avoiding NDK tooling is gold if one really doesn't need it, as its experience still sucks after all these years.
I was not meaning to say that anyone was wrong for their technology choices. Personally I think Java is great and some of my favorite games are made in it. I'm just saying that I don't see jank or clojure for that matter catching on because it isn't where the head space of the indie gamedev scene is at, and I don't see this changing, especially given the number of competing stacks.
Lisps (like Clojure) treat code as data (lists), so you write: `(if x (y) (z))` instead of Python’s `y() if x else z()`. So the code is more concise, but does less to walk a novice through it.
This gains a huge advantage, which allows even more concision: all code is data, so its easy to transform the code.
In Clojure if you want to add support for unless, a thing like if, but evaluating the opposite way you could do this: `(defmacro unless [p a b] `(if (not ~p) ~a ~b))`. Obviously if you wanted to do the same thing in Python you would in practice do `z() if x else y()`. However, you would do it that way because Python isn't as powerful a language. To actually do the same thing in Python you would need to...
1. Add __future__ support.
2. Update the Python language grammar.
3. Add a new AST type.
4. Add a new pass stage to the compiler.
5. Add a python library to integrate with this so you could use it.
Then you could do something like:
from __future__ import macros
defmacro unless(pred, then: block, else_: block = []):
return q[
if not u(pred):
u*(then)
else:
u*(else_)
]
So in the trivial case its just hundreds of lines harder plus requires massive coordination with other people to accomplish the same feat.
This sort of, wow, it takes hundreds or thousands of lines more to accomplish the same thing outside of Lisp as it does to accomplish it within Lisp shows up quite often; consider something like chaining. People write entire libraries to handle function chaining nicely. `a.b().c().d().map(f).map(g)`. Very pretty. Hundreds of lines to enable it, maybe thousands, because it does not come by default in the language.
But in Lisp? In Clojure? Just change the languages usual rules, threading operator and now chaining is omnipresent: `(->> a b c d e (map f) (map g))`. Same code, no need to write wrapper libraries to enable it.
That doesn't look like a factor in the article though, he isn't using many if any macros that aren't part of the core language. And the one macro I do spot (defcfn) is pretty mild in context.
People like GP often repeat that talking point: "code is data so that's amazing because of macros".
In practice, by and large, with very few exceptions, macros are frowned upon in the same way that using metaprogramming in ruby is. Macros are only fun to the person writing them (and not even the author when they have to maintain it).
Macros can almost always be expressed with a simple function and remove all the unexpectedness without losing anything. Again, there are some exceptions.
> In practice, by and large, with very few exceptions, macros are frowned upon in the same way that using metaprogramming in ruby is. Macros are only fun to the person writing them (and not even the author when they have to maintain it).
I'm not sure "frowned upon" is the right expression, but I'm not a native speaker.
The way I've internalized it, is basically "Avoid macros unless there is no other way", which basically means use functions/anything else whenever you can, but if you absolutely have to use a macro for something (like you wanna read the arguments before they're parsed), then go for it.
Dunno about the Clojure communtity but for Emacs Lisp and Common Lisp there are certain broadly accepted idioms where macros are accepted:
1. "with-context", where there is a need to control resource allocation/deallocation or things in the context of code in question.
2. use-package dsl that simplify configuration in a predictable way
3. object definition helpersresource
Then, there are core language extensions and std libraries suggested for the main implementation. This is where macros are fine as they always get good documentation and plenty of additional eyeballs.
I always looked at these features of being able to extend the language beyond some commonly accepted practices as detrimental to the language. I've spent way too much time debugging issues with operator overloading or complex templates (C++), or obscure side effects in DSLs. So, (re)defining language constructs in a project seems nightmarish to me to support in production and therefore I never even attempted anything serious in a functional programming language.
But... looks like the professional community knows this and so maybe it's time to take a deeper dive :)
> So, (re)defining language constructs in a project seems nightmarish to me to support in production and therefore I never even attempted anything serious in a functional programming language.
It can be, but also not. If you isolate them into libraries with clear interfaces, you can kind of avoid that. I think clojure.core.async is an excellent showcase in something you couldn't do in other languages, where asynchronous channels were possible to add to the core language without changing anything in the core compiler itself, and because of the small interface, you can still use it without ending up with nightmares :)
Fun fact: the big difference isn't the syntax. Lisps only go from foo(bar baz) to (foo bar baz) which is a change but not really much of one. The change is actually the immutable and high performance basic data structures. Clojure can do something that something like C can't do - cheaply create a copy of something with a small change. That leads to a completely different preferred code style in the Clojure community that is a big departure from C-like languages which make heavy use of variables. The code is doing something practically different from what a C-like language can ergonomically handle.
Clojure has a bunch of other syntactic structures not found in other lisps that makes it a lot more visually noisy. I'm very comfortable with Scheme and I can very quickly absorb Scheme code when reading it, but I have to very slowly decipher the code in the article.
That's just existing muscle memory. Nothing is wrong with you and nothing is wrong with Clojure. I had the same feeling when I started with Lisp. Give it some time, it's absolutely worth it. Interestingly, every single programmer I introduced to Clojure as their very first programming language had no issues picking it up. Later, they complained about difficulties getting used to Javascript and Python.
Have you tried experimenting with ham-fisted? I've found the libraries in the techascent part of the Clojure ecosystem to be very good performance wise. Ditto for neanderthal.
Jan, This is awesome! I have been following your progress for quite some time now. I actually found your project because you liked my dream chaser model that I put on GitHub some time ago. Really Looking forward to what’s to come and to try out your simulator at some point!
I mean it is pretty cool, but do people not roll their own graphics engines anymore? When was in to hobby game dev back in 2000 or so, we all wrote our own systems.
The hardware graphics acceleration stack is heavily shader-based now, so there's less and less graphics code being written in systems languages like C. In a way, people are still writing their own graphics engines, it's just for such a different platform from the unusual Turing-computer CPUs that all the old techniques go out of the window.
Nothing stopping you from writing it the old fashioned way though - you can just keep generating a single screen texture in the CPU and let the GPU idle!
As old dog I find this kind of funny, because for folks of my age C was like C# is seen nowadays.
Any serious game would be pure Assembly, and when using Pascal, C or BASIC compilers, they would be full of inline Assembly, almost like a poor man's macro Assembler, as the quality of code generation was awful.
Using same game engines and physics lead to a generic look-and-feel, even if they do allow for a large amount of creativity and differences.
This _looks_ different, which is awesome!
Even if the atmospheric effects still need some honing, there's a ton of work around lighting to eventually be done, the edgy polys make it look about 20 years old, and it's a bit pixel-y around the edges, this is headed into a spectacular direction!
If my ADD were in charge of this project, here's what I'd add:
- Optional stars / environment - a universal simulation would be unrealistically computationally expensive, but just having stars would be neat. Later, a planet in the horseshoe nebula, or playing spherical versions of recorded or streaming video for AR or making homegrown music videos.
- Ability for others to share datasets - the Earth is f-ing awesome and I can't wait for the Moon! What about a place where users could share different datasets like Arrakis with it's sand dunes and 2 moons or Tatooine with its 3 moons, then maybe they could fly in a heighliner, landspeeder, frigate, or imperial lambda shuttle, or even the jetcar from Buckaroo Bonzai?
- Solar Mayhem - Simulate a crazy atmospheric and orbital space war simulation or arcade-style game with satellites, lasers, plasma / electrical discharges and arcing, dust and nanorobot clouds, cloaking, jamming, ramming, repairs by robots and soldiers in tethered spacesuits, zooming cameras and 2D/3D scanners in different wavelengths, spacefaring naval ships, UAPs and other secret government vehicles, and complex 20th century fantasies of space stations running on nuclear and otherworldly power.
- Eclipse Support - when you add the Moon, doing an eclipse is not just the shadow but you'll need to handle the cool colors on the edges when the moon is covering the Sun.
- Ocean Simulation - Orcas, fish, eels, coral, lobsters, octopi, old ruins, Atlantis with its merpeople, tictacs and other USOs!
- Beautiful water features in Baltic Sea, Yukon Delta, Mississippi River, Lena River, Petermann Glacier, Brunt Ice Shelf, South Georgia Island, Guinea-Bissau, New Caledonia, Patagonian Sea, and the Icelandic and Norwegian fjords.
- Weather simulation with a way to pull in current atmospheric data historically to fly through hurricanes and tornados or simulate tsunamis after earthquakes.
Always, when read about "functional language usage in simulation", I check if OpenGL binding used. Other important things are physics engine and collision engine, because these are more than 90% of all code.
Unfortunately, just as I suspected, this project use OpenGL (nearly all implementations are C++), and C++ collision/physics engine.
So, looks like, in this project, Clojure is used just as high level script to orchestrate all C++ parts, may be later we hear about some game scripting, but for simulators they are not as need as for example for RPGs.
I agree, Clojure is better than C++ for orchestrate, but I have seen so tiny number of art persons familiar with functional paradigm, so this looks like beautiful dead end.
Again, this is really beautiful and respectful achievement for author, but people I seen working in gamedev will not accept such approach.
What would have been an alternative way to go here that would have been more acceptable to gamedevs?
I just can't wait to see how Jank gets production-ready and absolutely blows the indie gaming community. Hopefully, very soon.
Programming is a tiny part of game development. No programming language would blow anything in the indie game community. It would be nice and welcome, but it wouldn’t revolutionize anything.
> It would be nice and welcome, but it wouldn’t revolutionize anything.
I dunno, it'll certainly revolutionize my world once it's ready. A editor connected REPL, changes to running games on the fly while keeping existing state, using a well-designed language like Clojure but getting the performance (or similar) of C++ and native binaries.
It's pretty much a win-win-win for me, especially if I can replicate the speed of development I get with normal Clojure but for game dev.
If it is built in Lisp it will end up very customized. Just look at how far people take their emacs setups. It will be like a bespoke glove.
Yes, you don't have to keep on selling it to me, you've hit oil already! I'm eager to reach the future :)
I think Jank will find its people, but I don't know how many of those people will be indie game developers. I'm sure some will be, but on the whole I don't think most indie game devs are clamoring for using clojure, if only if it wasn't for the JVM or the performance. I doubt many indie game developers are even aware of what clojure or jank are, or even much about functional programming to be frank.
For indie game devs you're competing against engine ecosystems like Unity, Unreal, Godot. If someone is inclined for more of a DIY route, you're competing against Lua (love2D), C# (monogame), Javascript (...), or for the people who care about performance, C++, Rust, Odin, Zig, and soon even Jai. It's a very crowded competition space and again, I think overwhelmingly the people in this space aren't dreaming of programming in a functional style.
Then there are folks like Notch, that by not following such advices got gold, as they managed a great additive game, regardless of the technology stack being used.
There is also 80% of the mobile phone market available, in the context of JVM like ecosystem to target, and avoiding NDK tooling is gold if one really doesn't need it, as its experience still sucks after all these years.
I was not meaning to say that anyone was wrong for their technology choices. Personally I think Java is great and some of my favorite games are made in it. I'm just saying that I don't see jank or clojure for that matter catching on because it isn't where the head space of the indie gamedev scene is at, and I don't see this changing, especially given the number of competing stacks.
Come to think of it, where is the headspace of the Indie gaming community at?
Ah, that is a good point.
Guile has [multi-methods][1] and [fast hash maps][2], but not yet [dynamic vectors][3].
Clojure's data structures are easier to use, though.
[1]: https://www.gnu.org/software/guile/manual/html_node/Methods-...
[2]: https://www.gnu.org/software/guile/manual/html_node/Hash-Tab...
[3]: https://lists.gnu.org/archive/html/guile-devel/2022-01/msg00...
Clojure is such a departure for me, coming from C-Like languages. I have absolutely no idea whats going when looking at the code.
Lisps (like Clojure) treat code as data (lists), so you write: `(if x (y) (z))` instead of Python’s `y() if x else z()`. So the code is more concise, but does less to walk a novice through it.
This gains a huge advantage, which allows even more concision: all code is data, so its easy to transform the code.
In Clojure if you want to add support for unless, a thing like if, but evaluating the opposite way you could do this: `(defmacro unless [p a b] `(if (not ~p) ~a ~b))`. Obviously if you wanted to do the same thing in Python you would in practice do `z() if x else y()`. However, you would do it that way because Python isn't as powerful a language. To actually do the same thing in Python you would need to...
1. Add __future__ support.
2. Update the Python language grammar.
3. Add a new AST type.
4. Add a new pass stage to the compiler.
5. Add a python library to integrate with this so you could use it.
Then you could do something like:
So in the trivial case its just hundreds of lines harder plus requires massive coordination with other people to accomplish the same feat.This sort of, wow, it takes hundreds or thousands of lines more to accomplish the same thing outside of Lisp as it does to accomplish it within Lisp shows up quite often; consider something like chaining. People write entire libraries to handle function chaining nicely. `a.b().c().d().map(f).map(g)`. Very pretty. Hundreds of lines to enable it, maybe thousands, because it does not come by default in the language.
But in Lisp? In Clojure? Just change the languages usual rules, threading operator and now chaining is omnipresent: `(->> a b c d e (map f) (map g))`. Same code, no need to write wrapper libraries to enable it.
That doesn't look like a factor in the article though, he isn't using many if any macros that aren't part of the core language. And the one macro I do spot (defcfn) is pretty mild in context.
I've programmed in Clojure professionally.
People like GP often repeat that talking point: "code is data so that's amazing because of macros".
In practice, by and large, with very few exceptions, macros are frowned upon in the same way that using metaprogramming in ruby is. Macros are only fun to the person writing them (and not even the author when they have to maintain it).
Macros can almost always be expressed with a simple function and remove all the unexpectedness without losing anything. Again, there are some exceptions.
> In practice, by and large, with very few exceptions, macros are frowned upon in the same way that using metaprogramming in ruby is. Macros are only fun to the person writing them (and not even the author when they have to maintain it).
I'm not sure "frowned upon" is the right expression, but I'm not a native speaker.
The way I've internalized it, is basically "Avoid macros unless there is no other way", which basically means use functions/anything else whenever you can, but if you absolutely have to use a macro for something (like you wanna read the arguments before they're parsed), then go for it.
Dunno about the Clojure communtity but for Emacs Lisp and Common Lisp there are certain broadly accepted idioms where macros are accepted:
1. "with-context", where there is a need to control resource allocation/deallocation or things in the context of code in question. 2. use-package dsl that simplify configuration in a predictable way 3. object definition helpersresource
Then, there are core language extensions and std libraries suggested for the main implementation. This is where macros are fine as they always get good documentation and plenty of additional eyeballs.
Fully agreed with your examples. As a rule of thumb macros should be kept inside libraries, not application code.
Thank you for posting this.
I always looked at these features of being able to extend the language beyond some commonly accepted practices as detrimental to the language. I've spent way too much time debugging issues with operator overloading or complex templates (C++), or obscure side effects in DSLs. So, (re)defining language constructs in a project seems nightmarish to me to support in production and therefore I never even attempted anything serious in a functional programming language.
But... looks like the professional community knows this and so maybe it's time to take a deeper dive :)
> So, (re)defining language constructs in a project seems nightmarish to me to support in production and therefore I never even attempted anything serious in a functional programming language.
It can be, but also not. If you isolate them into libraries with clear interfaces, you can kind of avoid that. I think clojure.core.async is an excellent showcase in something you couldn't do in other languages, where asynchronous channels were possible to add to the core language without changing anything in the core compiler itself, and because of the small interface, you can still use it without ending up with nightmares :)
This was incredibly useful
Fun fact: the big difference isn't the syntax. Lisps only go from foo(bar baz) to (foo bar baz) which is a change but not really much of one. The change is actually the immutable and high performance basic data structures. Clojure can do something that something like C can't do - cheaply create a copy of something with a small change. That leads to a completely different preferred code style in the Clojure community that is a big departure from C-like languages which make heavy use of variables. The code is doing something practically different from what a C-like language can ergonomically handle.
Clojure has a bunch of other syntactic structures not found in other lisps that makes it a lot more visually noisy. I'm very comfortable with Scheme and I can very quickly absorb Scheme code when reading it, but I have to very slowly decipher the code in the article.
Many of Lisp ideas are possible in C++, but I guess that depends how much you know it.
That's just existing muscle memory. Nothing is wrong with you and nothing is wrong with Clojure. I had the same feeling when I started with Lisp. Give it some time, it's absolutely worth it. Interestingly, every single programmer I introduced to Clojure as their very first programming language had no issues picking it up. Later, they complained about difficulties getting used to Javascript and Python.
it's not code it's data. :) -macro
As a Clojure developer for many years, this is one of the coolest projects I've seen! Going to read your blog with interest!
Very cool work Jan!
Have you tried experimenting with ham-fisted? I've found the libraries in the techascent part of the Clojure ecosystem to be very good performance wise. Ditto for neanderthal.
Jan, This is awesome! I have been following your progress for quite some time now. I actually found your project because you liked my dream chaser model that I put on GitHub some time ago. Really Looking forward to what’s to come and to try out your simulator at some point!
This is awesome! Very nice example of malli in practice!
Wow, this is impressive not using standard gaming framework like Unity or Unreal.
I mean it is pretty cool, but do people not roll their own graphics engines anymore? When was in to hobby game dev back in 2000 or so, we all wrote our own systems.
The hardware graphics acceleration stack is heavily shader-based now, so there's less and less graphics code being written in systems languages like C. In a way, people are still writing their own graphics engines, it's just for such a different platform from the unusual Turing-computer CPUs that all the old techniques go out of the window.
Nothing stopping you from writing it the old fashioned way though - you can just keep generating a single screen texture in the CPU and let the GPU idle!
As old dog I find this kind of funny, because for folks of my age C was like C# is seen nowadays.
Any serious game would be pure Assembly, and when using Pascal, C or BASIC compilers, they would be full of inline Assembly, almost like a poor man's macro Assembler, as the quality of code generation was awful.
No, that's rightfully viewed as a waste of time if you want to make a game (vs if you want to make a game engine)
Using same game engines and physics lead to a generic look-and-feel, even if they do allow for a large amount of creativity and differences.
This _looks_ different, which is awesome!
Even if the atmospheric effects still need some honing, there's a ton of work around lighting to eventually be done, the edgy polys make it look about 20 years old, and it's a bit pixel-y around the edges, this is headed into a spectacular direction!
If my ADD were in charge of this project, here's what I'd add:
- Optional stars / environment - a universal simulation would be unrealistically computationally expensive, but just having stars would be neat. Later, a planet in the horseshoe nebula, or playing spherical versions of recorded or streaming video for AR or making homegrown music videos.
- Ability for others to share datasets - the Earth is f-ing awesome and I can't wait for the Moon! What about a place where users could share different datasets like Arrakis with it's sand dunes and 2 moons or Tatooine with its 3 moons, then maybe they could fly in a heighliner, landspeeder, frigate, or imperial lambda shuttle, or even the jetcar from Buckaroo Bonzai?
- Solar Mayhem - Simulate a crazy atmospheric and orbital space war simulation or arcade-style game with satellites, lasers, plasma / electrical discharges and arcing, dust and nanorobot clouds, cloaking, jamming, ramming, repairs by robots and soldiers in tethered spacesuits, zooming cameras and 2D/3D scanners in different wavelengths, spacefaring naval ships, UAPs and other secret government vehicles, and complex 20th century fantasies of space stations running on nuclear and otherworldly power.
- Eclipse Support - when you add the Moon, doing an eclipse is not just the shadow but you'll need to handle the cool colors on the edges when the moon is covering the Sun.
- Ocean Simulation - Orcas, fish, eels, coral, lobsters, octopi, old ruins, Atlantis with its merpeople, tictacs and other USOs!
- Beautiful water features in Baltic Sea, Yukon Delta, Mississippi River, Lena River, Petermann Glacier, Brunt Ice Shelf, South Georgia Island, Guinea-Bissau, New Caledonia, Patagonian Sea, and the Icelandic and Norwegian fjords.
- Weather simulation with a way to pull in current atmospheric data historically to fly through hurricanes and tornados or simulate tsunamis after earthquakes.
- Subterreania and the inner sun of the Earth.
- A 2D sim for flat earthers.
This was an amazing blog post, thank you!
Beautiful visuals. I'd like something to dock with.