Hacker News new | past | comments | ask | show | jobs | submit login
GameLisp: Scripting language for Rust game development (gamelisp.rs)
231 points by sundarurfriend on Dec 21, 2020 | hide | past | favorite | 41 comments



> GameLisp has a novel object system, inspired by Andy Gavin's writings on GOOL, which is uniquely well-suited for game development.

https://gamelisp.rs/reference/introduction-for-rust-programm...

Neat, I was going to ask if it was influenced by any of the Lisp stuff that Andy Gavin did at Naughty Dog.


Found an old postmortem on Jak and Daxter from 2002 talking about pros and cons of using GOAL: https://www.gamasutra.com/view/feature/131394/postmortem_nau...

And Andy Gavin talked briefly about GOOL on Ars Technica Crash Bandicoot interview: https://youtu.be/pSHj5UKSylk?t=5893

It seems Naughty Dog later decided not to maintain their own Lisp and moved to Racket: https://www.youtube.com/watch?v=oSmqbnhHp1c

I am really curious about how they use Lisp nowadays.


nitpick: GOAL (Game Oriented Assembly Lisp) not GOOL.


GOOL is correct.

You are thinking of a different language that he made for the Jak & Daxter games.

https://all-things-andy-gavin.com/2011/03/12/making-crash-ba...


I stand corrected, thanks. I'll have to look into GOOL.


The (pretty and quite good!) Game Boy Tetris clone has successfully sucked me in and distracted me from the actual language design. https://gamelisp.rs/playground/#quadris

(There's also a faithful Minesweeper clone!)


I really, really like this. At this point, I am not even sure if Bevy even needs another language (rust can work pretty well for scripting-needs,after all), but if it comes to having an editor for designers and separating that kind of logic from the build process, this could work very well. Looking forward to trying this out in the next few days.


I was wondering about Bevy when I read this. Is it designed to work with Bevy? Or completely agnostic? I didn’t see Bevy mentioned. I’ve been learning Bevy and loving it

UPDATE: just read separate comment thread on Bevy.



I would strongly prefer to not have to change every call site of a function (and the functions containing the call sites, and so on) when adding 1 yield to it and to be able to make higher order functions that work on functions that yield and functions that do not yield.


Genuinely interesting suggestion!

I used Lua coroutines, Python generators and Ruby fibers as my prior art; in all three cases, they're resumed by invoking a method on the coroutine object.

A lambda function which resumes a coroutine can simply be written as:

  (fn0 (coro-run the-coro))
This is more ugly than passing in the coroutine directly, but it's also more explicit. I'm concerned that if the "resume coroutine" operation looks like a normal function call, the control flow of coroutines might become too difficult to follow.

To the best of my knowledge, the only way to get rid of `yield-from` would be to switch from stackless to stackful coroutines. GameLisp used to have stackful coroutines, but they added a large complexity burden to the virtual machine, and they were so expensive that I could never bring myself to use them for entity scripting. My instinct is that other game developers would feel the same way.

I'll need to give this some more thought.


Lua coroutines have exactly the thing I asked for though. As an example, if I have some enemy in a shmup that moves (over the course of many frames), fires some bullets in an arc centered on the player (within one frame), and moves again, I can write something like this:

  function ExampleEnemy:behavior()
    while true do
      self:move()
      self:fire_arc()
    end
  end

  function ExampleEnemy:fire_arc()
    local theta = self:aim()
    for i=-5,5 do
      self:fire({speed=8, direction=theta+i*10*degrees})
    end
  end
If I later want the arc to have some delay between the bullets, and I insert a call to a function that yields for a certain number of frames in the loop in fire_arc, the code continues to work. If I do this same change in Python, the result is that the call to fire_arc in behavior begins to return an iterator or something and silently fails to actually fire any bullets!

Lua's stackful coroutines are sufficiently cheap that it's reasonable to have tens of thousands of entity behaviors implemented as coroutines running at a time.


Very good point! I'm caught between a rock and a hard place...

Apart from their other downsides, stackful coroutines can make asynchronous code less readable. For example, it's not obvious that your self:move() call is split across several frames.

Implicit coroutine construction has the silent failure mode which you describe.

Explicit coroutine construction, (coro f), would make `yield-from` even more noisy than it already is!

I wonder whether a naming convention would solve the problem? If all coroutine functions and methods have names like fire-arc* and move* , then your synchronous call to fire-arc* would stick out like a sore thumb. This would also force the programmer to visit every callsite after changing a synchronous function into an asynchronous one, or vice versa. It wouldn't exactly be convenient, but I think I value explicitness more highly than convenience in this case.


> I wonder whether a naming convention would solve the problem? If all coroutine functions and methods have names like fire-arc* and move* , then your synchronous call to fire-arc* would stick out like a sore thumb.

That'd be fine as long it's simple to write a wrapper of a different color. If I change fire-arc to fire-arcSTAR, but I can do a quick (def fire-arc (to-sync fire-arcSTAR)) then I don't have to visit call-sites. If it's not possible to make a wrapper, then that would be a major pain point.

Edit: Can't get HN to print the asterisks.


But the * suffix marks functions which might stagger their execution over multiple frames. If a function calls fire-arc* , then that function should also have the * suffix; otherwise, the suffix would be meaningless.

You're asking for the ability to refactor a synchronous function into an asynchronous function without needing to review the function's callsites. By definition, this would come with a high price: when writing a step handler, you'd need to assume that any function call could be refactored so that it doesn't return until several frames in the future. Even for a Lisp, that seems chaotic!


I think we have different views about the process of scripting behaviors, which determines most of our conversation so far. I don't like taking algorithms in my mind, breaking them up into basic blocks by hand, and writing a switch statement in a step handler. I especially don't like iterating on code that has been manually turned inside out in this way. So I do not write any step handlers at all, barring extremely general things like "things move in the direction they are moving."


Sounds like an interesting approach - can you point me towards any open-source code written in this style?


I don't have any particular project in mind, but every BulletML game and many Danmakufu games are written this way. My favorite BulletML game is rRootage[0], but the scripts used in rRootage are procedurally generated combinations of scripts from other games, so they may not make good reading. I think Shiroi Danmakukun[1] features handmade scripts.

BulletML and Danmakufu's scripting language are not my favorite languages for other reasons.

[0]: http://www.asahi-net.or.jp/~cs8k-cyu/windows/rr_e.html

[1]: https://shinh.skr.jp/sdmkun/


Oh, thanks for the explanation. That makes things very clear.

I'm not sure when I'll get time to play with GameLisp, but it looks absolutely brilliant. I'll keep an eye on it; maybe some interesting open-source project will turn up that I can hack on.


Agreed. The "what colour is your function" [0] article comes to mind. Even with that, this is an impressive project.

[0] http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...


Cool, I'm working on Scheme in computer music and will keep this on my radar to look at how you handle GC. We have the same basic context, where running per frame (where frame is some number of samples) could work well.


Does it integrate well with Bevy? Does it support live reload?


Thanks for your interest!

The new reference manual for version 0.2 (which I'll release within a few days) discusses Bevy specifically. Short version: GameLisp isn't a natural fit for a multithreaded ECS, but it's possible to make it work.

Because the language is so dynamic and late-bound, it shouldn't be too difficult to hack in hotloading yourself. I intend to make it easier in the future. There's some preliminary discussion here:

https://github.com/fleabitdev/glsp/issues/10


So, the big advantage of GOOL was being able to evaluate an redefine functions in the image in the running game. It's unclear on a short read of the documentation if this is a goal of GameLisp


Hotloading is a near-future goal in the project's roadmap. There are some initial notes here, if you're curious:

https://github.com/fleabitdev/glsp/issues/10

Thanks to the last 25 years of improvements in CPU tech, it's less "patching raw binary artifacts into the running program" and more "recompiling the entire source tree in a couple of hundred milliseconds" :)


Pros and cons in comparison to Lua? Or simply using "rlua"?


The pros are summarised here:

https://gamelisp.rs/reference/introduction-for-rust-programm...

The main cons would be the lack of LuaJIT, the lack of C or C++ bindings, and the fact that Lua is spectacularly more mature and stable :)

(Full disclosure: I'm the project's developer)


Could use fennel [1], too.

[1] https://fennel-lang.org/


Depends on what type of syntax you enjoy I think.


Functional language approaches are very neat to encode game rules. Just define the sets to which a rule apply, define rule -> behaviour maps and then apply. With this alot of procedural/object orientated cruft shrinks down to one page algo&definitions.

Lua does supply this style, just needs some slight additions.


This is the most hacker-newsy title I've ever seen.


Does it have a REPL like in Clojure?


5 posts about various lisps have reached the top today. If lisp is so awesome and people end up building lisps on top of Rust and everything else, why do we need non-lisp languages like Rust itself anyway?


> If lisp is so awesome and people end up building lisps on top of Rust and everything else, why do we need non-lisp languages like Rust itself anyway?

Building Lisps on top of anything is extremely rare, especially in commercial environments.


It's common, including in "commercial environments" - eg. I've seen multiple things that would qualify as "lisps" built in banks.

Why? Probably because they are easy to make and extremely expressive given that limited effort. Is making an expressive custom language with no tooling to solve a single problem a good idea? Nope. Do people do it anyway? Yes.

Edit: And never forget Greenspun's 10th rule: "Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp."


> Edit: And never forget Greenspun's 10th rule: "Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp."

Maybe it's time to put this to rest?

The rule is 27 years old and I'd be shocked if something like the .Net BCL (for example) is not bigger than Common Lisp, these days. And I'm not sure we can get a consensus that the .Net BCL can be considered "ad hoc, informally-specified, bug-ridden, slow implementation".

That rule comes from a time when half the software written was still writing its own string and linked list data structures/classes. Now people just use standard libraries.


Emacs and every Emacs developers custom elisp setups would like to have a word with you. Of course, I'm not entirely sure of another example of mass adoption of Lisp in a commercial setting outside of Hacker News if you consider this setup commercial. I would argue HN definitely provides value back to YC by bringing plenty of exposure to their startups, but in all honesty I don't think a single soul on HN would complain against it, considering the alternative is much worse.


How is your comment contradicting mine? Emacs is 1 out of hundreds if not thousands of editors out there.


Why does mine have to contradict yours? I'm just saying, there's people who've used Emacs for 30 plus years to be productive.


This is a good point. The intro to gamelisp reads like so:

> Rust is my favourite programming language. It has impeccable performance, an expressive and powerful type system, and a great community.

> However, when it comes to game development, Rust has a few well-known problems. Compile times are painfully slow, and the type system can sometimes feel rigid or bureaucratic. When adding a new feature to a game, you'll often need to write code in a messy, fast, exploratory fashion - and in those cases, Rust can be a hindrance rather than a help.

Having to bolt a programming language from 1958 to your hip new language du jore to make it easier to use doesn't exactly make a good case for using it.


It's almost as though programming language design is full of trade-offs, and that building a game engine and building a game within that same engine are different enough tasks that they have radically different requirements w/r/t those trade-offs.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: