Settings

Theme

Show HN: Lyceum – An MMO game built with Zig and Erlang

github.com

148 points by schonfinkel a year ago · 71 comments · 1 min read

Reader

Hey HN, this is a small project myself and closer friends have been building on our free time (https://github.com/Dr-Nekoma/lyceum), it finally reached 0.1.0, we are open to feedback!

The original idea was to experiment with Zig + Raylib, eventually we wrote a small server in Erlang as well. We started by first interacting with Erlang via its C bindings, but this eventually led to some of us to prototyping our own tooling to better integrate Zig types with Erlang, we called such tool "zerl" as its avaliable here https://github.com/dont-rely-on-nulls/zerl.

Most of the developers are NixOS users, so the tooling heavily relies on Nix as well, including a Postgres running our devshell as well.

Closi a year ago

Pretty amazing effort, this looks like a great labour of love!

I can't give feedback on the code/technology, but on the writing on the lore section, I would try to simplify the writing. For instance the following:

> The reverberations of the trumpet stirred the knights from their deep repose, igniting a tumultuous awakening. With swords unsheathed and hearts ablaze, they clashed in a thunderous symphony of war, each seeking to claim dominance over the waking realm.

Feels too ornate (purple prose) and could be more directly put as:

> The trumpet’s call jolted the knights from their rest. Swords drawn and hearts alight, they clashed in a fierce battle, each striving for dominance.

I'm not an author or anything, but a little bit of copy writing could help - although this might just be me as it's probably a matter of personal taste!

  • bitterblotter a year ago

    This definitely has a GPT smell, at no fault of the creator. Reminds me of a PR i reviewed about a simple security patch, where the description said "The following changes have been implemented to strengthen our role-based access policies and system security" - Like yeah, it's security patch. A small tweak to his prompt would probably do the trick

  • skulk a year ago

    It's definitely a matter of taste. The first version has flavor, the second is flat. The first reminds me of the style of writing that got me hooked on MUDs.

  • nonethewiser a year ago

    It's a style thing. Fantasy writing often does this deliberately.

    I've thought about this before when I revisited fantasy after years of being in the CS domain which helped me abhor ornate writing. I definitely think there is such thing as TOO ornate but dead-simple language also feels bad. It feels wrong to just say its an exception with fantasy - simplicity is good because it conveys the same thing more clearly and with less effort. I would think that transcends all domains. Still not sure how I feel about this. I guess there is a baseline non-styled language that is all about communicating raw info and then there is style that can be applied to writing which makes it feel more natural in different domains.

    Having said all this, I actually do like your example more.

    • Closi a year ago

      You are right - it's a balance and definitely a matter of taste.

      Although not-ornate doesn't necessarily mean dead-simple or bad. For instance compare the following:

      > The reverberations of the trumpet stirred the knights from their deep repose, igniting a tumultuous awakening.

      With a very similar sentence from Tolkein:

      > At that moment, among the trees nearby, a horn rang out. It rent the night like fire on a hill-top. Awake! Fear! Fire! Foes! Awake!

      This is much less ornate, with simpler language, yet easier to parse and the image is much more vivid.

      • danenania a year ago

        From a writing perspective, I think much of it is about “weighting”. If you make every line ornate and full of adjectives, then nothing stands out.

        Therefore for not-so-important details like “the knight woke up” that are just about giving the reader necessary info to follow along, it’s generally better to put less weight and emphasis by stating it plainly. This way when you do add emphasis to make the reader visualize a crucial scene or situation, or describe emotional states at these moments etc, they will jump out as being special rather than just more of the same.

        In my experience, every great writer follows this pattern, though they begin at different baselines. It’s fundamental to good writing, just like creating attention hierarchy is fundamental to good graphic design.

      • nonethewiser a year ago

        That's a good example. I agree the imagery is strong with the Tolkien example. It also struck me as how you might say it if you are verbally telling the story. The "among the trees nearby" interlude and the successive exclamations at the end.

        • sigbottle a year ago

          Exactly. Even though I understand every word of this quote

          > The reverberations of the trumpet stirred the knights from their deep repose, igniting a tumultuous awakening. With swords unsheathed and hearts ablaze, they clashed in a thunderous symphony of war, each seeking to claim dominance over the waking realm.

          there's a distinct feeling of disconnect, I guess? That language feels much more appropriate, when say, you're on top of a mountain and admiring the beautiful landscape around you. Tolkein's words capture the urgency and adrenaline of war with his simpler sentences.

          I suppose I'd need to see the context behind the original quote; in a historical lore recap, I'm more happy with that quote.

          (Not going to pretend like I know precisely what's different as it's all subjective, but I suspect the mood you're trying to go for heavily impacts your writing choice)

      • pandemic_region a year ago

        So when there is less ornate and difficult language to parse for the brain, there are more cycles available for imagination? Indeed z a thin line to balance.

    • inopinatus a year ago

      True high fantasy would not reference swords and trumpets, first spending a chapter or two defining a world with its own musical instruments and bladed weapons, their names of course also being in a language, sorry, tongue, for which the author must also first labour to invent a common version spoken by everyone, an ancient/high version only remembered by a few, and at least one alphabet.

      In addition, each one must be described in detail, including a potted life story of the blacksmith that created it; when, why, and for whom; metallurgical observations; history of actual use; any supernatural blessings whether apocryphal or actual; the litany of families that have retained it as an heirloom & their subsequent social or political fates; details of any inscription or filigree; and a nickname. This remains true for both the swords and the trumpet. Additional remarks concerning a scabbard or case are optional but highly regarded.

  • Kinrany a year ago

    The short version is better.

  • wyldfire a year ago

    Maybe we could throw a "lurgid bee" in there somewhere?

  • dgfitz a year ago

    The colorful version is better.

mtlynch a year ago

What's your experience been like with Nix?

How do you feel about devenv vs stock Nix? How are you getting devenv to work, as I don't see a devenv.nix file. I'm still a Nix beginner and would like to find ways of integrating it more into my development and improving my current techniques.[0]

[0] https://mtlynch.io/notes/nix-dev-environment/

  • fluidwizard a year ago

    So, we've been using devenv for some time now and it's useful for describing a monorepo-like environment that zig-enjoyers can quickly code and test their changes on the latest version of the erlang server. Also, it's incredibly easy to also manage postgres from there.

    It's great if you like local-first development experience.

    • mtlynch a year ago

      I looked harder at the code to figure out how you're using devenv without a devenv.nix file.

      I now see that you're using devenv from within your flake.nix, which I didn't realize you could do.[0] Neat!

      I'm going to give that a spin in my projects, as my current solution for pinning versions of Go, Zig, etc. is to use nixhub to look up which commit of nixpkgs corresponds to which version of Go (e.g., Go 1.23.2 is nixpkgs version 4ae2e647537bcdbb82265469442713d066675275). That's obviously a pain to look up and performs poorly, so I'm curious to see how devenv goes.

      Thanks for sharing the source!

      [0] https://github.com/Dr-Nekoma/lyceum/blob/1b0acf2d4bf295135bb...

fluidwizard a year ago

Regarding Zerl, my friend just presented about it in Functional Programming Sweden: https://www.youtube.com/watch?v=5Cuv0WnbZtk&t=795s.

diath a year ago

The BEAM is made with fault tolerance, scalability, and concurrency in mind at the cost of performance due to immutability and message passing approach among other things, which sounds like a terrible choice for a multiplayer video game that's anything more than a walking simulator. Erlang sounds like a good choice for auxiliary video game services, such as chat/social, guild management, auction house and so on, not so much for the game shard itself.

  • DuLR10 a year ago

    Given the current state of the game (the one we keep passing around between client and server), I don't think Erlang is a bottleneck in performance for us, and it won't be in the near future. Keep in mind this state is not that huge and it is yet not distributed, so it is totally possible that at some point Erlang will slow us down. I will keep your comment in mind, thank you!

    Edit: currently with the game running at 60fps, there is no bottleneck from the server side. And we call it every 16ms! I should also mention that Erlang's choice has a learning purpose; we want to try to use the game as a way to learn more about OTP and the BEAM.

  • toast0 a year ago

    If it comes down to immutability and copies being a bottleneck, a solution is probably to move more of the core game state into native code (like with Zig), but fault tolerance, scalability, and concurrency seem like important things for a MMO.

    Hot loading is pretty nice too.

  • cmdrk a year ago

    it really depends on the game. people have been writing large-scale multiplayer games for over 25 years now, with MMOs sporting 2,000 player+ shards on significantly more primitive hardware. There is always this assertion that high-level languages of various flavors are too slow for games, but I suspect that today's hardware more than makes up for it for the right kind of game.

racenis a year ago

Do you think that you could use that Erlang feature where you can link up several server program instances running on separate physical servers?

Maybe you could simulate different parts of the game world on different physical servers.

I think this is something like what the Very Large MMOs do, but with Erlang it might be easier.

  • DuLR10 a year ago

    Sounds like an awesome idea for when we introduce the Server Browser! Thank you!

mapcars a year ago

Its always nice to see people experimenting with different technologies.

I'm curious about Erlang server, do you see any advantage or features that Erlang provides, compared to for example if the server was running in Python via multiple instances?

  • DuLR10 a year ago

    We haven't touched the distributed part of the game, but our understanding is that when that time comes, it will be easier to use the BEAM approach given that it was made for this purpose.

    Given the experience so far, it seems that using Erlang was the correct choice, not only because of the above, but also because Erlang made the server implementation way easier than we thought.

    • mapcars a year ago

      I see now, you are sending messages directly to Erlang server so you don't have to worry about network sockets.

      In my experience the issues with Erlang come with working with data structures, records are not flexible and there is not much one can do to abstract the boilerplate.

      • jhgg a year ago

        Having a potentially untrusted client connect to the erlang node as a c_node (which seems to be what zerl does) is not a good idea generally, as connecting that way essentially allows the client to execute arbitrary code on the server.

        • DuLR10 a year ago

          Please correct me if I say anything wrong.

          As far as I can tell, this is not possible at all; the serialization layer (Zerl) cannot send arbitrary code to another node. Now, assuming we implement this, I also think this is not possible due to how the server is designed; based on supervisors and child processes for user sessions.

          We recently became aware that you can indeed send tuples that have fixed effects when using the supervisor behavior, so it may be totally possible and probable that one could exploit this vulnerability to some degree in our server. We plan to investigate more about it as we continue to learn more about OTP and the BEAM.

          • toast0 a year ago

            If you're using zerl on the client and plain dist on the server; the question isn't what Zerl can serialize, but what the server will process.

            With stock OTP dist, there is no barrier between nodes. Stock OTP runs an rpc server that you can use to send function calls to run, which can include BEAM code to load (or file I/O to do); and even if that's disabled, you can spawn a process to run a function with arguments on a remote node without needing an rpc server at all.

            • DuLR10 a year ago

              How can one protect the server then? Do we need some special behavior and/or library?

              • toast0 a year ago

                I'm not aware of anyone running a limited dist to allow for untrusted dist clients. But here's an OTP response to a proposal that's pretty clearly a no [1].

                It'd be much simpler to put together a custom protocol to communicate between the client and server. You could use Erlang's External Term Format to exchange data if you want, in which case you'd want to do binary_to_term(Binary, [safe]) to prevent creation of new atoms and new function references which can fill up tables and also consider that just because deserializing is safe for the runtime doesn't mean you can trust the client.

                Erlang makes it pretty easy to parse sensible things off of network sockets, if you want to go more custom, too. Binary pattern matching is lovely.

                [1] https://erlangforums.com/t/rfc-erlang-dist-security-filterin...

                • DuLR10 a year ago

                  Thanks for the ideas and references! I gotta say, though, that I will be pretty sad if having to write a custom protocol turns out to be the final solution. So much more convenient to use OTP (especially now that we finally have an infinitely extensible serialization library for it; Zerl). I'm shocked such an oversight would exist in a real commercial solution which is the BEAM.

                  • toast0 a year ago

                    The original application of dist clustering was dual computers in a single telecom switch. There's not really a need for a security barrier in that case; anyone with access to one computer would be expected to have access to the other.

                    Additional applications for dist have been explored over the years, but most of them involve clustering servers; where a security barrier isn't necessary; although it might be desirable --- I've used dist clusters where some people had access to only certain types of nodes; bypassing access control using dist clustering was certainly a possibility. Bolting security onto something designed without it often is pretty challenging. Especially if you want to keep all the existing applications working.

                  • mostatik a year ago

                    There's a good (new) library in Elixir that may work for this use case called Zigler https://hexdocs.pm/zigler/Zig.html

                  • zbentley a year ago

                    As another commenter said, OTP messages are meant to be between processes in the same privilege zone. That said, using a custom protcol via a good library can actually bring benefits relative to core OTP stuff.

                    For example, several of the gRPC libs I've used for Erlang/Elixir are pretty low-cognitive-overhead to use, and they come with all the added gRPC goodies: RPC semantics are described in one place rather than ad-hoc throughout code, protobufs have at least a documented (if not actually good) process for upgrades and backwards compatibility, multilanguage gets easier (even if your second language is just a tiny sliver of "dump protobufs into a database/Jupyter notebook/Rust program occasionally for offline reporting").

                    To be clear, this isn't a paean to gRPC; most of those features are table stakes for an IDL-driven protocol definition. Just saying that you do get some things in return for giving up the convenience of OTP, if you pick the right tools.

        • fluidwizard a year ago

          I think this behavior can be fixed by properly using something like `lib_chan`, but we needed something that worked first for our Func Prog Sweden demo.

          Indeed a malicious client can craft an brutal kill message as long as it knows the PID a process (either a worker or a supervisor) for instance.

      • DuLR10 a year ago

        We are using maps all over the place in the server, and so far nothing has been annoying.

        I gotta say though that lack of infix custom operators for the monadic bind is a pain.

        • klibertp a year ago

          Take a look at Erlang's "parse transforms" which would allow you to implement some syntactic sugar. That's what is used to implement qlc, the query language for ETS/Mnesia - that one adds new semantics to list comprehensions, but you can modify any part of the syntax you want.

          Also, Elixir supports macros and infix operator overloading - have you considered using it? If you know Erlang's stdlib and BEAM, switching to Elixir (and back) is almost painless. Not sure which monads you needed, but `with` macro is built-in, and it's a monadic bind for Option types (well, more or less). Adapting it for other monads shouldn't be hard.

          • DuLR10 a year ago

            Thanks, I will for sure take a look!

            • klibertp a year ago

              For reference (for the "parse transform" approach in Erlang): https://github.com/rabbitmq/erlando - it doesn't look maintained, but it's probably still usable; otherwise, you might get some inspiration from the code :) This also (ab)uses list comprehension syntax:

                  write_file(Path, Data, Modes) ->
                      Modes1 = [binary, write | (Modes -- [binary, write])],
                      do([error_m ||
                          Bin <- make_binary(Data),
                          Hdl <- file:open(Path, Modes1),
                          Result <- return(do([error_m ||
                                               file:write(Hdl, Bin),
                                               file:sync(Hdl)])),
                          file:close(Hdl),
                          Result]).
              
              (if one of the calls returns `{error, Reason}` tuple, the execution terminates and the tuple is returned; otherwise, `{ok, Value}` tuple is unpacked)
  • fluidwizard a year ago

    Also, Erlang makes it bizarrely simple to have a single process per user (and it's actually what we did for the demo).

mysterydip a year ago

I'm confused by the use of a 10,000 poly model for a tile instead of a flat (2 poly) square. I didn't even realize there were changes in its height from the animation on the page. Was it just to test things out and you plan to go to a full terrain later?

  • DuLR10 a year ago

    Former case. Just testing things out. We will for sure have to either learn Blender and this ecosystem or find someone with this skill set.

canadiantim a year ago

Seems cool, would love to see a couple more videos of gameplay, but otherwise looks really cool. I would definitely consider playing

subsaharancoder a year ago

Did you happen to tinker with https://machengine.org/ it's a Zig game engine & graphics toolkit?

7bit a year ago

I was clicking the source expecting to see what kind of game Lyceum is. There is no description about the game and no images of the game.

pmarreck a year ago

Props for using Nix.

lovegrenoble a year ago

Will the game evolve further, and what is its future?

  • DuLR10 a year ago

    The main 3D part of the game is now on pause. We'll implement minigames (2D ones) leveraging all the current game's Zig and Erlang pieces. After getting that done, we may return to developing the main game.

  • fluidwizard a year ago

    Yeah, so the game is kinda in a weird state where a library was born to make it work better, we are planning more features so it stops being a multiplayer walking simulator.

newobj a year ago

is Lyceum a reference to Ultima?

lionkor a year ago

> the client written in Zig (superchanged with raylib and Zerl).

What does "supercharged" mean here? I'd guess raylib simply does all the rendering and input handling, what is it "supercharging" here?

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection